Files
Nexora/app/Models/DocumentVersion.php

321 lines
8.1 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Storage;
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;
}
}