Lixeira
Lixeira e arquivo morto via status (GedTrashService) — distinto do soft delete do framework. Retenção configurável.
Lixeira
O GED tem seu próprio fluxo de lixeira/arquivo morto, independente do soft
delete genérico do framework. É uma máquina de estados sobre a coluna
Document.status (active ↔ trash ↔ archived), implementada em
App\Service\Ged\GedTrashService.
Por que não é o
HasMadSoftDeletesdo framework? Todo model do GED usa o trait, mas ele só vira soft delete quando o model mapeia uma colunadeleted_at(via$madAuditou a propriedade legada) — nenhum model do GED faz esse mapeamento. Na prática,Document::find($id)->delete()é uma exclusão física real nesta instalação, não uma marcação. A "lixeira" do GED é deliberadamente um conceito de negócio à parte (documento continua visível/recuperável viastatus), não o mecanismo de soft delete do Eloquent.
Os estados
active ──trash()────────► trash ──restore()──────► active
active ──archive()──────► archived ──restoreFromArchive()──► active
trash ──permanentDelete()──► (removido de verdade)
use App\Service\Ged\GedTrashService;
GedTrashService::trash($documentId, $userId); // active → trash
GedTrashService::restore($documentId, $userId); // trash → active
GedTrashService::archive($documentId, $userId); // active → archived
GedTrashService::restoreFromArchive($documentId, $userId); // archived → active
GedTrashService::permanentDelete($documentId, $userId); // remove de vez
trash()
$doc->original_folder_id = $doc->folder_id; // lembra de onde veio
$doc->folder_id = null; // sai da árvore de pastas
$doc->status = 'trash';
$doc->trash_date = date('Y-m-d H:i:s');
$doc->trashed_by = $userId;
$doc->save();
O documento some da pasta onde estava (folder_id = null) enquanto está na
lixeira — restore() usa original_folder_id para devolvê-lo ao lugar
certo.
permanentDelete() — o que de fato é apagado
// objetos no disco de uploads — todo IO via Storage/Flysystem
foreach ($versions as $v) {
GedStorage::delete((string) ($v->storage_path ?? '')); // chave inválida = no-op
}
// e o prefixo inteiro do documento, INCLUSIVE sobras não registradas no banco
\Mad\Service\MadUploadStorage::deleteDirectory("files/ged/{$documentId}");
// linhas relacionadas (todas exclusões reais — sem soft delete ativo)
DocumentVersion::where('document_id', $documentId)->delete();
DocumentTag::where('document_id', $documentId)->delete();
DocumentPermission::where('document_id', $documentId)->delete();
SharedLink::where('document_id', $documentId)->delete();
Favorite::where('document_id', $documentId)->delete();
ActivityLog::where('document_id', $documentId)->delete();
$doc->delete();
Objetos no storage, todas as linhas filhas (versões, tags, permissões, links,
favoritos, atividade) e a linha do documento em si — tudo removido, dentro de
uma transação para as linhas do banco. Não há como recuperar um documento
depois de permanentDelete().
O deleteDirectory() existe além do loop por versão de propósito: ele varre o
prefixo inteiro e leva embora arquivos órfãos (um upload que gravou no storage
e cujo commit da transação falhou, por exemplo). Como todo o IO passa pelo
disco de uploads (mad_uploads por default, ou o de MAD_UPLOAD_DISK), o
mesmo código funciona em S3 — ver
Upload e versionamento.
Retenção automática
public static function purgeExpired(): void
Lê ged_trash_retention_days (preferência do sistema, default 30), encontra todo
documento com status = 'trash' e trash_date além do prazo, e chama
permanentDelete() em cada um (com userId = 0, o que silencia o log de
atividade — propositalmente: é uma rotina automática, não uma ação de
usuário).
purgeExpired() não está agendada em routes/console.php nesta
instalação — para que a retenção funcione automaticamente, registre-a você
mesmo:
// routes/console.php
use Illuminate\Support\Facades\Schedule;
use App\Service\Ged\GedTrashService;
Schedule::call(fn () => GedTrashService::purgeExpired())->daily();
Ver Scheduler para mais sobre Schedule::*.
Configuração
Mesmas preferências do sistema usadas por upload/versionamento (ver
Upload e versionamento) — mad_sys_preference
com prefixo ged_, editadas na aba "GED" do PreferenceForm e lidas por
App\Service\Ged\GedSettingsService:
| Preferência | Default | Efeito |
|---|---|---|
ged_trash_retention_days |
30 |
Dias na lixeira antes de purgeExpired() considerar o documento elegível para remoção definitiva. |
$days = (int) GedSettingsService::get('trash_retention_days', '30');
Na tela
DocumentList tem modos de visualização trash e archived (prop
$viewMode, filtrando Document.status na query do grid) — é de lá que o
usuário restaura ou apaga em definitivo pelas ações da linha. O painel
DocumentDetail também expõe "Mover para lixeira" e "Arquivar" no rodapé,
delegando para GedTrashService::trash()/archive().
Cuidado ao estender: as ações de linha do próprio
DocumentList(onDelete,onRestore,onPermanentDelete,onRestoreFromArchive) reimplementam a lógica inline em vez de chamarGedTrashService— e não são equivalentes em todos os pontos. OonPermanentDeleteda lista apaga só linhas (DocumentTag,Favorite,DocumentVersion,Document): não remove os objetos do storage nem asDocumentPermission/SharedLink/ActivityLog, e não registra atividade.GedTrashService::permanentDelete()faz tudo isso. Se você for estender o fluxo, centralize no service e chame-o dos dois lugares.
Ver também
- Visão geral do GED
- Upload e versionamento
- Soft delete (framework) — o mecanismo genérico do Eloquent/MAD, para contraste com a lixeira própria do GED.