updates to document handling and code editing features
This commit is contained in:
@@ -6,6 +6,8 @@ use App\Events\DocumentVersionUpdated;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Spatie\Activitylog\LogOptions;
|
||||
use Spatie\Activitylog\Traits\LogsActivity;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Document extends Model
|
||||
{
|
||||
@@ -20,22 +22,63 @@ class Document extends Model
|
||||
'file_path',
|
||||
'project_id', // Asegurar que está en fillable
|
||||
'folder_id',
|
||||
'user_id',
|
||||
'issuer',
|
||||
'status',
|
||||
'revision',
|
||||
'version',
|
||||
'discipline',
|
||||
'document_type',
|
||||
'issuer',
|
||||
'entry_date',
|
||||
'current_version_id',
|
||||
'code',
|
||||
];
|
||||
|
||||
|
||||
public function versions() {
|
||||
protected static function booted()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return $this->hasMany(Approval::class);
|
||||
@@ -44,17 +87,6 @@ class Document extends Model
|
||||
public function comments() {
|
||||
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()
|
||||
{
|
||||
@@ -63,7 +95,7 @@ class Document extends Model
|
||||
|
||||
public function uploadVersion($file)
|
||||
{
|
||||
$this->versions()->create([
|
||||
$version = $this->versions()->create([
|
||||
'file_path' => $file->store("projects/{$this->id}/versions"),
|
||||
'hash' => hash_file('sha256', $file),
|
||||
'version' => $this->versions()->count() + 1,
|
||||
@@ -82,4 +114,9 @@ class Document extends Model
|
||||
->logUnguarded();
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,320 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user