321 lines
8.1 KiB
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;
|
|
}
|
|
} |