feat(dict): soft-delete с recovery (Phase 1)
This commit is contained in:
+29
@@ -112,6 +112,22 @@ public class DictionaryDefinition {
|
||||
@Column(name = "version")
|
||||
private Long version;
|
||||
|
||||
/**
|
||||
* Soft-delete timestamp. NULL = active dictionary. Non-null = deleted (set
|
||||
* by SchemaDeletionApproval after 2nd admin approve). Records/drafts
|
||||
* остаются физически в БД (recovery возможен через restore()).
|
||||
*
|
||||
* <p>Permanent purge (drop row + cascade на child tables) — отдельный
|
||||
* admin-only endpoint после approval+30d retention. По умолчанию
|
||||
* list/findByName excludeает deleted, restore сбрасывает в NULL.
|
||||
*/
|
||||
@Column(name = "deleted_at")
|
||||
private OffsetDateTime deletedAt;
|
||||
|
||||
/** Admin sub claim that approved the deletion (audit trail). */
|
||||
@Column(name = "deleted_by", length = 64)
|
||||
private String deletedBy;
|
||||
|
||||
protected DictionaryDefinition() {}
|
||||
|
||||
public DictionaryDefinition(UUID id, String name, DataScope scope, JsonNode schemaJson) {
|
||||
@@ -139,6 +155,19 @@ public class DictionaryDefinition {
|
||||
public String getCreatedBy() { return createdBy; }
|
||||
public String getUpdatedBy() { return updatedBy; }
|
||||
public Long getVersion() { return version; }
|
||||
public OffsetDateTime getDeletedAt() { return deletedAt; }
|
||||
public String getDeletedBy() { return deletedBy; }
|
||||
public boolean isDeleted() { return deletedAt != null; }
|
||||
|
||||
public void markDeleted(String approverSub) {
|
||||
this.deletedAt = OffsetDateTime.now();
|
||||
this.deletedBy = approverSub;
|
||||
}
|
||||
|
||||
public void restore() {
|
||||
this.deletedAt = null;
|
||||
this.deletedBy = null;
|
||||
}
|
||||
|
||||
public void setDisplayName(String v) { this.displayName = v; }
|
||||
public void setDescription(String v) { this.description = v; }
|
||||
|
||||
+27
-1
@@ -9,7 +9,33 @@ import java.util.UUID;
|
||||
|
||||
public interface DictionaryDefinitionRepository extends JpaRepository<DictionaryDefinition, UUID> {
|
||||
|
||||
Optional<DictionaryDefinition> findByName(String name);
|
||||
/**
|
||||
* Active (non-deleted) dict by name. Soft-deleted (deleted_at IS NOT NULL)
|
||||
* excluded — выглядит как 404 для regular consumers. Admin Корзина UI
|
||||
* использует {@link #findByNameIncludingDeleted} для restore flow.
|
||||
*/
|
||||
@org.springframework.data.jpa.repository.Query(
|
||||
"SELECT d FROM DictionaryDefinition d WHERE d.name = :name AND d.deletedAt IS NULL")
|
||||
Optional<DictionaryDefinition> findByName(@org.springframework.data.repository.query.Param("name") String name);
|
||||
|
||||
/** Includes soft-deleted — для admin restore/purge flows. */
|
||||
@org.springframework.data.jpa.repository.Query(
|
||||
"SELECT d FROM DictionaryDefinition d WHERE d.name = :name")
|
||||
Optional<DictionaryDefinition> findByNameIncludingDeleted(@org.springframework.data.repository.query.Param("name") String name);
|
||||
|
||||
/**
|
||||
* All active (non-deleted) dictionaries. JpaRepository.findAll() возвращает
|
||||
* ВСЕ rows (включая deleted) — controllers должны использовать этот метод
|
||||
* для regular list.
|
||||
*/
|
||||
@org.springframework.data.jpa.repository.Query(
|
||||
"SELECT d FROM DictionaryDefinition d WHERE d.deletedAt IS NULL")
|
||||
List<DictionaryDefinition> findAllActive();
|
||||
|
||||
/** Soft-deleted only — admin Корзина page. */
|
||||
@org.springframework.data.jpa.repository.Query(
|
||||
"SELECT d FROM DictionaryDefinition d WHERE d.deletedAt IS NOT NULL ORDER BY d.deletedAt DESC")
|
||||
List<DictionaryDefinition> findAllDeleted();
|
||||
|
||||
List<DictionaryDefinition> findByBundle(String bundle);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user