Compare commits
6 Commits
19fa52ca65
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 047e155238 | |||
| e42ce8b092 | |||
| 7b00887372 | |||
| 88e526cf6c | |||
| d8ae8c8894 | |||
| 28c225687a |
@@ -20,4 +20,50 @@ class FileHelper
|
|||||||
default => 'document'
|
default => 'document'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getFileIconClass($filename)
|
||||||
|
{
|
||||||
|
$fileType = self::getFileType($filename);
|
||||||
|
|
||||||
|
$classes = [
|
||||||
|
'pdf' => 'text-red-500',
|
||||||
|
'word' => 'text-blue-500',
|
||||||
|
'excel' => 'text-green-500',
|
||||||
|
'image' => 'text-yellow-500',
|
||||||
|
'archive' => 'text-purple-500',
|
||||||
|
'text' => 'text-gray-500',
|
||||||
|
'powerpoint' => 'text-orange-500',
|
||||||
|
'document' => 'text-gray-400'
|
||||||
|
];
|
||||||
|
|
||||||
|
return $classes[$fileType] ?? 'text-gray-400';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getFileIconSvg($filename)
|
||||||
|
{
|
||||||
|
$fileType = self::getFileType($filename);
|
||||||
|
$colorClass = self::getFileIconClass($filename);
|
||||||
|
|
||||||
|
$icons = [
|
||||||
|
'pdf' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||||
|
</svg>',
|
||||||
|
'word' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||||
|
</svg>',
|
||||||
|
'excel' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||||
|
</svg>',
|
||||||
|
'image' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/>
|
||||||
|
</svg>',
|
||||||
|
'archive' => '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/>
|
||||||
|
</svg>'
|
||||||
|
];
|
||||||
|
|
||||||
|
return $icons[$fileType] ?? '<svg class="w-5 h-5 '.$colorClass.'" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/>
|
||||||
|
</svg>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
53
app/Http/Controllers/CompanyContactController.php
Normal file
53
app/Http/Controllers/CompanyContactController.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class CompanyContactController extends Controller
|
||||||
|
{
|
||||||
|
public function store(Request $request, Company $company)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'user_id' => 'required|exists:users,id',
|
||||||
|
'position' => 'nullable|string|max:100'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Evitar duplicados
|
||||||
|
if (!$company->contacts()->where('user_id', $request->user_id)->exists()) {
|
||||||
|
$company->contacts()->attach($request->user_id, [
|
||||||
|
'position' => $request->position
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->with('success', 'Contacto agregado exitosamente');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->with('error', 'Este contacto ya está asociado a la empresa');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, Company $company, User $contact)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'position' => 'nullable|string|max:100'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$company->contacts()->updateExistingPivot($contact->id, [
|
||||||
|
'position' => $request->position
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->with('success', 'Cargo del contacto actualizado');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Company $company, User $contact)
|
||||||
|
{
|
||||||
|
$company->contacts()->detach($contact->id);
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->with('success', 'Contacto eliminado de la empresa');
|
||||||
|
}
|
||||||
|
}
|
||||||
143
app/Http/Controllers/CompanyController.php
Normal file
143
app/Http/Controllers/CompanyController.php
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Company;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CompanyController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$companies = Company::orderBy('name')->paginate(10);
|
||||||
|
return view('companies.index', compact('companies'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view('companies.form');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'commercial_name' => 'required|string|max:255',
|
||||||
|
'status' => 'required|in:active,closed',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
'postal_code' => 'nullable|string|max:10',
|
||||||
|
'city' => 'nullable|string|max:100',
|
||||||
|
'country' => 'nullable|string|max:100',
|
||||||
|
'phone' => 'nullable|string|max:20',
|
||||||
|
'email' => 'nullable|email|max:255',
|
||||||
|
'cif' => 'nullable|string|max:20',
|
||||||
|
'logo' => 'nullable|image|max:2048|mimes:jpg,jpeg,png,gif',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Manejar la carga del logo
|
||||||
|
if ($request->hasFile('logo')) {
|
||||||
|
$validated['logo'] = $request->file('logo')->store('companies/logos', 'public');
|
||||||
|
}
|
||||||
|
|
||||||
|
Company::create($validated);
|
||||||
|
|
||||||
|
return redirect()->route('companies.index')
|
||||||
|
->with('success', 'Empresa creada exitosamente.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Company $company)
|
||||||
|
{
|
||||||
|
$company->load(['contacts' => function($query) {
|
||||||
|
$query->withPivot('position');
|
||||||
|
}]);
|
||||||
|
|
||||||
|
$contacts = $company->contacts()->paginate(5);
|
||||||
|
$projects = $company->contacts()->paginate(5);//$company->projects()->paginate(5);
|
||||||
|
$availableUsers = User::whereDoesntHave('companies', function($query) use ($company) {
|
||||||
|
$query->where('company_id', $company->id);
|
||||||
|
})->get();
|
||||||
|
|
||||||
|
return view('companies.show', compact('company', 'contacts', 'projects', 'availableUsers'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(Company $company)
|
||||||
|
{
|
||||||
|
|
||||||
|
return view('companies.form', compact('company'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, Company $company)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'commercial_name' => 'required|string|max:255',
|
||||||
|
'status' => 'required|in:active,closed',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
'postal_code' => 'nullable|string|max:10',
|
||||||
|
'city' => 'nullable|string|max:100',
|
||||||
|
'country' => 'nullable|string|max:100',
|
||||||
|
'phone' => 'nullable|string|max:20',
|
||||||
|
'email' => 'nullable|email|max:255',
|
||||||
|
'cif' => 'nullable|string|max:20',
|
||||||
|
'logo' => 'nullable|image|max:2048|mimes:jpg,jpeg,png,gif',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Manejar la actualización del logo
|
||||||
|
if ($request->hasFile('logo')) {
|
||||||
|
// Eliminar el logo anterior si existe
|
||||||
|
if ($company->logo && Storage::disk('public')->exists($company->logo)) {
|
||||||
|
Storage::disk('public')->delete($company->logo);
|
||||||
|
}
|
||||||
|
$validated['logo'] = $request->file('logo')->store('companies/logos', 'public');
|
||||||
|
} elseif ($request->has('remove_logo')) {
|
||||||
|
// Eliminar el logo si se seleccionó la opción de eliminar
|
||||||
|
if ($company->logo && Storage::disk('public')->exists($company->logo)) {
|
||||||
|
Storage::disk('public')->delete($company->logo);
|
||||||
|
}
|
||||||
|
$validated['logo'] = null;
|
||||||
|
} else {
|
||||||
|
// Mantener el logo existente
|
||||||
|
$validated['logo'] = $company->logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
$company->update($validated);
|
||||||
|
|
||||||
|
return redirect()->route('companies.index')
|
||||||
|
->with('success', 'Empresa actualizada exitosamente.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(Company $company)
|
||||||
|
{
|
||||||
|
// Eliminar el logo si existe
|
||||||
|
if ($company->logo && Storage::disk('public')->exists($company->logo)) {
|
||||||
|
Storage::disk('public')->delete($company->logo);
|
||||||
|
}
|
||||||
|
|
||||||
|
$company->delete();
|
||||||
|
|
||||||
|
return redirect()->route('companies.index')
|
||||||
|
->with('success', 'Empresa eliminada exitosamente.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,10 +24,8 @@ class DashboardController extends Controller
|
|||||||
|
|
||||||
// Documentos recientes (últimos 7 días)
|
// Documentos recientes (últimos 7 días)
|
||||||
$recentDocuments = Document::with(['project', 'currentVersion'])
|
$recentDocuments = Document::with(['project', 'currentVersion'])
|
||||||
->where('created_at', '>=', now()->subDays(7))
|
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->limit(5)
|
->limit(5);
|
||||||
->get();
|
|
||||||
|
|
||||||
// Actividad reciente
|
// Actividad reciente
|
||||||
$recentActivities = DB::table('activity_log')
|
$recentActivities = DB::table('activity_log')
|
||||||
@@ -35,7 +33,9 @@ class DashboardController extends Controller
|
|||||||
->limit(10)
|
->limit(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('dashboard', compact('stats', 'recentDocuments', 'recentActivities'));
|
$showSidebar = true; // Variable para mostrar el sidebar
|
||||||
|
|
||||||
|
return view('dashboard', compact('stats', 'recentDocuments', 'recentActivities', 'showSidebar'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateStorageUsed()
|
private function calculateStorageUsed()
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ class DocumentController extends Controller
|
|||||||
|
|
||||||
$document->url = Storage::url($document->file_path);
|
$document->url = Storage::url($document->file_path);
|
||||||
|
|
||||||
|
$document->load('user');
|
||||||
|
|
||||||
return view('documents.show', [
|
return view('documents.show', [
|
||||||
'document' => $document,
|
'document' => $document,
|
||||||
'versions' => $document->versions()->latest()->get(),
|
'versions' => $document->versions()->latest()->get(),
|
||||||
@@ -108,7 +110,7 @@ class DocumentController extends Controller
|
|||||||
foreach ($request->file('files') as $file) {
|
foreach ($request->file('files') as $file) {
|
||||||
$document = $project->documents()->create([
|
$document = $project->documents()->create([
|
||||||
'name' => $file->getClientOriginalName(),
|
'name' => $file->getClientOriginalName(),
|
||||||
'status' => 'pending'
|
'status' => 0
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->createVersion($document, $file);
|
$this->createVersion($document, $file);
|
||||||
@@ -125,4 +127,331 @@ class DocumentController extends Controller
|
|||||||
|
|
||||||
$document->update(['current_version_id' => $version->id]);
|
$document->update(['current_version_id' => $version->id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actualizar PDF con anotaciones, firmas y sellos
|
||||||
|
*/
|
||||||
|
public function updatePdf(Request $request, Document $document)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $document);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'pdf_data' => 'required|string', // PDF modificado en base64
|
||||||
|
'annotations' => 'sometimes|array',
|
||||||
|
'signatures' => 'sometimes|array',
|
||||||
|
'stamps' => 'sometimes|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Procesar el PDF modificado
|
||||||
|
$modifiedPdf = $this->processPdfData($request->pdf_data);
|
||||||
|
|
||||||
|
// Reemplazar el archivo original (sin crear nueva versión si no quieres)
|
||||||
|
$this->replaceOriginalPdf($document, $modifiedPdf);
|
||||||
|
|
||||||
|
// Opcional: también crear una nueva versión para historial
|
||||||
|
$newVersion = $this->createNewVersion($document, $modifiedPdf, $request->all());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'PDF actualizado correctamente',
|
||||||
|
'version_id' => $newVersion->id ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Error updating PDF: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al actualizar el PDF: ' . $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesar datos del PDF en base64
|
||||||
|
*/
|
||||||
|
private function processPdfData($pdfData)
|
||||||
|
{
|
||||||
|
// Eliminar el prefijo data:application/pdf;base64, si existe
|
||||||
|
$pdfData = preg_replace('/^data:application\/pdf;base64,/', '', $pdfData);
|
||||||
|
|
||||||
|
// Decodificar base64
|
||||||
|
$pdfContent = base64_decode($pdfData);
|
||||||
|
|
||||||
|
if (!$pdfContent) {
|
||||||
|
throw new \Exception('Datos PDF inválidos');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pdfContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesar PDF con anotaciones (método alternativo)
|
||||||
|
*/
|
||||||
|
private function processPdfWithAnnotations($document, $data)
|
||||||
|
{
|
||||||
|
// Aquí integrarías una librería PHP para PDF como spatie/pdf-to-image o setasign/fpdi
|
||||||
|
// Por ahora, devolvemos el contenido del archivo original
|
||||||
|
// En producción, implementarías la lógica de modificación
|
||||||
|
|
||||||
|
if ($document->currentVersion) {
|
||||||
|
$filePath = $document->currentVersion->file_path;
|
||||||
|
} else {
|
||||||
|
$filePath = $document->getFirstMedia('documents')->getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Storage::exists($filePath)) {
|
||||||
|
throw new \Exception('Archivo PDF no encontrado');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::get($filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reemplazar el PDF original
|
||||||
|
*/
|
||||||
|
private function replaceOriginalPdf($document, $pdfContent)
|
||||||
|
{
|
||||||
|
// Obtener la ruta del archivo original
|
||||||
|
$filePath = $document->file_path;
|
||||||
|
|
||||||
|
// Si el documento usa media library
|
||||||
|
if ($document->getFirstMedia('documents')) {
|
||||||
|
$media = $document->getFirstMedia('documents');
|
||||||
|
$media->update([
|
||||||
|
'file_name' => $document->name . '.pdf',
|
||||||
|
'size' => strlen($pdfContent),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Reemplazar el archivo
|
||||||
|
Storage::put($media->getPath(), $pdfContent);
|
||||||
|
} else {
|
||||||
|
// Si usas file_path directo
|
||||||
|
Storage::put($filePath, $pdfContent);
|
||||||
|
|
||||||
|
// Actualizar metadata del documento
|
||||||
|
$document->update([
|
||||||
|
'file_size' => strlen($pdfContent),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crear nueva versión del documento
|
||||||
|
*/
|
||||||
|
private function createNewVersion($document, $pdfContent, $data = [])
|
||||||
|
{
|
||||||
|
$versionNumber = $document->versions()->count() + 1;
|
||||||
|
$fileName = "documents/{$document->id}/v{$versionNumber}.pdf";
|
||||||
|
|
||||||
|
// Guardar el nuevo PDF
|
||||||
|
Storage::put($fileName, $pdfContent);
|
||||||
|
|
||||||
|
// Crear registro de versión
|
||||||
|
$version = $document->versions()->create([
|
||||||
|
'version_number' => $versionNumber,
|
||||||
|
'file_path' => $fileName,
|
||||||
|
'file_size' => strlen($pdfContent),
|
||||||
|
'hash' => hash('sha256', $pdfContent),
|
||||||
|
'created_by' => auth()->id(),
|
||||||
|
'metadata' => [
|
||||||
|
'annotations_count' => count($data['annotations'] ?? []),
|
||||||
|
'signatures_count' => count($data['signatures'] ?? []),
|
||||||
|
'stamps_count' => count($data['stamps'] ?? []),
|
||||||
|
'edited_at' => now()->toISOString(),
|
||||||
|
'edited_by' => auth()->user()->name
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guardar metadatos de anotaciones
|
||||||
|
*/
|
||||||
|
private function saveAnnotationsMetadata($version, $data)
|
||||||
|
{
|
||||||
|
// Guardar anotaciones en la base de datos si es necesario
|
||||||
|
if (!empty($data['annotations'])) {
|
||||||
|
foreach ($data['annotations'] as $annotation) {
|
||||||
|
$version->annotations()->create([
|
||||||
|
'type' => $annotation['type'] ?? 'text',
|
||||||
|
'content' => $annotation['content'] ?? '',
|
||||||
|
'position' => $annotation['position'] ?? [],
|
||||||
|
'page' => $annotation['page'] ?? 1,
|
||||||
|
'created_by' => auth()->id()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subir firma
|
||||||
|
*/
|
||||||
|
public function uploadSignature(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'signature' => 'required|image|max:2048|mimes:png,jpg,jpeg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user = auth()->user();
|
||||||
|
$path = $request->file('signature')->store("signatures/{$user->id}", 'public');
|
||||||
|
|
||||||
|
// Opcional: Guardar en base de datos
|
||||||
|
$user->signatures()->create([
|
||||||
|
'file_path' => $path,
|
||||||
|
'file_name' => $request->file('signature')->getClientOriginalName()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'path' => Storage::url($path),
|
||||||
|
'filename' => basename($path)
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Error uploading signature: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al subir la firma'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subir sello
|
||||||
|
*/
|
||||||
|
public function uploadStamp(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'stamp' => 'required|image|max:2048|mimes:png,jpg,jpeg'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user = auth()->user();
|
||||||
|
$path = $request->file('stamp')->store("stamps/{$user->id}", 'public');
|
||||||
|
|
||||||
|
// Opcional: Guardar en base de datos
|
||||||
|
$user->stamps()->create([
|
||||||
|
'file_path' => $path,
|
||||||
|
'file_name' => $request->file('stamp')->getClientOriginalName(),
|
||||||
|
'type' => $request->type ?? 'custom'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'path' => Storage::url($path),
|
||||||
|
'filename' => basename($path)
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Error uploading stamp: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al subir el sello'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener firmas del usuario
|
||||||
|
*/
|
||||||
|
public function getSignatures()
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$signatures = $user->signatures()->get()->map(function($signature) {
|
||||||
|
return [
|
||||||
|
'id' => $signature->id,
|
||||||
|
'url' => Storage::url($signature->file_path),
|
||||||
|
'name' => $signature->file_name
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'signatures' => $signatures
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener sellos del usuario
|
||||||
|
*/
|
||||||
|
public function getStamps()
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$stamps = $user->stamps()->get()->map(function($stamp) {
|
||||||
|
return [
|
||||||
|
'id' => $stamp->id,
|
||||||
|
'url' => Storage::url($stamp->file_path),
|
||||||
|
'name' => $stamp->file_name,
|
||||||
|
'type' => $stamp->type
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'stamps' => $stamps
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descargar documento
|
||||||
|
*/
|
||||||
|
public function download(Document $document, $versionId = null)
|
||||||
|
{
|
||||||
|
$this->authorize('view', $document);
|
||||||
|
|
||||||
|
$version = $versionId ?
|
||||||
|
$document->versions()->findOrFail($versionId) :
|
||||||
|
$document->currentVersion;
|
||||||
|
|
||||||
|
if (!$version || !Storage::exists($version->file_path)) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::download($version->file_path, $document->name . '.pdf');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener el PDF actual para edición
|
||||||
|
*/
|
||||||
|
public function getPdfForEditing(Document $document)
|
||||||
|
{
|
||||||
|
$this->authorize('view', $document);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($document->getFirstMedia('documents')) {
|
||||||
|
$filePath = $document->getFirstMedia('documents')->getPath();
|
||||||
|
} else {
|
||||||
|
$filePath = $document->file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Storage::exists($filePath)) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdfContent = Storage::get($filePath);
|
||||||
|
$base64Pdf = base64_encode($pdfContent);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'pdf_data' => $base64Pdf,
|
||||||
|
'document_name' => $document->name
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('Error getting PDF for editing: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al cargar el PDF'
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use App\Models\Folder;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class ProjectController extends Controller
|
class ProjectController extends Controller
|
||||||
{
|
{
|
||||||
use AuthorizesRequests; // ← Añadir este trait
|
use AuthorizesRequests;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
@@ -18,8 +20,8 @@ class ProjectController extends Controller
|
|||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$projects = auth()->user()->hasRole('admin')
|
$projects = auth()->user()->hasRole('admin')
|
||||||
? Project::get() // Todos los proyectos para admin
|
? Project::get() // Todos los proyectos para admin
|
||||||
: auth()->user()->projects()->latest()->get(); // Solo proyectos asignados
|
: auth()->user()->projects()->latest()->get(); // Solo proyectos asignados
|
||||||
|
|
||||||
/*
|
/*
|
||||||
$projects = Project::whereHas('users', function($query) {
|
$projects = Project::whereHas('users', function($query) {
|
||||||
@@ -37,10 +39,12 @@ class ProjectController extends Controller
|
|||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->authorize('create projects');
|
$this->authorize('create projects');
|
||||||
|
$project = new Project();
|
||||||
return view('projects.create', [
|
return view('projects.create', [
|
||||||
|
'project' => $project,
|
||||||
'categories' => Category::orderBy('name')->get(),
|
'categories' => Category::orderBy('name')->get(),
|
||||||
'users' => User::where('id', '!=', auth()->id())->get(),
|
'users' => User::where('id', '!=', auth()->id())->get(),
|
||||||
|
'companies' => \App\Models\Company::all(), // Pass companies to the view,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,25 +54,21 @@ class ProjectController extends Controller
|
|||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
|
'reference' => 'required|string|max:12',
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'status' => 'required|in:Activo,Inactivo',
|
'status' => 'required|in:Activo,Inactivo',
|
||||||
//'team' => 'sometimes|array',
|
|
||||||
//'team.*' => 'exists:users,id',
|
|
||||||
'project_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
|
|
||||||
'address' => 'nullable|string|max:255',
|
'address' => 'nullable|string|max:255',
|
||||||
'province' => 'nullable|string|max:100',
|
'province' => 'nullable|string|max:100',
|
||||||
//'country' => 'nullable|string|size:2',
|
'country' => 'nullable|string|size:2',
|
||||||
'postal_code' => 'nullable|string|max:10',
|
'postal_code' => 'nullable|string|max:10',
|
||||||
'latitude' => 'required|numeric|between:-90,90',
|
'latitude' => 'required|numeric|between:-90,90',
|
||||||
'longitude' => 'required|numeric|between:-180,180',
|
'longitude' => 'required|numeric|between:-180,180',
|
||||||
//'icon' => 'nullable|in:'.implode(',', config('project.icons')),
|
|
||||||
'start_date' => 'nullable|date',
|
'start_date' => 'nullable|date',
|
||||||
'deadline' => 'nullable|date|after:start_date',
|
'deadline' => 'nullable|date|after:start_date',
|
||||||
'categories' => 'nullable|array|exists:categories,id',
|
'categories' => 'nullable|array|exists:categories,id',
|
||||||
//'categories' => 'required|array',
|
'project_image_path' => 'nullable|string',
|
||||||
//'categories.*' => 'exists:categories,id',
|
'company_id' => 'required|exists:companies,id', // Validate company_id
|
||||||
//'documents.*' => 'file|max:5120|mimes:pdf,docx,xlsx,jpg,png'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
@@ -79,11 +79,14 @@ class ProjectController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Manejar la imagen
|
// Manejar la imagen
|
||||||
if ($request->hasFile('project_image')) {
|
if ($request->has('project_image_path') && $request->project_image_path) {
|
||||||
$path = $request->file('project_image')->store('project-images', 'public');
|
$tempPath = $request->project_image_path;
|
||||||
$validated['project_image_path'] = $path; // Usar el nombre correcto de columna
|
$newPath = 'images/projects/' . basename($tempPath);
|
||||||
|
|
||||||
|
Storage::move($tempPath, $newPath); // Mover el archivo
|
||||||
|
$validated['project_image_path'] = $newPath; // Actualizar path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear el proyecto con todos los datos validados
|
// Crear el proyecto con todos los datos validados
|
||||||
$project = Project::create($validated);
|
$project = Project::create($validated);
|
||||||
|
|
||||||
@@ -91,17 +94,12 @@ class ProjectController extends Controller
|
|||||||
if($request->has('categories')) {
|
if($request->has('categories')) {
|
||||||
$project->categories()->sync($request->categories);
|
$project->categories()->sync($request->categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manejar documentos adjuntos
|
Folder::create([
|
||||||
/*
|
'name' => 'Project',
|
||||||
if($request->hasFile('documents')) {
|
'project_id' => $project->id,
|
||||||
foreach ($request->file('documents') as $file) {
|
'parent_id' => null,
|
||||||
$project->documents()->create([
|
]);
|
||||||
'file_path' => $file->store('project-documents', 'public'),
|
|
||||||
'original_name' => $file->getClientOriginalName()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return redirect()->route('projects.show', $project)->with('success', 'Proyecto creado exitosamente');
|
return redirect()->route('projects.show', $project)->with('success', 'Proyecto creado exitosamente');
|
||||||
|
|
||||||
@@ -110,6 +108,20 @@ class ProjectController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(Project $project)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
return view('projects.create', [
|
||||||
|
'project' => $project,
|
||||||
|
'categories' => Category::orderBy('name')->get(),
|
||||||
|
'users' => User::where('id', '!=', auth()->id())->get(),
|
||||||
|
'companies' => \App\Models\Company::all(), // Pass companies to the view
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*/
|
*/
|
||||||
@@ -125,22 +137,37 @@ class ProjectController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the form for editing the specified resource.
|
|
||||||
*/
|
|
||||||
public function edit(Project $project)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, Project $project)
|
public function update(Request $request, Project $project)
|
||||||
{
|
{
|
||||||
$this->authorize('update', $project);
|
$this->authorize('update', $project);
|
||||||
// Lógica de actualización
|
|
||||||
$project->update($request->all());
|
$validated = $request->validate([
|
||||||
|
'reference' => 'required|string|max:255',
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'status' => 'required|in:Activo,Inactivo',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
'province' => 'nullable|string|max:100',
|
||||||
|
'country' => 'nullable|string|size:2',
|
||||||
|
'postal_code' => 'nullable|string|max:10',
|
||||||
|
'latitude' => 'required|numeric|between:-90,90',
|
||||||
|
'longitude' => 'required|numeric|between:-180,180',
|
||||||
|
'start_date' => 'nullable|date',
|
||||||
|
'deadline' => 'nullable|date|after:start_date',
|
||||||
|
'categories' => 'nullable|array|exists:categories,id',
|
||||||
|
'project_image_path' => 'nullable|string',
|
||||||
|
'company_id' => 'required|exists:companies,id', // Validate company_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$project->update($validated);
|
||||||
|
|
||||||
|
if ($request->has('categories')) {
|
||||||
|
$project->categories()->sync($request->categories);
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('projects.show', $project)->with('success', 'Project updated successfully.');
|
return redirect()->route('projects.show', $project)->with('success', 'Project updated successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +179,9 @@ class ProjectController extends Controller
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
public function __invoke(Project $project)
|
public function __invoke(Project $project)
|
||||||
{
|
{
|
||||||
return view('projects.show', [
|
return view('projects.show', [
|
||||||
|
|||||||
223
app/Http/Controllers/ProjectSettingsController.php
Normal file
223
app/Http/Controllers/ProjectSettingsController.php
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\ProjectCodingConfig;
|
||||||
|
use App\Models\ProjectDocumentStatus;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ProjectSettingsController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Project $project)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
|
||||||
|
//$project->load(['codingConfig', 'documentStatuses']);
|
||||||
|
|
||||||
|
return view('project-settings.index', compact('project'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCoding(Request $request, Project $project)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'format' => 'required|string|max:255',
|
||||||
|
'year_format' => 'required|in:Y,y,Yy,yy,Y-m,Y/m,y-m,y/m',
|
||||||
|
'separator' => 'required|string|max:5',
|
||||||
|
'sequence_length' => 'required|integer|min:1|max:10',
|
||||||
|
'auto_generate' => 'boolean',
|
||||||
|
'elements' => 'nullable|array',
|
||||||
|
'reset_sequence' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$codingConfig = $project->codingConfig ?: new ProjectCodingConfig();
|
||||||
|
$codingConfig->project_id = $project->id;
|
||||||
|
|
||||||
|
$codingConfig->fill([
|
||||||
|
'format' => $validated['format'],
|
||||||
|
'year_format' => $validated['year_format'],
|
||||||
|
'separator' => $validated['separator'],
|
||||||
|
'sequence_length' => $validated['sequence_length'],
|
||||||
|
'auto_generate' => $validated['auto_generate'] ?? false,
|
||||||
|
'elements' => $validated['elements'] ?? [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->boolean('reset_sequence')) {
|
||||||
|
$codingConfig->next_sequence = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$codingConfig->save();
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('project-settings.index', $project)
|
||||||
|
->with('success', 'Configuración de codificación actualizada correctamente.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return redirect()->back()
|
||||||
|
->with('error', 'Error al actualizar la configuración: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeStatus(Request $request, Project $project)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100',
|
||||||
|
'color' => 'required|string|max:7',
|
||||||
|
'text_color' => 'nullable|string|max:7',
|
||||||
|
'description' => 'nullable|string|max:500',
|
||||||
|
'allow_upload' => 'boolean',
|
||||||
|
'allow_edit' => 'boolean',
|
||||||
|
'allow_delete' => 'boolean',
|
||||||
|
'requires_approval' => 'boolean',
|
||||||
|
'is_default' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
// Si se marca como default, quitar el default de otros estados
|
||||||
|
if ($validated['is_default'] ?? false) {
|
||||||
|
$project->documentStatuses()->update(['is_default' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = new ProjectDocumentStatus($validated);
|
||||||
|
$status->project_id = $project->id;
|
||||||
|
$status->slug = Str::slug($validated['name']);
|
||||||
|
|
||||||
|
// Verificar que el slug sea único
|
||||||
|
$counter = 1;
|
||||||
|
$originalSlug = $status->slug;
|
||||||
|
while ($project->documentStatuses()->where('slug', $status->slug)->exists()) {
|
||||||
|
$status->slug = $originalSlug . '-' . $counter;
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status->save();
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('project-settings.index', $project)
|
||||||
|
->with('success', 'Estado creado correctamente.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return redirect()->back()
|
||||||
|
->with('error', 'Error al crear el estado: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateStatus(Request $request, Project $project, ProjectDocumentStatus $status)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
|
||||||
|
if ($status->project_id !== $project->id) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100',
|
||||||
|
'color' => 'required|string|max:7',
|
||||||
|
'text_color' => 'nullable|string|max:7',
|
||||||
|
'description' => 'nullable|string|max:500',
|
||||||
|
'allow_upload' => 'boolean',
|
||||||
|
'allow_edit' => 'boolean',
|
||||||
|
'allow_delete' => 'boolean',
|
||||||
|
'requires_approval' => 'boolean',
|
||||||
|
'is_default' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
// Si se marca como default, quitar el default de otros estados
|
||||||
|
if ($validated['is_default'] ?? false) {
|
||||||
|
$project->documentStatuses()
|
||||||
|
->where('id', '!=', $status->id)
|
||||||
|
->update(['is_default' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$status->update($validated);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('project-settings.index', $project)
|
||||||
|
->with('success', 'Estado actualizado correctamente.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return redirect()->back()
|
||||||
|
->with('error', 'Error al actualizar el estado: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyStatus(Project $project, ProjectDocumentStatus $status)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
|
||||||
|
if ($status->project_id !== $project->id) {
|
||||||
|
abort(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar que no haya documentos con este estado
|
||||||
|
if ($status->documents()->exists()) {
|
||||||
|
return redirect()->back()
|
||||||
|
->with('error', 'No se puede eliminar el estado porque hay documentos asociados.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si es el estado por defecto, establecer otro como default
|
||||||
|
if ($status->is_default) {
|
||||||
|
$newDefault = $project->documentStatuses()
|
||||||
|
->where('id', '!=', $status->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($newDefault) {
|
||||||
|
$newDefault->update(['is_default' => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$status->delete();
|
||||||
|
|
||||||
|
return redirect()->route('project-settings.index', $project)
|
||||||
|
->with('success', 'Estado eliminado correctamente.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reorderStatuses(Request $request, Project $project)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $project);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'order' => 'required|array',
|
||||||
|
'order.*' => 'exists:project_document_statuses,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
foreach ($request->order as $index => $statusId) {
|
||||||
|
$status = ProjectDocumentStatus::find($statusId);
|
||||||
|
if ($status && $status->project_id === $project->id) {
|
||||||
|
$status->update(['order' => $index + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return response()->json(['error' => $e->getMessage()], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,18 +17,25 @@ use Spatie\Permission\Models\Role;
|
|||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
|
public $collapsedGroups = [];
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$this->authorize('viewAny', User::class);
|
$this->authorize('viewAny', User::class);
|
||||||
$users = User::paginate(10);
|
$users = User::paginate(10);
|
||||||
return view('users.index', compact('users'));
|
return view('users.index', ['users' => $users,
|
||||||
|
'showSidebar' => 'true',]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->authorize('create', User::class);
|
$this->authorize('create', User::class);
|
||||||
$roles = Role::all();
|
$roles = Role::all();
|
||||||
return view('users.create', compact('roles'));
|
$user = new User();
|
||||||
|
return view('users.create', ['user' => $user,
|
||||||
|
'roles' => $roles,
|
||||||
|
'showSidebar' => 'true',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -57,12 +64,10 @@ class UserController extends Controller
|
|||||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||||
'email' => 'required|email|unique:users',
|
'email' => 'required|email|unique:users',
|
||||||
'phone' => 'nullable|string|max:20',
|
'phone' => 'nullable|string|max:20',
|
||||||
<<<<<<< HEAD
|
|
||||||
'address' => 'nullable|string|max:255',
|
'address' => 'nullable|string|max:255',
|
||||||
|
'user_type' => 'required|integer|in:0,1,2',
|
||||||
|
'company_id' => 'nullable|exists:companies,id', // Si se usa una relación con empresas
|
||||||
'profile_photo_path' => 'nullable|string' // Ruta de la imagen subida por Livewire
|
'profile_photo_path' => 'nullable|string' // Ruta de la imagen subida por Livewire
|
||||||
=======
|
|
||||||
'address' => 'nullable|string|max:255'
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Creación del usuario
|
// Creación del usuario
|
||||||
@@ -77,20 +82,14 @@ class UserController extends Controller
|
|||||||
'address' => $validated['address'],
|
'address' => $validated['address'],
|
||||||
'access_start' => $validated['start_date'],
|
'access_start' => $validated['start_date'],
|
||||||
'access_end' => $validated['end_date'],
|
'access_end' => $validated['end_date'],
|
||||||
<<<<<<< HEAD
|
'is_active' => $validated['is_active'] ?? false, // Por defecto, inactivo
|
||||||
'is_active' => true,
|
'user_type' => $validated['user_type'] ?? 0, // 0: Usuario, 1: Administrador, 2: Super Admin
|
||||||
|
'company_id' => $validated['company_id'] ?? null, // Si se usa una relación con empresas
|
||||||
'profile_photo_path' => $validated['profile_photo_path'] ?? null
|
'profile_photo_path' => $validated['profile_photo_path'] ?? null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($request->hasFile('image_path')) {
|
if ($request->hasFile('image_path')) {
|
||||||
$path = $request->file('image_path')->store('public/photos');
|
$path = $request->file('image_path')->store('public/photos');
|
||||||
=======
|
|
||||||
'is_active' => true
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($request->hasFile('photo')) {
|
|
||||||
$path = $request->file('photo')->store('public/photos');
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
$user->profile_photo_path = basename($path);
|
$user->profile_photo_path = basename($path);
|
||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
@@ -142,17 +141,11 @@ class UserController extends Controller
|
|||||||
Rule::unique('users')->ignore($user->id)
|
Rule::unique('users')->ignore($user->id)
|
||||||
],
|
],
|
||||||
'phone' => 'nullable|string|max:20',
|
'phone' => 'nullable|string|max:20',
|
||||||
<<<<<<< HEAD
|
|
||||||
'address' => 'nullable|string|max:255',
|
'address' => 'nullable|string|max:255',
|
||||||
'profile_photo_path' => 'nullable|string', // Añadido para la ruta de la imagen
|
'profile_photo_path' => 'nullable|string', // Añadido para la ruta de la imagen
|
||||||
//'is_active' => 'nullable|boolean' // Añadido para el estado activo
|
//'is_active' => 'nullable|boolean' // Añadido para el estado activo
|
||||||
]);
|
]);
|
||||||
|
|
||||||
=======
|
|
||||||
'address' => 'nullable|string|max:255'
|
|
||||||
]);
|
|
||||||
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
// Preparar datos para actualización
|
// Preparar datos para actualización
|
||||||
$updateData = [
|
$updateData = [
|
||||||
'title' => $validated['title'],
|
'title' => $validated['title'],
|
||||||
@@ -164,21 +157,14 @@ class UserController extends Controller
|
|||||||
'address' => $validated['address'],
|
'address' => $validated['address'],
|
||||||
'access_start' => $validated['start_date'],
|
'access_start' => $validated['start_date'],
|
||||||
'access_end' => $validated['end_date'],
|
'access_end' => $validated['end_date'],
|
||||||
<<<<<<< HEAD
|
|
||||||
'is_active' => $validated['is_active'] ?? false,
|
'is_active' => $validated['is_active'] ?? false,
|
||||||
'profile_photo_path' => $validated['profile_photo_path'] ?? $user->profile_photo_path
|
'profile_photo_path' => $validated['profile_photo_path'] ?? $user->profile_photo_path
|
||||||
];
|
];
|
||||||
|
|
||||||
=======
|
|
||||||
'is_active' => $request->has('is_active') // Si usas un checkbox
|
|
||||||
];
|
|
||||||
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
// Actualizar contraseña solo si se proporciona
|
// Actualizar contraseña solo si se proporciona
|
||||||
if (!empty($validated['password'])) {
|
if (!empty($validated['password'])) {
|
||||||
$updateData['password'] = Hash::make($validated['password']);
|
$updateData['password'] = Hash::make($validated['password']);
|
||||||
}
|
}
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
// Eliminar imagen anterior si se está actualizando
|
// Eliminar imagen anterior si se está actualizando
|
||||||
if (isset($validated['profile_photo_path']) && $user->profile_photo_path) {
|
if (isset($validated['profile_photo_path']) && $user->profile_photo_path) {
|
||||||
@@ -196,32 +182,6 @@ class UserController extends Controller
|
|||||||
return redirect()->back()->withErrors($e->validator)->withInput();
|
return redirect()->back()->withErrors($e->validator)->withInput();
|
||||||
|
|
||||||
} catch (QueryException $e) {
|
} catch (QueryException $e) {
|
||||||
=======
|
|
||||||
|
|
||||||
if ($request->hasFile('photo')) {
|
|
||||||
// Eliminar foto anterior si existe
|
|
||||||
if ($user->prfile_photo_path) {
|
|
||||||
Storage::delete('public/photos/'.$user->profile_photo_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $request->file('photo')->store('public/photos');
|
|
||||||
$user->update(['profile_photo_path' => basename($path)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actualizar el usuario
|
|
||||||
$user->update($updateData);
|
|
||||||
|
|
||||||
// Redireccionar con mensaje de éxito
|
|
||||||
return redirect()->route('users.show', $user)
|
|
||||||
->with('success', 'Usuario actualizado exitosamente');
|
|
||||||
|
|
||||||
} catch (ValidationException $e) {
|
|
||||||
// Redireccionar con errores de validación
|
|
||||||
return redirect()->back()->withErrors($e->validator)->withInput();
|
|
||||||
|
|
||||||
} catch (QueryException $e) {
|
|
||||||
// Manejar errores de base de datos
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
$errorCode = $e->errorInfo[1];
|
$errorCode = $e->errorInfo[1];
|
||||||
$errorMessage = 'Error al actualizar el usuario: ';
|
$errorMessage = 'Error al actualizar el usuario: ';
|
||||||
|
|
||||||
@@ -230,20 +190,11 @@ class UserController extends Controller
|
|||||||
} else {
|
} else {
|
||||||
$errorMessage .= 'Error en la base de datos';
|
$errorMessage .= 'Error en la base de datos';
|
||||||
}
|
}
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
Log::error("Error actualizando usuario ID {$user->id}: " . $e->getMessage());
|
Log::error("Error actualizando usuario ID {$user->id}: " . $e->getMessage());
|
||||||
return redirect()->back()->with('error', $errorMessage)->withInput();
|
return redirect()->back()->with('error', $errorMessage)->withInput();
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
=======
|
|
||||||
|
|
||||||
Log::error("Error actualizando usuario ID {$user->id}: " . $e->getMessage());
|
|
||||||
return redirect()->back()->with('error', $errorMessage)->withInput();
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// Manejar otros errores
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
Log::error("Error general actualizando usuario ID {$user->id}: " . $e->getMessage());
|
Log::error("Error general actualizando usuario ID {$user->id}: " . $e->getMessage());
|
||||||
return redirect()->back()->with('error', 'Ocurrió un error inesperado al actualizar el usuario')->withInput();
|
return redirect()->back()->with('error', 'Ocurrió un error inesperado al actualizar el usuario')->withInput();
|
||||||
}
|
}
|
||||||
@@ -253,20 +204,15 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
$previousUser = User::where('id', '<', $user->id)->latest('id')->first();
|
$previousUser = User::where('id', '<', $user->id)->latest('id')->first();
|
||||||
$nextUser = User::where('id', '>', $user->id)->oldest('id')->first();
|
$nextUser = User::where('id', '>', $user->id)->oldest('id')->first();
|
||||||
<<<<<<< HEAD
|
|
||||||
$permissionGroups = $this->getPermissionGroups($user);
|
$permissionGroups = $this->getPermissionGroups($user);
|
||||||
=======
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
|
|
||||||
return view('users.show', [
|
return view('users.show', [
|
||||||
'user' => $user,
|
'user' => $user,
|
||||||
'previousUser' => $previousUser,
|
'previousUser' => $previousUser,
|
||||||
'nextUser' => $nextUser,
|
'nextUser' => $nextUser,
|
||||||
<<<<<<< HEAD
|
|
||||||
'permissionGroups' => $permissionGroups,
|
'permissionGroups' => $permissionGroups,
|
||||||
=======
|
'showSidebar' => true,
|
||||||
'permissionGroups' => Permission::all()->groupBy('group')
|
'collapsedGroups' => $this->collapsedGroups,
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,4 +286,14 @@ class UserController extends Controller
|
|||||||
|
|
||||||
return $descriptions[$permissionName] ?? str_replace('.', ' ', $permissionName);
|
return $descriptions[$permissionName] ?? str_replace('.', ' ', $permissionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleGroupCollapse($groupName)
|
||||||
|
{
|
||||||
|
if (in_array($groupName, $this->collapsedGroups)) {
|
||||||
|
$this->collapsedGroups = array_diff($this->collapsedGroups, [$groupName]);
|
||||||
|
} else {
|
||||||
|
$this->collapsedGroups[] = $groupName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
28
app/Http/Services/PdfProcessor.php
Normal file
28
app/Http/Services/PdfProcessor.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Spatie\PdfToText\Pdf;
|
||||||
|
use PDFLib;
|
||||||
|
|
||||||
|
class PdfProcessor
|
||||||
|
{
|
||||||
|
public function extractAnnotations($path)
|
||||||
|
{
|
||||||
|
$text = (new Pdf())->setPdf($path)->text();
|
||||||
|
// Analizar texto para detectar anotaciones
|
||||||
|
return $this->parseAnnotations($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function embedAnnotations($originalPdf, $annotations)
|
||||||
|
{
|
||||||
|
$pdf = new PDFLib();
|
||||||
|
$pdf->begin_document('', '');
|
||||||
|
|
||||||
|
foreach($annotations as $annotation) {
|
||||||
|
$this->addAnnotationToPdf($pdf, $annotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pdf->get_buffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
251
app/Livewire/CodeEdit.php
Normal file
251
app/Livewire/CodeEdit.php
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class CodeEdit extends Component
|
||||||
|
{
|
||||||
|
public $componentId; // Nuevo: ID del componente padre
|
||||||
|
public $name = '';
|
||||||
|
public $codeInput = '';
|
||||||
|
public $labelInput = '';
|
||||||
|
public $maxLength = 3;
|
||||||
|
public $documentTypes = [];
|
||||||
|
public $sortBy = 'code';
|
||||||
|
public $sortDirection = 'asc';
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'name' => 'required|string|min:2|max:50',
|
||||||
|
'codeInput' => 'required|string',
|
||||||
|
'labelInput' => 'required|string',
|
||||||
|
'maxLength' => 'required|integer|min:2|max:12',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount($componentId, $initialName = '', $initialMaxLength = 3, $initialDocumentTypes = [])
|
||||||
|
{
|
||||||
|
$this->componentId = $componentId;
|
||||||
|
$this->name = $initialName;
|
||||||
|
$this->maxLength = $initialMaxLength;
|
||||||
|
$this->documentTypes = $initialDocumentTypes;
|
||||||
|
|
||||||
|
// Guardar datos iniciales
|
||||||
|
$this->initialData = [
|
||||||
|
'name' => $initialName,
|
||||||
|
'maxLength' => $initialMaxLength,
|
||||||
|
'documentTypes' => $initialDocumentTypes
|
||||||
|
];
|
||||||
|
|
||||||
|
// Disparar eventos iniciales
|
||||||
|
$this->dispatch('nameUpdated',
|
||||||
|
componentId: $this->componentId,
|
||||||
|
data: [
|
||||||
|
'name' => $this->name,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->dispatch('componentUpdated',
|
||||||
|
componentId: $this->componentId,
|
||||||
|
data: [
|
||||||
|
'documentTypes' => $this->documentTypes,
|
||||||
|
'maxLength' => $this->maxLength
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateName()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'name' => 'required|string|min:2|max:50',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->dispatch('nameUpdated',
|
||||||
|
componentId: $this->componentId,
|
||||||
|
data: [
|
||||||
|
'name' => $this->name
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateMaxLength()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'maxLength' => 'integer|min:2|max:12',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (strlen($this->codeInput) > $this->maxLength) {
|
||||||
|
$this->codeInput = substr($this->codeInput, 0, $this->maxLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addField()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'codeInput' => "required|string|size:{$this->maxLength}",
|
||||||
|
'labelInput' => 'required|string|min:1',
|
||||||
|
], [
|
||||||
|
'codeInput.size' => "El código debe tener exactamente {$this->maxLength} caracteres",
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->documentTypes[] = [
|
||||||
|
'code' => $this->codeInput,
|
||||||
|
'label' => $this->labelInput,
|
||||||
|
//'max_length' => $this->maxLength,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->sortList();
|
||||||
|
$this->reset(['codeInput', 'labelInput']);
|
||||||
|
|
||||||
|
$this->dispatch('componentUpdated',
|
||||||
|
componentId: $this->componentId, // Cambiado aquí
|
||||||
|
data: [
|
||||||
|
'documentTypes' => $this->documentTypes,
|
||||||
|
'maxLength' => $this->maxLength
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCode()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'codeInput' => "required|string|size:{$this->maxLength}",
|
||||||
|
], [
|
||||||
|
'codeInput.size' => "El código debe tener exactamente {$this->maxLength} caracteres",
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->dispatch('focus-label-input');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addLabel()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'labelInput' => 'required|string|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($this->codeInput) && !empty($this->labelInput)) {
|
||||||
|
$this->addField();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeField($index)
|
||||||
|
{
|
||||||
|
if (isset($this->documentTypes[$index])) {
|
||||||
|
unset($this->documentTypes[$index]);
|
||||||
|
$this->documentTypes = array_values($this->documentTypes);
|
||||||
|
|
||||||
|
$this->dispatch('componentUpdated',
|
||||||
|
componentId: $this->componentId, // Cambiado aquí
|
||||||
|
data: [
|
||||||
|
'documentTypes' => $this->documentTypes,
|
||||||
|
'maxLength' => $this->maxLength
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedMaxLength($value)
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'maxLength' => 'integer|min:2|max:12',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (strlen($this->codeInput) > $value) {
|
||||||
|
$this->codeInput = substr($this->codeInput, 0, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedCodeInput($value)
|
||||||
|
{
|
||||||
|
if (strlen($value) > $this->maxLength) {
|
||||||
|
$this->codeInput = substr($value, 0, $this->maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->codeInput = strtoupper($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sortByCode()
|
||||||
|
{
|
||||||
|
if ($this->sortBy === 'code') {
|
||||||
|
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
$this->sortBy = 'code';
|
||||||
|
$this->sortDirection = 'asc';
|
||||||
|
}
|
||||||
|
$this->sortList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sortByLabel()
|
||||||
|
{
|
||||||
|
if ($this->sortBy === 'label') {
|
||||||
|
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
$this->sortBy = 'label';
|
||||||
|
$this->sortDirection = 'asc';
|
||||||
|
}
|
||||||
|
$this->sortList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sortList()
|
||||||
|
{
|
||||||
|
$direction = $this->sortDirection === 'asc' ? SORT_ASC : SORT_DESC;
|
||||||
|
|
||||||
|
if ($this->sortBy === 'code') {
|
||||||
|
array_multisort(
|
||||||
|
array_column($this->documentTypes, 'code'),
|
||||||
|
$direction,
|
||||||
|
SORT_STRING,
|
||||||
|
$this->documentTypes
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
array_multisort(
|
||||||
|
array_column($this->documentTypes, 'label'),
|
||||||
|
$direction,
|
||||||
|
SORT_STRING,
|
||||||
|
$this->documentTypes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSortedDocumentTypesProperty()
|
||||||
|
{
|
||||||
|
$sorted = $this->documentTypes;
|
||||||
|
$direction = $this->sortDirection === 'asc' ? SORT_ASC : SORT_DESC;
|
||||||
|
|
||||||
|
if ($this->sortBy === 'code') {
|
||||||
|
array_multisort(
|
||||||
|
array_column($sorted, 'code'),
|
||||||
|
$direction,
|
||||||
|
SORT_STRING,
|
||||||
|
$sorted
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
array_multisort(
|
||||||
|
array_column($sorted, 'label'),
|
||||||
|
$direction,
|
||||||
|
SORT_STRING,
|
||||||
|
$sorted
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalDocumentTypesProperty()
|
||||||
|
{
|
||||||
|
return count($this->documentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSortIcon($column)
|
||||||
|
{
|
||||||
|
if ($this->sortBy !== $column) {
|
||||||
|
return 'sort';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sortDirection === 'asc' ? 'sort-up' : 'sort-down';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.code-edit');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ class ImageUploader extends Component
|
|||||||
{
|
{
|
||||||
use WithFileUploads;
|
use WithFileUploads;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
public $image;
|
public $image;
|
||||||
public $imagePath;
|
public $imagePath;
|
||||||
public $fieldName; // Nombre del campo para el formulario
|
public $fieldName; // Nombre del campo para el formulario
|
||||||
@@ -47,41 +46,10 @@ class ImageUploader extends Component
|
|||||||
field: $this->fieldName,
|
field: $this->fieldName,
|
||||||
path: $this->imagePath
|
path: $this->imagePath
|
||||||
);
|
);
|
||||||
=======
|
|
||||||
public $photo;
|
|
||||||
public $currentImage;
|
|
||||||
public $fieldName;
|
|
||||||
public $placeholder;
|
|
||||||
public $storagePath = 'tmp/uploads';
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'photo' => 'nullable|image|max:2048', // 2MB Max
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount($fieldName = 'photo', $currentImage = null, $placeholder = null)
|
|
||||||
{
|
|
||||||
$this->fieldName = $fieldName;
|
|
||||||
$this->currentImage = $currentImage;
|
|
||||||
$this->placeholder = $placeholder ?? asset('images/default-avatar.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedPhoto()
|
|
||||||
{
|
|
||||||
$this->validate([
|
|
||||||
'photo' => 'image|max:2048', // 2MB Max
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removePhoto()
|
|
||||||
{
|
|
||||||
$this->photo = null;
|
|
||||||
$this->currentImage = null;
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
<<<<<<< HEAD
|
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'image' => 'required|image|max:2048',
|
'image' => 'required|image|max:2048',
|
||||||
]);
|
]);
|
||||||
@@ -111,38 +79,6 @@ class ImageUploader extends Component
|
|||||||
$this->hover = $status;
|
$this->hover = $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
=======
|
|
||||||
$this->validate();
|
|
||||||
|
|
||||||
if ($this->photo) {
|
|
||||||
$path = $this->photo->store($this->storagePath);
|
|
||||||
|
|
||||||
if ($this->model) {
|
|
||||||
// Eliminar imagen anterior si existe
|
|
||||||
if ($this->model->{$this->fieldName}) {
|
|
||||||
Storage::delete($this->model->{$this->fieldName});
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->model->{$this->fieldName} = $path;
|
|
||||||
$this->model->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->currentUrl = Storage::url($path);
|
|
||||||
$this->showSavedMessage = true;
|
|
||||||
$this->photo = null; // Limpiar el input de subida
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getCurrentImageUrl()
|
|
||||||
{
|
|
||||||
if ($this->model && $this->model->{$this->fieldName}) {
|
|
||||||
return Storage::url($this->model->{$this->fieldName});
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->placeholder;
|
|
||||||
}
|
|
||||||
|
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.image-uploader');
|
return view('livewire.image-uploader');
|
||||||
|
|||||||
37
app/Livewire/PdfViewer.php
Normal file
37
app/Livewire/PdfViewer.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class PdfViewer extends Component
|
||||||
|
{
|
||||||
|
public $pdfUrl;
|
||||||
|
public $currentPage = 1;
|
||||||
|
public $totalPages = 1;
|
||||||
|
public $zoomLevel = 1;
|
||||||
|
|
||||||
|
protected $listeners = ['pageChanged', 'annotationSaved'];
|
||||||
|
|
||||||
|
public function mount($pdfId)
|
||||||
|
{
|
||||||
|
$this->pdfUrl = Storage::disk('pdfs')->temporaryUrl($pdfId, now()->addMinutes(30));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pageChanged($page)
|
||||||
|
{
|
||||||
|
$this->currentPage = $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function annotationSaved($data)
|
||||||
|
{
|
||||||
|
// Procesar y guardar anotaciones
|
||||||
|
$this->emit('refreshAnnotations');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.pdf-viewer');
|
||||||
|
}
|
||||||
|
}
|
||||||
273
app/Livewire/ProjectDocumentList.php
Normal file
273
app/Livewire/ProjectDocumentList.php
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\Document;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
use App\Exports\DocumentsExport;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Rappasoft\LaravelLivewireTables\DataTableComponent;
|
||||||
|
use Rappasoft\LaravelLivewireTables\Views\Column;
|
||||||
|
use Rappasoft\LaravelLivewireTables\Views\Filters\SelectFilter;
|
||||||
|
use Rappasoft\LaravelLivewireTables\Views\Filters\TextFilter;
|
||||||
|
|
||||||
|
class ProjectDocumentList extends DataTableComponent
|
||||||
|
{
|
||||||
|
public $projectId;
|
||||||
|
public $folderId = null;
|
||||||
|
|
||||||
|
protected $model = Document::class;
|
||||||
|
|
||||||
|
public function configure(): void
|
||||||
|
{
|
||||||
|
$this->setPrimaryKey('id')
|
||||||
|
->setAdditionalSelects(['documents.id as id'])
|
||||||
|
|
||||||
|
/*->setConfigurableAreas([
|
||||||
|
'toolbar-left-start' => ['includes.areas.toolbar-left-start', ['param1' => 'Default', 'param2' => ['param2' => 2]]],
|
||||||
|
])*/
|
||||||
|
->setPaginationEnabled()
|
||||||
|
->setPaginationMethod('simple')
|
||||||
|
->setPaginationVisibilityEnabled()
|
||||||
|
|
||||||
|
|
||||||
|
//->setReorderEnabled()
|
||||||
|
->setHideReorderColumnUnlessReorderingEnabled()
|
||||||
|
->setSecondaryHeaderTrAttributes(function ($rows) {
|
||||||
|
return ['class' => 'bg-gray-100'];
|
||||||
|
})
|
||||||
|
->setSecondaryHeaderTdAttributes(function (Column $column, $rows) {
|
||||||
|
if ($column->isField('id')) {
|
||||||
|
return ['class' => 'text-red-100'];
|
||||||
|
}
|
||||||
|
return ['default' => true];
|
||||||
|
})
|
||||||
|
->setFooterTrAttributes(function ($rows) {
|
||||||
|
return ['class' => 'bg-gray-100'];
|
||||||
|
})
|
||||||
|
->setFooterTdAttributes(function (Column $column, $rows) {
|
||||||
|
if ($column->isField('name')) {
|
||||||
|
return ['class' => 'text-green-500'];
|
||||||
|
}
|
||||||
|
return ['default' => true];
|
||||||
|
})
|
||||||
|
->setHideBulkActionsWhenEmptyEnabled()
|
||||||
|
->setUseHeaderAsFooterEnabled()
|
||||||
|
|
||||||
|
->setPaginationEnabled()
|
||||||
|
->setPaginationVisibilityEnabled()
|
||||||
|
//->setToolsDisabled()
|
||||||
|
//->setToolBarDisabled()
|
||||||
|
|
||||||
|
// Configuración de paginación
|
||||||
|
->setPerPage(25) // Número de elementos por página
|
||||||
|
->setPerPageAccepted([10, 25, 50, 100]) // Opciones de elementos por página
|
||||||
|
->setPaginationEnabled() // Asegurar que la paginación esté habilitada
|
||||||
|
->setPaginationVisibilityStatus(true); // Hacer visible el paginador
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount($projectId = null, $folderId = null)
|
||||||
|
{
|
||||||
|
$this->projectId = $projectId;
|
||||||
|
$this->folderId = $folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function columns(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Column::make("Código", "code")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->secondaryHeaderFilter('code') // Filtro para esta columna
|
||||||
|
->format(
|
||||||
|
fn($value, $row, Column $column) =>
|
||||||
|
'<a href="'.route('documents.show', $row->id).'" target="_blank" class=" target="_blank" class="flex items-center hover:text-blue-600 transition-colors"">'.$value.'</a>'
|
||||||
|
)->html(),
|
||||||
|
|
||||||
|
Column::make("Nombre", "name")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->secondaryHeaderFilter('name') // Filtro para esta columna
|
||||||
|
->format(
|
||||||
|
fn($value, $row, Column $column) =>
|
||||||
|
'<div class="flex items-center">
|
||||||
|
<span class="mr-2 text-lg">
|
||||||
|
'.\App\Helpers\FileHelper::getFileIconSvg($value).'
|
||||||
|
</span>
|
||||||
|
<a href="'.route('documents.show', $row->id).'"
|
||||||
|
target="_blank"
|
||||||
|
class=" target="_blank" class="flex items-center hover:text-blue-600 transition-colors"">
|
||||||
|
'.$value.'
|
||||||
|
</a>
|
||||||
|
</div>'
|
||||||
|
)->html(),
|
||||||
|
|
||||||
|
Column::make("Estado", "status")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->secondaryHeaderFilter('status') // Filtro para esta columna
|
||||||
|
->format(
|
||||||
|
fn($value, $row, Column $column) =>
|
||||||
|
'<span class="px-2 py-1 text-xs font-semibold rounded-full '.
|
||||||
|
($value === 'active' ? 'bg-green-100 text-green-800' :
|
||||||
|
($value === 'pending' ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800')).'">'.
|
||||||
|
$value.'</span>'
|
||||||
|
)->html(),
|
||||||
|
|
||||||
|
Column::make("Revisión", "revision")
|
||||||
|
->sortable()
|
||||||
|
->searchable(),
|
||||||
|
|
||||||
|
Column::make("Versión", "version")
|
||||||
|
->sortable()
|
||||||
|
->searchable(),
|
||||||
|
|
||||||
|
Column::make("Área", "area")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->secondaryHeaderFilter('area'), // Filtro para esta columna
|
||||||
|
|
||||||
|
Column::make("Disciplina", "discipline")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->secondaryHeaderFilter('discipline'), // Filtro para esta columna
|
||||||
|
|
||||||
|
Column::make("Tipo", "document_type")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->secondaryHeaderFilter('type'), // Filtro para esta columna
|
||||||
|
|
||||||
|
Column::make("Fecha Entrada", "entry_date")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->format(
|
||||||
|
fn($value, $row, Column $column) =>
|
||||||
|
$value ? \Carbon\Carbon::parse($value)->format('d/m/Y') : '-'
|
||||||
|
),
|
||||||
|
|
||||||
|
Column::make("Actualizado", "updated_at")
|
||||||
|
->sortable()
|
||||||
|
->searchable()
|
||||||
|
->format(
|
||||||
|
fn($value, $row, Column $column) =>
|
||||||
|
$value ? \Carbon\Carbon::parse($value)->diffForHumans() : '-'
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function filters(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
TextFilter::make('Código', 'code') // Agregar clave 'code'
|
||||||
|
->config([
|
||||||
|
'placeholder' => 'Buscar por código',
|
||||||
|
])
|
||||||
|
->filter(function (Builder $builder, string $value) {
|
||||||
|
$builder->where('documents.code', 'like', '%'.$value.'%');
|
||||||
|
}),
|
||||||
|
|
||||||
|
TextFilter::make('Nombre', 'name') // Agregar clave 'name'
|
||||||
|
->config([
|
||||||
|
'placeholder' => 'Buscar por nombre',
|
||||||
|
])
|
||||||
|
->filter(function (Builder $builder, string $value) {
|
||||||
|
$builder->where('documents.name', 'like', '%'.$value.'%');
|
||||||
|
}),
|
||||||
|
|
||||||
|
SelectFilter::make('Estado', 'status') // Agregar clave 'status'
|
||||||
|
->options([
|
||||||
|
'' => 'Todos',
|
||||||
|
'active' => 'Activo',
|
||||||
|
'pending' => 'Pendiente',
|
||||||
|
'inactive' => 'Inactivo',
|
||||||
|
])
|
||||||
|
->filter(function (Builder $builder, string $value) {
|
||||||
|
if ($value) {
|
||||||
|
$builder->where('documents.status', $value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
SelectFilter::make('Disciplina', 'discipline') // Agregar clave 'discipline'
|
||||||
|
->options(
|
||||||
|
collect(['Estructural', 'Arquitectura', 'Eléctrica', 'Mecánica', 'Civil', 'Otros'])
|
||||||
|
->prepend('Todas', '')
|
||||||
|
->toArray()
|
||||||
|
)
|
||||||
|
->filter(function (Builder $builder, string $value) {
|
||||||
|
if ($value) {
|
||||||
|
$builder->where('documents.discipline', $value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
SelectFilter::make('Area', 'area') // Agregar clave 'area'
|
||||||
|
->options(
|
||||||
|
collect(['Estructural', 'Arquitectura', 'Eléctrica', 'Mecánica', 'Civil', 'Otros'])
|
||||||
|
->prepend('Todas', '')
|
||||||
|
->toArray()
|
||||||
|
)
|
||||||
|
->filter(function (Builder $builder, string $value) {
|
||||||
|
if ($value) {
|
||||||
|
$builder->where('documents.area', $value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
SelectFilter::make('Tipo', 'type') // Agregar clave 'document_type'
|
||||||
|
->options(
|
||||||
|
collect(['Estructural', 'Arquitectura', 'Eléctrica', 'Mecánica', 'Civil', 'Otros'])
|
||||||
|
->prepend('Todas', '')
|
||||||
|
->toArray()
|
||||||
|
)
|
||||||
|
->filter(function (Builder $builder, string $value) {
|
||||||
|
if ($value) {
|
||||||
|
$builder->where('documents.document_type', $value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function builder(): Builder
|
||||||
|
{
|
||||||
|
$query = Document::query()->where('project_id', $this->projectId);
|
||||||
|
|
||||||
|
if ($this->folderId) {
|
||||||
|
$query->where('folder_id', $this->folderId);
|
||||||
|
} else {
|
||||||
|
$query->whereNull('folder_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->with('user');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bulkActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'activate' => 'Activar',
|
||||||
|
'deactivate' => 'Desactivar',
|
||||||
|
'export' => 'Exportar',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function export()
|
||||||
|
{
|
||||||
|
$documents = $this->getSelected();
|
||||||
|
|
||||||
|
$this->clearSelected();
|
||||||
|
|
||||||
|
return Excel::download(new DocumentsExport($documents), 'documentos.xlsx');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activate()
|
||||||
|
{
|
||||||
|
Document::whereIn('id', $this->getSelected())->update(['status' => 'active']);
|
||||||
|
$this->clearSelected();
|
||||||
|
$this->dispatch('documents-updated');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deactivate()
|
||||||
|
{
|
||||||
|
Document::whereIn('id', $this->getSelected())->update(['status' => 'inactive']);
|
||||||
|
$this->clearSelected();
|
||||||
|
$this->dispatch('documents-updated');
|
||||||
|
}
|
||||||
|
}
|
||||||
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
274
app/Livewire/ProjectNameCoder.php
Normal file
274
app/Livewire/ProjectNameCoder.php
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\ProjectCodingConfig;
|
||||||
|
|
||||||
|
class ProjectNameCoder extends Component
|
||||||
|
{
|
||||||
|
public $components = [];
|
||||||
|
public $nextId = 1;
|
||||||
|
public $project;
|
||||||
|
|
||||||
|
protected $listeners = [
|
||||||
|
'nameUpdated' => 'headerLabelUpdate',
|
||||||
|
'componentUpdated' => 'handleComponentUpdate',
|
||||||
|
'removeComponent' => 'removeComponent'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount(Project $project)
|
||||||
|
{
|
||||||
|
// Inicializar con un componente vacío
|
||||||
|
$this->project = $project;
|
||||||
|
|
||||||
|
if ($project->codingConfig) {
|
||||||
|
$this->loadDatabaseConfiguration();
|
||||||
|
} else {
|
||||||
|
$this->addComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadDatabaseConfiguration()
|
||||||
|
{
|
||||||
|
// Buscar la configuración de codificación del proyecto
|
||||||
|
$config = $this->project->codingConfig;
|
||||||
|
|
||||||
|
if ($config && isset($config->elements['components'])) {
|
||||||
|
$this->components = $config->elements['components'];
|
||||||
|
$this->nextId = count($this->components) + 1;
|
||||||
|
|
||||||
|
// Asegurar que cada componente tenga los campos necesarios
|
||||||
|
foreach ($this->components as &$component) {
|
||||||
|
$component['data'] = $component['data'] ?? [];
|
||||||
|
$component['order'] = $component['order'] ?? 0;
|
||||||
|
$component['headerLabel'] = $component['headerLabel'] ?? '';
|
||||||
|
$component['documentTypes'] = $component['documentTypes'] ?? [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Si no hay configuración, inicializar con un componente vacío
|
||||||
|
$this->addComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addComponent()
|
||||||
|
{
|
||||||
|
$id = $this->nextId++;
|
||||||
|
$this->components[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'data' => [],
|
||||||
|
'order' => count($this->components),
|
||||||
|
'headerLabel' => ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeComponent($componentId)
|
||||||
|
{
|
||||||
|
$this->components = array_filter($this->components, function($component) use ($componentId) {
|
||||||
|
return $component['id'] != $componentId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reindexar el orden
|
||||||
|
$this->reorderComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headerLabelUpdate($componentId, $data)
|
||||||
|
{
|
||||||
|
foreach ($this->components as &$component) {
|
||||||
|
if ($component['id'] == $componentId) {
|
||||||
|
$component['headerLabel'] = $data['name'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleComponentUpdate($componentId, $data)
|
||||||
|
{
|
||||||
|
foreach ($this->components as &$component) {
|
||||||
|
if ($component['id'] == $componentId) {
|
||||||
|
$component['data'] = $data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateComponentOrder($orderedIds)
|
||||||
|
{
|
||||||
|
foreach ($orderedIds as $index => $id) {
|
||||||
|
foreach ($this->components as &$component) {
|
||||||
|
if ($component['id'] == $id) {
|
||||||
|
$component['order'] = $index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ordenar el array por el campo 'order'
|
||||||
|
usort($this->components, function($a, $b) {
|
||||||
|
return $a['order'] - $b['order'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private function reorderComponents()
|
||||||
|
{
|
||||||
|
foreach ($this->components as $index => &$component) {
|
||||||
|
$component['order'] = $index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveComponentUp($componentId)
|
||||||
|
{
|
||||||
|
$index = $this->findComponentIndex($componentId);
|
||||||
|
|
||||||
|
if ($index > 0) {
|
||||||
|
// Intercambiar con el componente anterior
|
||||||
|
$temp = $this->components[$index];
|
||||||
|
$this->components[$index] = $this->components[$index - 1];
|
||||||
|
$this->components[$index - 1] = $temp;
|
||||||
|
|
||||||
|
// Actualizar órdenes
|
||||||
|
$this->reorderComponents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function moveComponentDown($componentId)
|
||||||
|
{
|
||||||
|
$index = $this->findComponentIndex($componentId);
|
||||||
|
|
||||||
|
if ($index < count($this->components) - 1) {
|
||||||
|
// Intercambiar con el componente siguiente
|
||||||
|
$temp = $this->components[$index];
|
||||||
|
$this->components[$index] = $this->components[$index + 1];
|
||||||
|
$this->components[$index + 1] = $temp;
|
||||||
|
|
||||||
|
// Actualizar órdenes
|
||||||
|
$this->reorderComponents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findComponentIndex($componentId)
|
||||||
|
{
|
||||||
|
foreach ($this->components as $index => $component) {
|
||||||
|
if ($component['id'] == $componentId) {
|
||||||
|
return $index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComponentsCountProperty()
|
||||||
|
{
|
||||||
|
return count($this->components);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalDocumentTypesProperty()
|
||||||
|
{
|
||||||
|
$total = 0;
|
||||||
|
foreach ($this->components as $component) {
|
||||||
|
if (isset($component['data']['documentTypes'])) {
|
||||||
|
$total += count($component['data']['documentTypes']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveConfiguration()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Preparar la configuración completa
|
||||||
|
$configData = [
|
||||||
|
'components' => $this->components,
|
||||||
|
'total_components' => $this->componentsCount,
|
||||||
|
//'total_document_types' => $this->totalDocumentTypes,
|
||||||
|
//'generated_format' => $this->generateFormatString(),
|
||||||
|
//'last_updated' => now()->toDateTimeString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Buscar o crear la configuración de codificación
|
||||||
|
$codingConfig = ProjectCodingConfig::firstOrNew(['project_id' => $this->project->id]);
|
||||||
|
|
||||||
|
// Actualizar los campos
|
||||||
|
$codingConfig->fill([
|
||||||
|
'elements' => $configData,
|
||||||
|
'format' => $this->generateFormatString(),
|
||||||
|
'auto_generate' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$codingConfig->save();
|
||||||
|
|
||||||
|
// Emitir evento de éxito
|
||||||
|
$this->dispatch('configurationSaved', [
|
||||||
|
'message' => 'Configuración guardada exitosamente',
|
||||||
|
'format' => $this->generateFormatString()
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->dispatch('configurationError', [
|
||||||
|
'message' => 'Error al guardar la configuración: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function autoSave()
|
||||||
|
{
|
||||||
|
// Auto-guardar cada 30 segundos de inactividad o cuando haya cambios importantes
|
||||||
|
// Esto es opcional pero mejora la experiencia de usuario
|
||||||
|
$this->dispatch('configurationAutoSaved');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateFormatString()
|
||||||
|
{
|
||||||
|
$formatParts = [];
|
||||||
|
|
||||||
|
// Agregar el código del proyecto
|
||||||
|
$formatParts[] = $this->project->code ?? $this->project->reference ?? 'PROJ';
|
||||||
|
|
||||||
|
// Agregar cada componente
|
||||||
|
foreach ($this->components as $component) {
|
||||||
|
if (!empty($component['headerLabel'])) {
|
||||||
|
$formatParts[] = '[' . strtoupper($component['headerLabel']) . ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar el nombre del documento
|
||||||
|
$formatParts[] = '[DOCUMENT_NAME]';
|
||||||
|
|
||||||
|
return implode('-', $formatParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExampleCodeAttribute()
|
||||||
|
{
|
||||||
|
$exampleParts = [];
|
||||||
|
|
||||||
|
// Agregar el código del proyecto
|
||||||
|
$exampleParts[] = $this->project->code ?? $this->project->reference ?? 'PROJ';
|
||||||
|
|
||||||
|
// Agregar cada componente con un valor de ejemplo
|
||||||
|
foreach ($this->components as $component) {
|
||||||
|
if (!empty($component['headerLabel'])) {
|
||||||
|
$exampleParts[] = $this->getExampleForComponent($component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar nombre de documento de ejemplo
|
||||||
|
$exampleParts[] = 'Documento-Ejemplo';
|
||||||
|
|
||||||
|
return implode('-', $exampleParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getExampleForComponent($component)
|
||||||
|
{
|
||||||
|
if (isset($component['data']['documentTypes']) && count($component['data']['documentTypes']) > 0) {
|
||||||
|
// Tomar el primer tipo de documento como ejemplo
|
||||||
|
$firstType = $component['data']['documentTypes'][0];
|
||||||
|
return $firstType['code'] ?? $firstType['name'] ?? 'TIPO';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'VALOR';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project-name-coder');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ use App\Models\Document;
|
|||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
use App\Services\ProjectCodeService;
|
||||||
|
|
||||||
class ProjectShow extends Component
|
class ProjectShow extends Component
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -31,10 +33,14 @@ class ProjectShow extends Component
|
|||||||
public $selectedFiles = []; // Archivos temporales en el modal
|
public $selectedFiles = []; // Archivos temporales en el modal
|
||||||
public $uploadProgress = [];
|
public $uploadProgress = [];
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,28 +86,6 @@ class ProjectShow extends Component
|
|||||||
$this->project->refresh();
|
$this->project->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*public function uploadFiles(): void
|
|
||||||
{
|
|
||||||
$this->validate([
|
|
||||||
'files.*' => 'file|max:10240|mimes:pdf,docx,xlsx,jpg,png'
|
|
||||||
]);
|
|
||||||
dd($this->files);
|
|
||||||
foreach ($this->files as $file) {
|
|
||||||
Document::create([
|
|
||||||
'name' => $file->getClientOriginalName(),
|
|
||||||
'file_path' => $file->store("projects/{$this->project->id}/documents"),
|
|
||||||
'project_id' => $this->project->id,
|
|
||||||
'folder_id' => $this->currentFolder?->id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->reset('files');
|
|
||||||
if ($this->currentFolder) {
|
|
||||||
$this->currentFolder->refresh(); // Recargar documentos
|
|
||||||
}
|
|
||||||
$this->reset('files');
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public function getDocumentsProperty()
|
public function getDocumentsProperty()
|
||||||
{
|
{
|
||||||
return $this->currentFolder
|
return $this->currentFolder
|
||||||
@@ -147,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);
|
||||||
}
|
}
|
||||||
@@ -158,20 +149,47 @@ class ProjectShow extends Component
|
|||||||
$this->selectedFiles = array_values($this->selectedFiles); // Reindexar array
|
$this->selectedFiles = array_values($this->selectedFiles); // Reindexar array
|
||||||
}
|
}
|
||||||
|
|
||||||
// Método para confirmar y guardar
|
|
||||||
public function uploadFiles(): void
|
public function uploadFiles(): void
|
||||||
{
|
{
|
||||||
|
$validator = new ProjectCodeValidator($project->codingConfig);
|
||||||
foreach ($this->selectedFiles as $file) {
|
foreach ($this->selectedFiles as $file) {
|
||||||
Document::create([
|
//print_r($analizador);
|
||||||
'name' => $file->getClientOriginalName(),
|
//$resultado1 = $analizador->analizarDocumento($file->getClientOriginalName());
|
||||||
'file_path' => $file->store("projects/{$this->project->id}/documents"),
|
if ($validator->validate($file->getClientOriginalName())) {
|
||||||
'project_id' => $this->project->id, // Asegurar que se envía
|
echo "✅ Código válido\n";
|
||||||
'folder_id' => $this->currentFolder?->id,
|
} else {
|
||||||
//'user_id' => Auth::id(),
|
echo "❌ Código inválido\n";
|
||||||
//'status' => 'active' // Añadir si tu modelo lo requiere
|
return;
|
||||||
]);
|
}
|
||||||
|
|
||||||
|
$code = $this->project->reference;
|
||||||
|
|
||||||
|
// Buscar si ya existe un documento con el mismo nombre en el mismo proyecto y carpeta
|
||||||
|
$existingDocument = Document::where('project_id', $this->project->id)
|
||||||
|
->where('folder_id', $this->currentFolder?->id)
|
||||||
|
->where('name', $file->getClientOriginalName())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingDocument) {
|
||||||
|
// Si existe, crear una nueva versión/revisión
|
||||||
|
$existingDocument->createVersion($file);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Si no existe, crear el documento con revisión 0
|
||||||
|
$document = Document::create([
|
||||||
|
'name' => $file->getClientOriginalName(),
|
||||||
|
'file_path' => $file->store("projects/{$this->project->id}/documents"),
|
||||||
|
'project_id' => $this->project->id,
|
||||||
|
'folder_id' => $this->currentFolder?->id,
|
||||||
|
'issuer_id' => Auth::id(),
|
||||||
|
'code' => $code,
|
||||||
|
'entry_date' => now(),
|
||||||
|
'revision' => 0, // Revisión inicial
|
||||||
|
]);
|
||||||
|
$document->createVersion($file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->resetUpload();
|
$this->resetUpload();
|
||||||
$this->project->refresh();
|
$this->project->refresh();
|
||||||
}
|
}
|
||||||
@@ -226,7 +244,8 @@ class ProjectShow extends Component
|
|||||||
'file_path' => $path,
|
'file_path' => $path,
|
||||||
'project_id' => $this->project->id,
|
'project_id' => $this->project->id,
|
||||||
'folder_id' => $this->currentFolder?->id,
|
'folder_id' => $this->currentFolder?->id,
|
||||||
'user_id' => Auth::id()
|
'user_id' => Auth::id(),
|
||||||
|
'code' => $code,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -240,16 +259,16 @@ class ProjectShow extends Component
|
|||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
<<<<<<< HEAD
|
return view('livewire.project.show');
|
||||||
return view('livewire.project.show')->layout('components.layouts.documents', [
|
}
|
||||||
'title' => __('Project: :name', ['name' => $this->project->name]),
|
|
||||||
'breadcrumbs' => [
|
public function generateProjectCode(array $fields): string
|
||||||
['name' => __('Projects'), 'url' => route('projects.index')],
|
{
|
||||||
['name' => $this->project->name, 'url' => route('projects.show', $this->project)],
|
return \App\Helpers\ProjectNamingSchema::generate($fields);
|
||||||
],
|
}
|
||||||
]);
|
|
||||||
=======
|
public function sellectAllDocuments()
|
||||||
return view('livewire.project-show');
|
{
|
||||||
>>>>>>> f97a7a84985ea300216ba3ea2ab4c31306e1659a
|
$this->selectedFiles = $this->documents->pluck('id')->toArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
app/Models/Company.php
Normal file
65
app/Models/Company.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
|
class Company extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'commercial_name',
|
||||||
|
'status',
|
||||||
|
'address',
|
||||||
|
'postal_code',
|
||||||
|
'city',
|
||||||
|
'country',
|
||||||
|
'phone',
|
||||||
|
'email',
|
||||||
|
'cif',
|
||||||
|
'logo'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime:d/m/Y H:i',
|
||||||
|
'updated_at' => 'datetime:d/m/Y H:i',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getStatusColorAttribute()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'active' => 'bg-green-100 text-green-800',
|
||||||
|
'closed' => 'bg-red-100 text-red-800',
|
||||||
|
][$this->status] ?? 'bg-gray-100 text-gray-800';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusTextAttribute()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'active' => 'Activo',
|
||||||
|
'closed' => 'Cerrado',
|
||||||
|
][$this->status] ?? 'Desconocido';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contacts(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class, 'company_contacts')
|
||||||
|
->withPivot('position')
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function users()
|
||||||
|
{
|
||||||
|
return $this->hasMany(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function projects()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Project::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ use App\Events\DocumentVersionUpdated;
|
|||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Spatie\Activitylog\LogOptions;
|
use Spatie\Activitylog\LogOptions;
|
||||||
use Spatie\Activitylog\Traits\LogsActivity;
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
class Document extends Model
|
class Document extends Model
|
||||||
{
|
{
|
||||||
@@ -20,14 +22,63 @@ class Document extends Model
|
|||||||
'file_path',
|
'file_path',
|
||||||
'project_id', // Asegurar que está en fillable
|
'project_id', // Asegurar que está en fillable
|
||||||
'folder_id',
|
'folder_id',
|
||||||
'user_id',
|
'issuer',
|
||||||
'status'
|
'status',
|
||||||
|
'revision',
|
||||||
|
'version',
|
||||||
|
'discipline',
|
||||||
|
'document_type',
|
||||||
|
'entry_date',
|
||||||
|
'current_version_id',
|
||||||
|
'code',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
public function versions() {
|
{
|
||||||
|
static::created(function ($document) {
|
||||||
|
if (request()->hasFile('file')) {
|
||||||
|
$file = request()->file('file');
|
||||||
|
$document->createVersion($file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all versions of the document.
|
||||||
|
*/
|
||||||
|
public function versions(): HasMany
|
||||||
|
{
|
||||||
return $this->hasMany(DocumentVersion::class);
|
return $this->hasMany(DocumentVersion::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current version of the document.
|
||||||
|
*/
|
||||||
|
public function currentVersion(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(DocumentVersion::class, 'current_version_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest version of the document.
|
||||||
|
*/
|
||||||
|
public function getLatestVersionAttribute()
|
||||||
|
{
|
||||||
|
return $this->versions()->latestFirst()->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new version from file content.
|
||||||
|
*/
|
||||||
|
public function createVersion(string $content, array $changes = [], ?User $user = null): DocumentVersion
|
||||||
|
{
|
||||||
|
$version = DocumentVersion::createFromContent($this, $content, $changes, $user);
|
||||||
|
|
||||||
|
// Update current version pointer
|
||||||
|
$this->update(['current_version_id' => $version->id]);
|
||||||
|
|
||||||
|
return $version;
|
||||||
|
}
|
||||||
|
|
||||||
public function approvals() {
|
public function approvals() {
|
||||||
return $this->hasMany(Approval::class);
|
return $this->hasMany(Approval::class);
|
||||||
@@ -36,17 +87,6 @@ class Document extends Model
|
|||||||
public function comments() {
|
public function comments() {
|
||||||
return $this->hasMany(Comment::class)->whereNull('parent_id');
|
return $this->hasMany(Comment::class)->whereNull('parent_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function createVersion($file)
|
|
||||||
{
|
|
||||||
return $this->versions()->create([
|
|
||||||
'file_path' => $file->store("documents/{$this->id}/versions"),
|
|
||||||
'hash' => hash_file('sha256', $file),
|
|
||||||
'user_id' => auth()->id(),
|
|
||||||
'version_number' => $this->versions()->count() + 1
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrentVersionAttribute()
|
public function getCurrentVersionAttribute()
|
||||||
{
|
{
|
||||||
@@ -55,7 +95,7 @@ class Document extends Model
|
|||||||
|
|
||||||
public function uploadVersion($file)
|
public function uploadVersion($file)
|
||||||
{
|
{
|
||||||
$this->versions()->create([
|
$version = $this->versions()->create([
|
||||||
'file_path' => $file->store("projects/{$this->id}/versions"),
|
'file_path' => $file->store("projects/{$this->id}/versions"),
|
||||||
'hash' => hash_file('sha256', $file),
|
'hash' => hash_file('sha256', $file),
|
||||||
'version' => $this->versions()->count() + 1,
|
'version' => $this->versions()->count() + 1,
|
||||||
@@ -74,4 +114,9 @@ class Document extends Model
|
|||||||
->logUnguarded();
|
->logUnguarded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,320 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class DocumentVersion extends Model
|
class DocumentVersion extends Model
|
||||||
{
|
{
|
||||||
//
|
use HasFactory;
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'document_id',
|
||||||
|
'file_path',
|
||||||
|
'hash',
|
||||||
|
'version',
|
||||||
|
'user_id',
|
||||||
|
'changes',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'changes' => 'array',
|
||||||
|
'version' => 'integer',
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be appended to the model's array form.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $appends = [
|
||||||
|
'file_url',
|
||||||
|
'file_size_formatted',
|
||||||
|
'created_at_formatted',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boot function for model events
|
||||||
|
*/
|
||||||
|
protected static function boot()
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::creating(function ($model) {
|
||||||
|
// Asegurar que la versión sea incremental para el mismo documento
|
||||||
|
if (empty($model->version)) {
|
||||||
|
$lastVersion = self::where('document_id', $model->document_id)
|
||||||
|
->max('version');
|
||||||
|
$model->version = $lastVersion ? $lastVersion + 1 : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
static::deleting(function ($model) {
|
||||||
|
// No eliminar el archivo físico si hay otras versiones que lo usan
|
||||||
|
$sameFileCount = self::where('file_path', $model->file_path)
|
||||||
|
->where('id', '!=', $model->id)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($sameFileCount === 0 && Storage::exists($model->file_path)) {
|
||||||
|
Storage::delete($model->file_path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the document that owns the version.
|
||||||
|
*/
|
||||||
|
public function document(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Document::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user who created the version.
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file URL for the version.
|
||||||
|
*/
|
||||||
|
public function getFileUrlAttribute(): string
|
||||||
|
{
|
||||||
|
return Storage::url($this->file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file size in a human-readable format.
|
||||||
|
*/
|
||||||
|
public function getFileSizeFormattedAttribute(): string
|
||||||
|
{
|
||||||
|
if (!Storage::exists($this->file_path)) {
|
||||||
|
return '0 B';
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes = Storage::size($this->file_path);
|
||||||
|
|
||||||
|
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
while ($bytes >= 1024 && $index < count($units) - 1) {
|
||||||
|
$bytes /= 1024;
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($bytes, 2) . ' ' . $units[$index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual file size in bytes.
|
||||||
|
*/
|
||||||
|
public function getFileSizeAttribute(): int
|
||||||
|
{
|
||||||
|
return Storage::exists($this->file_path) ? Storage::size($this->file_path) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get formatted created_at date.
|
||||||
|
*/
|
||||||
|
public function getCreatedAtFormattedAttribute(): string
|
||||||
|
{
|
||||||
|
return $this->created_at->format('d/m/Y H:i');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version label (v1, v2, etc.)
|
||||||
|
*/
|
||||||
|
public function getVersionLabelAttribute(): string
|
||||||
|
{
|
||||||
|
return 'v' . $this->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this is the current version of the document.
|
||||||
|
*/
|
||||||
|
public function getIsCurrentAttribute(): bool
|
||||||
|
{
|
||||||
|
return $this->document->current_version_id === $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get changes summary for display.
|
||||||
|
*/
|
||||||
|
public function getChangesSummaryAttribute(): string
|
||||||
|
{
|
||||||
|
if (empty($this->changes)) {
|
||||||
|
return 'Sin cambios registrados';
|
||||||
|
}
|
||||||
|
|
||||||
|
$changes = $this->changes;
|
||||||
|
|
||||||
|
if (is_array($changes)) {
|
||||||
|
$summary = [];
|
||||||
|
|
||||||
|
if (isset($changes['annotations_count']) && $changes['annotations_count'] > 0) {
|
||||||
|
$summary[] = $changes['annotations_count'] . ' anotación(es)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($changes['signatures_count']) && $changes['signatures_count'] > 0) {
|
||||||
|
$summary[] = $changes['signatures_count'] . ' firma(s)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($changes['stamps_count']) && $changes['stamps_count'] > 0) {
|
||||||
|
$summary[] = $changes['stamps_count'] . ' sello(s)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($changes['edited_by'])) {
|
||||||
|
$summary[] = 'por ' . $changes['edited_by'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(', ', $summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope a query to only include versions of a specific document.
|
||||||
|
*/
|
||||||
|
public function scopeOfDocument($query, $documentId)
|
||||||
|
{
|
||||||
|
return $query->where('document_id', $documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope a query to order versions by latest first.
|
||||||
|
*/
|
||||||
|
public function scopeLatestFirst($query)
|
||||||
|
{
|
||||||
|
return $query->orderBy('version', 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope a query to order versions by oldest first.
|
||||||
|
*/
|
||||||
|
public function scopeOldestFirst($query)
|
||||||
|
{
|
||||||
|
return $query->orderBy('version', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the previous version.
|
||||||
|
*/
|
||||||
|
public function getPreviousVersion(): ?self
|
||||||
|
{
|
||||||
|
return self::where('document_id', $this->document_id)
|
||||||
|
->where('version', '<', $this->version)
|
||||||
|
->orderBy('version', 'desc')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next version.
|
||||||
|
*/
|
||||||
|
public function getNextVersion(): ?self
|
||||||
|
{
|
||||||
|
return self::where('document_id', $this->document_id)
|
||||||
|
->where('version', '>', $this->version)
|
||||||
|
->orderBy('version', 'asc')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if file exists in storage.
|
||||||
|
*/
|
||||||
|
public function fileExists(): bool
|
||||||
|
{
|
||||||
|
return Storage::exists($this->file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file content.
|
||||||
|
*/
|
||||||
|
public function getFileContent(): ?string
|
||||||
|
{
|
||||||
|
return $this->fileExists() ? Storage::get($this->file_path) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify file integrity using hash.
|
||||||
|
*/
|
||||||
|
public function verifyIntegrity(): bool
|
||||||
|
{
|
||||||
|
if (!$this->fileExists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentHash = hash_file('sha256', Storage::path($this->file_path));
|
||||||
|
return $currentHash === $this->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new version from file content.
|
||||||
|
*/
|
||||||
|
public static function createFromContent(Document $document, string $content, array $changes = [], ?User $user = null): self
|
||||||
|
{
|
||||||
|
$user = $user ?: auth()->user();
|
||||||
|
|
||||||
|
// Calcular hash
|
||||||
|
$hash = hash('sha256', $content);
|
||||||
|
|
||||||
|
// Determinar siguiente versión
|
||||||
|
$lastVersion = self::where('document_id', $document->id)->max('version');
|
||||||
|
$version = $lastVersion ? $lastVersion + 1 : 1;
|
||||||
|
|
||||||
|
// Guardar archivo
|
||||||
|
$filePath = "documents/{$document->id}/versions/v{$version}.pdf";
|
||||||
|
Storage::put($filePath, $content);
|
||||||
|
|
||||||
|
// Crear registro
|
||||||
|
return self::create([
|
||||||
|
'document_id' => $document->id,
|
||||||
|
'file_path' => $filePath,
|
||||||
|
'hash' => $hash,
|
||||||
|
'version' => $version,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'changes' => $changes,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore this version as the current version.
|
||||||
|
*/
|
||||||
|
public function restoreAsCurrent(): bool
|
||||||
|
{
|
||||||
|
if (!$this->fileExists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear una nueva versión idéntica a esta
|
||||||
|
$content = $this->getFileContent();
|
||||||
|
$newVersion = self::createFromContent(
|
||||||
|
$this->document,
|
||||||
|
$content,
|
||||||
|
['restored_from' => 'v' . $this->version]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Actualizar documento para apuntar a la nueva versión
|
||||||
|
$this->document->update([
|
||||||
|
'current_version_id' => $newVersion->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
class Project extends Model
|
class Project extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
|
'reference',
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'creator_id',
|
'creator_id',
|
||||||
@@ -22,6 +23,7 @@ class Project extends Model
|
|||||||
'icon',
|
'icon',
|
||||||
'start_date',
|
'start_date',
|
||||||
'deadline',
|
'deadline',
|
||||||
|
'company_id',
|
||||||
// Agrega cualquier otro campo nuevo aquí
|
// Agrega cualquier otro campo nuevo aquí
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -81,5 +83,113 @@ class Project extends Model
|
|||||||
return $this->belongsToMany(Category::class);
|
return $this->belongsToMany(Category::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function company()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Company::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agregar estas relaciones al modelo Project
|
||||||
|
public function codingConfig()
|
||||||
|
{
|
||||||
|
return $this->hasOne(ProjectCodingConfig::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function documentStatuses()
|
||||||
|
{
|
||||||
|
//return $this->hasMany(ProjectDocumentStatus::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function defaultStatus()
|
||||||
|
{
|
||||||
|
//return $this->hasOne(ProjectDocumentStatus::class)->where('is_default', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método para inicializar la configuración
|
||||||
|
public function initializeSettings()
|
||||||
|
{
|
||||||
|
// Crear configuración de codificación si no existe
|
||||||
|
if (!$this->codingConfig) {
|
||||||
|
$this->codingConfig()->create([
|
||||||
|
'format' => '[PROJECT]-[TYPE]-[YEAR]-[SEQUENCE]',
|
||||||
|
'next_sequence' => 1,
|
||||||
|
'year_format' => 'Y',
|
||||||
|
'separator' => '-',
|
||||||
|
'sequence_length' => 4,
|
||||||
|
'auto_generate' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear estados predeterminados si no existen
|
||||||
|
/*
|
||||||
|
if ($this->documentStatuses()->count() === 0) {
|
||||||
|
$defaultStatuses = [
|
||||||
|
[
|
||||||
|
'name' => 'Borrador',
|
||||||
|
'slug' => 'draft',
|
||||||
|
'color' => '#6b7280', // Gris
|
||||||
|
'order' => 1,
|
||||||
|
'is_default' => true,
|
||||||
|
'allow_upload' => true,
|
||||||
|
'allow_edit' => true,
|
||||||
|
'allow_delete' => true,
|
||||||
|
'requires_approval' => false,
|
||||||
|
'description' => 'Documento en proceso de creación',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'En Revisión',
|
||||||
|
'slug' => 'in_review',
|
||||||
|
'color' => '#f59e0b', // Ámbar
|
||||||
|
'order' => 2,
|
||||||
|
'is_default' => false,
|
||||||
|
'allow_upload' => false,
|
||||||
|
'allow_edit' => false,
|
||||||
|
'allow_delete' => false,
|
||||||
|
'requires_approval' => true,
|
||||||
|
'description' => 'Documento en proceso de revisión',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Aprobado',
|
||||||
|
'slug' => 'approved',
|
||||||
|
'color' => '#10b981', // Verde
|
||||||
|
'order' => 3,
|
||||||
|
'is_default' => false,
|
||||||
|
'allow_upload' => false,
|
||||||
|
'allow_edit' => false,
|
||||||
|
'allow_delete' => false,
|
||||||
|
'requires_approval' => false,
|
||||||
|
'description' => 'Documento aprobado',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Rechazado',
|
||||||
|
'slug' => 'rejected',
|
||||||
|
'color' => '#ef4444', // Rojo
|
||||||
|
'order' => 4,
|
||||||
|
'is_default' => false,
|
||||||
|
'allow_upload' => true,
|
||||||
|
'allow_edit' => true,
|
||||||
|
'allow_delete' => false,
|
||||||
|
'requires_approval' => false,
|
||||||
|
'description' => 'Documento rechazado',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'Archivado',
|
||||||
|
'slug' => 'archived',
|
||||||
|
'color' => '#8b5cf6', // Violeta
|
||||||
|
'order' => 5,
|
||||||
|
'is_default' => false,
|
||||||
|
'allow_upload' => false,
|
||||||
|
'allow_edit' => false,
|
||||||
|
'allow_delete' => false,
|
||||||
|
'requires_approval' => false,
|
||||||
|
'description' => 'Documento archivado',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defaultStatuses as $status) {
|
||||||
|
//$this->documentStatuses()->create($status);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
107
app/Models/ProjectCodingConfig.php
Normal file
107
app/Models/ProjectCodingConfig.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ProjectCodingConfig extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'project_id',
|
||||||
|
'format',
|
||||||
|
'elements',
|
||||||
|
'next_sequence',
|
||||||
|
'year_format',
|
||||||
|
'separator',
|
||||||
|
'sequence_length',
|
||||||
|
'auto_generate'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'elements' => 'array',
|
||||||
|
'auto_generate' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método para generar un código de documento
|
||||||
|
public function generateCode($type = 'DOC', $year = null)
|
||||||
|
{
|
||||||
|
$code = $this->format;
|
||||||
|
|
||||||
|
// Reemplazar variables
|
||||||
|
$replacements = [
|
||||||
|
'[PROJECT]' => $this->project->code ?? 'PROJ',
|
||||||
|
'[TYPE]' => $type,
|
||||||
|
'[YEAR]' => $year ?? date($this->year_format),
|
||||||
|
'[MONTH]' => date('m'),
|
||||||
|
'[DAY]' => date('d'),
|
||||||
|
'[SEQUENCE]' => str_pad($this->next_sequence, $this->sequence_length, '0', STR_PAD_LEFT),
|
||||||
|
'[RANDOM]' => strtoupper(substr(md5(uniqid()), 0, 6)),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Si hay elementos personalizados, agregarlos
|
||||||
|
if (is_array($this->elements)) {
|
||||||
|
foreach ($this->elements as $key => $value) {
|
||||||
|
$replacements["[{$key}]"] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = str_replace(array_keys($replacements), array_values($replacements), $code);
|
||||||
|
|
||||||
|
// Incrementar secuencia
|
||||||
|
if (strpos($this->format, '[SEQUENCE]') !== false && $this->auto_generate) {
|
||||||
|
$this->increment('next_sequence');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método para obtener elementos disponibles
|
||||||
|
public static function getAvailableElements()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'project' => [
|
||||||
|
'label' => 'Código del Proyecto',
|
||||||
|
'description' => 'Código único del proyecto',
|
||||||
|
'variable' => '[PROJECT]',
|
||||||
|
],
|
||||||
|
'type' => [
|
||||||
|
'label' => 'Tipo de Documento',
|
||||||
|
'description' => 'Tipo de documento (DOC, IMG, PDF, etc.)',
|
||||||
|
'variable' => '[TYPE]',
|
||||||
|
],
|
||||||
|
'year' => [
|
||||||
|
'label' => 'Año',
|
||||||
|
'description' => 'Año actual en formato configurable',
|
||||||
|
'variable' => '[YEAR]',
|
||||||
|
],
|
||||||
|
'month' => [
|
||||||
|
'label' => 'Mes',
|
||||||
|
'description' => 'Mes actual (01-12)',
|
||||||
|
'variable' => '[MONTH]',
|
||||||
|
],
|
||||||
|
'day' => [
|
||||||
|
'label' => 'Día',
|
||||||
|
'description' => 'Día actual (01-31)',
|
||||||
|
'variable' => '[DAY]',
|
||||||
|
],
|
||||||
|
'sequence' => [
|
||||||
|
'label' => 'Secuencia',
|
||||||
|
'description' => 'Número secuencial autoincremental',
|
||||||
|
'variable' => '[SEQUENCE]',
|
||||||
|
],
|
||||||
|
'random' => [
|
||||||
|
'label' => 'Aleatorio',
|
||||||
|
'description' => 'Cadena aleatoria de 6 caracteres',
|
||||||
|
'variable' => '[RANDOM]',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ use Illuminate\Notifications\Notifiable;
|
|||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
@@ -109,5 +110,17 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
return $this->profile_photo ? Storage::url($this->profile_photo) : asset('images/default-user.png');
|
return $this->profile_photo ? Storage::url($this->profile_photo) : asset('images/default-user.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function companies(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Company::class, 'company_contacts')
|
||||||
|
->withPivot('position')
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function company()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Company::class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ namespace App\Providers;
|
|||||||
|
|
||||||
use App\Livewire\FileUpload;
|
use App\Livewire\FileUpload;
|
||||||
use App\Livewire\ImageUploader;
|
use App\Livewire\ImageUploader;
|
||||||
|
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;
|
||||||
@@ -20,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] ?? []);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +50,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
Livewire::component('file-upload', FileUpload::class);
|
Livewire::component('file-upload', FileUpload::class);
|
||||||
Livewire::component('toolbar', Toolbar::class);
|
Livewire::component('toolbar', Toolbar::class);
|
||||||
Livewire::component('image-uploader', ImageUploader::class);
|
Livewire::component('image-uploader', ImageUploader::class);
|
||||||
|
Livewire::component('pdf-viewer', PdfViewer::class);
|
||||||
|
|
||||||
// Validación personalizada
|
// Validación personalizada
|
||||||
Validator::extend('max_upload_size', function ($attribute, $value, $parameters, $validator) {
|
Validator::extend('max_upload_size', function ($attribute, $value, $parameters, $validator) {
|
||||||
@@ -47,5 +61,6 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
return $totalSize <= ($maxSize * 1024);
|
return $totalSize <= ($maxSize * 1024);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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";
|
||||||
|
}
|
||||||
|
*/
|
||||||
30
app/View/Components/Accordion.php
Normal file
30
app/View/Components/Accordion.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Accordion extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return <<<'blade'
|
||||||
|
<div>
|
||||||
|
<!-- Simplicity is the essence of happiness. - Cedric Bledsoe -->
|
||||||
|
</div>
|
||||||
|
blade;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/View/Components/AccordionItem.php
Normal file
26
app/View/Components/AccordionItem.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class AccordionItem extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.accordion-item');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"livewire/flux": "^2.1.1",
|
"livewire/flux": "^2.1.1",
|
||||||
"livewire/volt": "^1.7.0",
|
"livewire/volt": "^1.7.0",
|
||||||
|
"luvi-ui/laravel-luvi": "^0.6.0",
|
||||||
|
"rappasoft/laravel-livewire-tables": "^3.7",
|
||||||
"spatie/laravel-activitylog": "^4.10",
|
"spatie/laravel-activitylog": "^4.10",
|
||||||
"spatie/laravel-medialibrary": "^11.12",
|
"spatie/laravel-medialibrary": "^11.12",
|
||||||
"spatie/laravel-permission": "^6.17",
|
"spatie/laravel-permission": "^6.17",
|
||||||
|
|||||||
831
composer.lock
generated
831
composer.lock
generated
File diff suppressed because it is too large
Load Diff
214
config/livewire-pdf.php
Normal file
214
config/livewire-pdf.php
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PDF Storage Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value determines the path where uploaded PDFs will be stored.
|
||||||
|
| The path is relative to the storage directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'storage_path' => 'pdfs',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Temporary Storage Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value determines the path where temporary PDFs will be stored
|
||||||
|
| during processing. The path is relative to the storage directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'temp_path' => 'pdfs/temp',
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| PDF Processing Options
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These options control how PDFs are processed and rendered.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'processing' => [
|
||||||
|
'max_file_size' => 10 * 1024 * 1024, // 10MB
|
||||||
|
'allowed_extensions' => ['pdf'],
|
||||||
|
'use_web_workers' => true,
|
||||||
|
'cache_rendered_pages' => true,
|
||||||
|
'cache_ttl' => 60 * 24, // 24 hours
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Form Field Options
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These options control the default properties for form fields.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'form_fields' => [
|
||||||
|
'text' => [
|
||||||
|
'font_size' => 12,
|
||||||
|
'font_family' => 'Helvetica',
|
||||||
|
'color' => '#000000',
|
||||||
|
'multiline' => false,
|
||||||
|
],
|
||||||
|
'checkbox' => [
|
||||||
|
'size' => 12,
|
||||||
|
'checked_by_default' => false,
|
||||||
|
],
|
||||||
|
'radio' => [
|
||||||
|
'size' => 12,
|
||||||
|
'selected_by_default' => false,
|
||||||
|
],
|
||||||
|
'dropdown' => [
|
||||||
|
'font_size' => 12,
|
||||||
|
'font_family' => 'Helvetica',
|
||||||
|
],
|
||||||
|
'signature' => [
|
||||||
|
'width' => 200,
|
||||||
|
'height' => 50,
|
||||||
|
],
|
||||||
|
'date' => [
|
||||||
|
'format' => 'YYYY-MM-DD',
|
||||||
|
'font_size' => 12,
|
||||||
|
'font_family' => 'Helvetica',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| eSignature Platform Compatibility
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These options control compatibility with various eSignature platforms.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'esignature_compatibility' => [
|
||||||
|
'docusign' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'field_naming_convention' => 'docusign',
|
||||||
|
],
|
||||||
|
'adobe_sign' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'field_naming_convention' => 'adobe',
|
||||||
|
],
|
||||||
|
'pandadoc' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'field_naming_convention' => 'pandadoc',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| UI Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure the UI elements of the PDF viewer and editor.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'ui' => [
|
||||||
|
// Available zoom levels for the PDF viewer
|
||||||
|
'zoom_levels' => [0.5, 0.75, 1, 1.25, 1.5, 2],
|
||||||
|
|
||||||
|
// Default zoom level
|
||||||
|
'default_zoom' => 1,
|
||||||
|
|
||||||
|
// Show page thumbnails
|
||||||
|
'show_thumbnails' => true,
|
||||||
|
|
||||||
|
// Show toolbar
|
||||||
|
'show_toolbar' => true,
|
||||||
|
|
||||||
|
// Show sidebar
|
||||||
|
'show_sidebar' => true,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Form Field Types
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure the available form field types.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
'field_types' => [
|
||||||
|
'text' => [
|
||||||
|
'label' => 'Text Field',
|
||||||
|
'icon' => 'text-field',
|
||||||
|
'properties' => [
|
||||||
|
'font_size' => 12,
|
||||||
|
'font_family' => 'Helvetica',
|
||||||
|
'text_color' => '#000000',
|
||||||
|
'background_color' => '#FFFFFF',
|
||||||
|
'border_color' => '#000000',
|
||||||
|
'border_width' => 1,
|
||||||
|
'border_style' => 'solid',
|
||||||
|
'placeholder' => '',
|
||||||
|
'max_length' => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'checkbox' => [
|
||||||
|
'label' => 'Checkbox',
|
||||||
|
'icon' => 'checkbox',
|
||||||
|
'properties' => [
|
||||||
|
'checked' => false,
|
||||||
|
'background_color' => '#FFFFFF',
|
||||||
|
'border_color' => '#000000',
|
||||||
|
'border_width' => 1,
|
||||||
|
'border_style' => 'solid',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'radio' => [
|
||||||
|
'label' => 'Radio Button',
|
||||||
|
'icon' => 'radio',
|
||||||
|
'properties' => [
|
||||||
|
'checked' => false,
|
||||||
|
'background_color' => '#FFFFFF',
|
||||||
|
'border_color' => '#000000',
|
||||||
|
'border_width' => 1,
|
||||||
|
'border_style' => 'solid',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'dropdown' => [
|
||||||
|
'label' => 'Dropdown',
|
||||||
|
'icon' => 'dropdown',
|
||||||
|
'properties' => [
|
||||||
|
'options' => [],
|
||||||
|
'selected' => null,
|
||||||
|
'font_size' => 12,
|
||||||
|
'font_family' => 'Helvetica',
|
||||||
|
'text_color' => '#000000',
|
||||||
|
'background_color' => '#FFFFFF',
|
||||||
|
'border_color' => '#000000',
|
||||||
|
'border_width' => 1,
|
||||||
|
'border_style' => 'solid',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'signature' => [
|
||||||
|
'label' => 'Signature',
|
||||||
|
'icon' => 'signature',
|
||||||
|
'properties' => [
|
||||||
|
'background_color' => '#FFFFFF',
|
||||||
|
'border_color' => '#000000',
|
||||||
|
'border_width' => 1,
|
||||||
|
'border_style' => 'solid',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'date' => [
|
||||||
|
'label' => 'Date',
|
||||||
|
'icon' => 'date',
|
||||||
|
'properties' => [
|
||||||
|
'format' => 'YYYY-MM-DD',
|
||||||
|
'font_size' => 12,
|
||||||
|
'font_family' => 'Helvetica',
|
||||||
|
'text_color' => '#000000',
|
||||||
|
'background_color' => '#FFFFFF',
|
||||||
|
'border_color' => '#000000',
|
||||||
|
'border_width' => 1,
|
||||||
|
'border_style' => 'solid',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -13,10 +13,23 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
Schema::create('documents', function (Blueprint $table) {
|
Schema::create('documents', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->string('code');
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->enum('status', ['pending', 'in_review', 'approved', 'rejected'])->default('pending');
|
|
||||||
$table->foreignId('project_id')->constrained();
|
$table->foreignId('project_id')->constrained();
|
||||||
$table->foreignId('folder_id')->nullable()->constrained();
|
$table->foreignId('folder_id')->nullable()->constrained();
|
||||||
|
|
||||||
|
$table->string('area', 2)->nullable();
|
||||||
|
$table->string('discipline', 2)->nullable();
|
||||||
|
$table->string('document_type', 3)->nullable();
|
||||||
|
|
||||||
|
$table->string('version')->nullable()->default("0");
|
||||||
|
$table->string('revision')->nullable()->default("0");
|
||||||
|
|
||||||
|
$table->enum('status', [0, 1, 2, 3, 4])->default(0); //['pending', 'in_review', 'approved', 'rejected']
|
||||||
|
|
||||||
|
$table->string('issuer_id')->nullable();
|
||||||
|
$table->date('entry_date')->nullable()->default(now());
|
||||||
//$table->foreignId('current_version_id')->nullable()->constrained('document_versions');
|
//$table->foreignId('current_version_id')->nullable()->constrained('document_versions');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,11 +15,19 @@ return new class extends Migration
|
|||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('document_id')->constrained();
|
$table->foreignId('document_id')->constrained();
|
||||||
$table->string('file_path');
|
$table->string('file_path');
|
||||||
$table->string('hash');
|
$table->string('hash'); // SHA256 hash for integrity verification
|
||||||
$table->unsignedInteger('version')->default(1);
|
$table->unsignedInteger('version')->default(1);
|
||||||
|
$table->unsignedInteger('review')->default(0);
|
||||||
$table->foreignId('user_id')->constrained();
|
$table->foreignId('user_id')->constrained();
|
||||||
$table->text('changes')->nullable();
|
$table->text('changes')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Unique constraint to ensure one version per document
|
||||||
|
$table->unique(['document_id', 'version', 'review']);
|
||||||
|
|
||||||
|
// Index for faster queries
|
||||||
|
$table->index(['document_id', 'version']);
|
||||||
|
$table->index('hash');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('projects', function (Blueprint $table) {
|
Schema::table('projects', function (Blueprint $table) {
|
||||||
|
$table->string('reference', 12)->nullable()->after('id')->uniqidue();
|
||||||
$table->string('status')->nullable();
|
$table->string('status')->nullable();
|
||||||
$table->string('project_image_path')->nullable();
|
$table->string('project_image_path')->nullable();
|
||||||
$table->string('address')->nullable();
|
$table->string('address')->nullable();
|
||||||
@@ -33,6 +34,7 @@ return new class extends Migration
|
|||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::table('documents', function (Blueprint $table) {
|
Schema::table('documents', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('reference');
|
||||||
$table->dropColumn('status');
|
$table->dropColumn('status');
|
||||||
$table->dropColumn('project_image_path');
|
$table->dropColumn('project_image_path');
|
||||||
$table->dropColumn('address');
|
$table->dropColumn('address');
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('companies', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name'); // Nombre legal
|
||||||
|
$table->string('commercial_name'); // Apodo comercial
|
||||||
|
$table->enum('status', ['active', 'closed'])->default('active');
|
||||||
|
$table->string('address')->nullable();
|
||||||
|
$table->string('postal_code')->nullable();
|
||||||
|
$table->string('city')->nullable();
|
||||||
|
$table->string('country')->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->string('email')->nullable();
|
||||||
|
$table->string('cif')->nullable(); // Código de identificación fiscal
|
||||||
|
$table->string('logo')->nullable(); // Ruta del logo
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('companies');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('company_contacts', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('company_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->foreignId('user_id')->constrained()->onDelete('cascade');
|
||||||
|
$table->string('position')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['company_id', 'user_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('company_contacts');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// 1. Corregir tipo de dato para 'type' (boolean en lugar de boolval)
|
||||||
|
$table->unsignedSmallInteger('user_type')->default(false)->after('remember_token');
|
||||||
|
|
||||||
|
// 2. Agregar company_id como nullable con constrained correcto
|
||||||
|
$table->foreignId('company_id')
|
||||||
|
->nullable()
|
||||||
|
->after('user_type')
|
||||||
|
->constrained('companies'); // Especificar tabla explícitamente
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('projects', function (Blueprint $table) {
|
||||||
|
$table->foreignId('company_id')
|
||||||
|
->constrained('companies');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// 5. Eliminar restricción de clave foránea primero
|
||||||
|
$table->dropForeign(['company_id']);
|
||||||
|
|
||||||
|
// 6. Eliminar columnas en orden inverso
|
||||||
|
$table->dropColumn('company_id');
|
||||||
|
$table->dropColumn('user_type');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('projects', function (Blueprint $table) {
|
||||||
|
// 7. Eliminar restricción antes de la columna
|
||||||
|
$table->dropForeign(['company_id']);
|
||||||
|
$table->dropColumn('company_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('project_coding_configs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('project_id')->constrained()->onDelete('cascade')->unique();
|
||||||
|
$table->string('format')->default('[PROJECT]-[TYPE]-[YEAR]-[SEQUENCE]');
|
||||||
|
$table->json('elements')->nullable(); // Elementos configurados del código
|
||||||
|
$table->integer('next_sequence')->default(1);
|
||||||
|
$table->string('year_format')->default('Y'); // Y, y, YY, yyyy
|
||||||
|
$table->string('separator')->default('-');
|
||||||
|
$table->integer('sequence_length')->default(4);
|
||||||
|
$table->boolean('auto_generate')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('project_coding_configs');
|
||||||
|
}
|
||||||
|
};
|
||||||
93
docs/ISO19650_Naming_Convention.md
Normal file
93
docs/ISO19650_Naming_Convention.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# ISO 19650 Project Naming Convention
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document outlines the naming convention for projects based on the ISO 19650 standard. The naming convention ensures consistency, clarity, and compliance with industry standards.
|
||||||
|
|
||||||
|
## Naming Fields
|
||||||
|
The naming convention consists of the following fields:
|
||||||
|
|
||||||
|
| Field | Definition | Requirement | Length |
|
||||||
|
|--------------------|---------------------------------------------------------------------------|-------------------|--------------|
|
||||||
|
| **Proyecto** | Identificador del expediente, contrato o proyecto | Requerido | 2-12 |
|
||||||
|
| **Creador** | Organización creadora del documento | Requerido | 3-6 |
|
||||||
|
| **Volumen o Sistema** | Agrupaciones, áreas o tramos representativos en los que se fragmenta el proyecto | Requerido | 2-3 |
|
||||||
|
| **Nivel o Localización** | Localización dentro de un Volumen o Sistema | Requerido | 3 |
|
||||||
|
| **Tipo de Documento** | Tipología de documento, entregable o auxiliar | Requerido | 3 |
|
||||||
|
| **Disciplina** | Ámbito al que se corresponde el documento | Requerido | 3 |
|
||||||
|
| **Número** | Enumerador de partes | Requerido | 3 |
|
||||||
|
| **Descripción** | Texto que describe el documento y su contenido | Opcional | Sin límite |
|
||||||
|
| **Estado** | Situación, temporal o definitiva, del documento | Opcional/Metadato | 2 |
|
||||||
|
| **Revisión** | Versión del documento | Opcional/Metadato | 4 |
|
||||||
|
|
||||||
|
## Field Details
|
||||||
|
|
||||||
|
### Proyecto
|
||||||
|
- **Definition**: Identifies the project, contract, or file.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 2-12 characters
|
||||||
|
|
||||||
|
### Creador
|
||||||
|
- **Definition**: Organization responsible for creating the document.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 3-6 characters
|
||||||
|
|
||||||
|
### Volumen o Sistema
|
||||||
|
- **Definition**: Representative groupings, areas, or sections of the project.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 2-3 characters
|
||||||
|
|
||||||
|
### Nivel o Localización
|
||||||
|
- **Definition**: Location within a Volume or System.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 3 characters
|
||||||
|
|
||||||
|
### Tipo de Documento
|
||||||
|
- **Definition**: Document type, deliverable, or auxiliary.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 3 characters
|
||||||
|
|
||||||
|
### Disciplina
|
||||||
|
- **Definition**: Scope to which the document corresponds.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 3 characters
|
||||||
|
|
||||||
|
### Número
|
||||||
|
- **Definition**: Part enumerator.
|
||||||
|
- **Requirement**: Required
|
||||||
|
- **Length**: 3 characters
|
||||||
|
|
||||||
|
### Descripción
|
||||||
|
- **Definition**: Text describing the document and its content.
|
||||||
|
- **Requirement**: Optional
|
||||||
|
- **Length**: Unlimited
|
||||||
|
|
||||||
|
### Estado
|
||||||
|
- **Definition**: Temporary or definitive status of the document.
|
||||||
|
- **Requirement**: Optional/Metadata
|
||||||
|
- **Length**: 2 characters
|
||||||
|
|
||||||
|
### Revisión
|
||||||
|
- **Definition**: Document version.
|
||||||
|
- **Requirement**: Optional/Metadata
|
||||||
|
- **Length**: 4 characters
|
||||||
|
|
||||||
|
## Example
|
||||||
|
A sample project name following this convention:
|
||||||
|
```
|
||||||
|
PRJ001-ORG01-V01-L01-DOC-ARC-001-Description-ST-0001
|
||||||
|
```
|
||||||
|
Where:
|
||||||
|
- **PRJ001**: Project identifier
|
||||||
|
- **ORG01**: Creator organization
|
||||||
|
- **V01**: Volume
|
||||||
|
- **L01**: Level
|
||||||
|
- **DOC**: Document type
|
||||||
|
- **ARC**: Discipline
|
||||||
|
- **001**: Part number
|
||||||
|
- **Description**: Description of the document
|
||||||
|
- **ST**: Status
|
||||||
|
- **0001**: Revision
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- All fields marked as "Required" must be included.
|
||||||
|
- Optional fields can be omitted if not applicable.
|
||||||
80
docs/ProjectNamingCodeGenerator.md
Normal file
80
docs/ProjectNamingCodeGenerator.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Project Naming Code Generator
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The `ProjectNamingSchema` class provides a utility to generate project names based on the ISO 19650 naming convention. This ensures consistency and compliance with industry standards.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Ensure the `ProjectNamingSchema` class is located in the `App\Helpers` namespace. No additional installation steps are required.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
To generate a project name, use the `generate` method of the `ProjectNamingSchema` class. This method accepts an associative array of fields and returns the generated project name.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```php
|
||||||
|
use App\Helpers\ProjectNamingSchema;
|
||||||
|
|
||||||
|
$fields = [
|
||||||
|
'project' => 'PRJ001',
|
||||||
|
'creator' => 'ORG01',
|
||||||
|
'volume' => 'V01',
|
||||||
|
'level' => 'L01',
|
||||||
|
'documentType' => 'DOC',
|
||||||
|
'discipline' => 'ARC',
|
||||||
|
'number' => '1',
|
||||||
|
'description' => 'Description',
|
||||||
|
'status' => 'ST',
|
||||||
|
'revision' => '1',
|
||||||
|
];
|
||||||
|
|
||||||
|
$projectName = ProjectNamingSchema::generate($fields);
|
||||||
|
|
||||||
|
// Output: PRJ001-ORG01-V01-L01-DOC-ARC-001-Description-ST-0001
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
The following fields are supported:
|
||||||
|
|
||||||
|
| Field | Definition | Requirement | Length |
|
||||||
|
|--------------------|---------------------------------------------------------------------------|-------------------|--------------|
|
||||||
|
| **project** | Identifies the project, contract, or file. | Required | 2-12 |
|
||||||
|
| **creator** | Organization responsible for creating the document. | Required | 3-6 |
|
||||||
|
| **volume** | Representative groupings, areas, or sections of the project. | Required | 2-3 |
|
||||||
|
| **level** | Location within a Volume or System. | Required | 3 |
|
||||||
|
| **documentType** | Document type, deliverable, or auxiliary. | Required | 3 |
|
||||||
|
| **discipline** | Scope to which the document corresponds. | Required | 3 |
|
||||||
|
| **number** | Part enumerator. | Required | 3 |
|
||||||
|
| **description** | Text describing the document and its content. | Optional | Unlimited |
|
||||||
|
| **status** | Temporary or definitive status of the document. | Optional | 2 |
|
||||||
|
| **revision** | Document version. | Optional | 4 |
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
The `generate` method validates that all required fields are provided. If any required field is missing, an `InvalidArgumentException` is thrown.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```php
|
||||||
|
$fields = [
|
||||||
|
'creator' => 'ORG01',
|
||||||
|
'volume' => 'V01',
|
||||||
|
'level' => 'L01',
|
||||||
|
'documentType' => 'DOC',
|
||||||
|
'discipline' => 'ARC',
|
||||||
|
'number' => '1',
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$projectName = ProjectNamingSchema::generate($fields);
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
echo $e->getMessage(); // Output: The field 'project' is required.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
Unit tests for the `ProjectNamingSchema` class are located in the `tests/Unit/ProjectNamingSchemaTest.php` file. Run the tests using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vendor\bin\pest --filter ProjectNamingSchemaTest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- All fields marked as "Required" must be included.
|
||||||
|
- Optional fields can be omitted if not applicable.
|
||||||
1441
package-lock.json
generated
1441
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,9 @@
|
|||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"axios": "^1.7.4",
|
"axios": "^1.7.4",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
|
"fabric": "^6.7.1",
|
||||||
"laravel-vite-plugin": "^1.0",
|
"laravel-vite-plugin": "^1.0",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^5.2.133",
|
"pdfjs-dist": "^5.2.133",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.3",
|
||||||
"tailwindcss": "^4.0.7",
|
"tailwindcss": "^4.0.7",
|
||||||
|
|||||||
177
resources/js/pdfjs-5.2.133-dist/LICENSE
Normal file
177
resources/js/pdfjs-5.2.133-dist/LICENSE
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
22614
resources/js/pdfjs-5.2.133-dist/build/pdf.mjs
Normal file
22614
resources/js/pdfjs-5.2.133-dist/build/pdf.mjs
Normal file
File diff suppressed because it is too large
Load Diff
1
resources/js/pdfjs-5.2.133-dist/build/pdf.mjs.map
Normal file
1
resources/js/pdfjs-5.2.133-dist/build/pdf.mjs.map
Normal file
File diff suppressed because one or more lines are too long
216
resources/js/pdfjs-5.2.133-dist/build/pdf.sandbox.mjs
Normal file
216
resources/js/pdfjs-5.2.133-dist/build/pdf.sandbox.mjs
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
57734
resources/js/pdfjs-5.2.133-dist/build/pdf.worker.mjs
Normal file
57734
resources/js/pdfjs-5.2.133-dist/build/pdf.worker.mjs
Normal file
File diff suppressed because one or more lines are too long
1
resources/js/pdfjs-5.2.133-dist/build/pdf.worker.mjs.map
Normal file
1
resources/js/pdfjs-5.2.133-dist/build/pdf.worker.mjs.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-EUC-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-EUC-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-EUC-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-EUC-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-RKSJ-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78ms-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78ms-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78ms-RKSJ-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/78ms-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/83pv-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/83pv-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90ms-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90ms-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90ms-RKSJ-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90ms-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90msp-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90msp-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90msp-RKSJ-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90msp-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90pv-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90pv-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90pv-RKSJ-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/90pv-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-RKSJ-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-RKSJ-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-RKSJ-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-RKSJ-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Add-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-0.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-0.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-1.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-1.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-2.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-2.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-3.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-3.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-4.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-4.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-5.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-5.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-6.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-6.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-UCS2.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-CNS1-UCS2.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-0.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-0.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-1.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-1.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-2.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-2.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-3.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-3.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-4.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-4.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-5.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-5.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-UCS2.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-GB1-UCS2.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-0.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-0.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-1.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-1.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-2.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-2.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-3.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-3.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-4.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-4.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-5.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-5.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-6.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Japan1-6.bcmap
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Korea1-0.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Korea1-0.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Korea1-1.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Korea1-1.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Korea1-2.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/Adobe-Korea1-2.bcmap
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5pc-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5pc-H.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5pc-V.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/B5pc-V.bcmap
Normal file
Binary file not shown.
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/CNS-EUC-H.bcmap
Normal file
BIN
resources/js/pdfjs-5.2.133-dist/web/cmaps/CNS-EUC-H.bcmap
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user