Sari la conținut

DB Schema — Registrul Interdicțiilor (v2)

Acest conținut nu este încă disponibil în limba selectată.

Schema derivată din interdictii.jdl (v2) pentru verificare manuală înainte de jhipster jdl ../interdictii.jdl --force.

Tipuri de date: PostgreSQL (profil prod). H2 (dev) folosește mapări echivalente generate de Liquibase. Toate tabelele moștenesc 4 câmpuri de audit de la AbstractAuditingEntity (omise din DDL-urile de mai jos pentru lizibilitate — vezi §6).

erDiagram
ENTITATE ||--o{ INTERDICTIE : "vizat de"
AUTORITATE_EMITENTA ||--o{ INTERDICTIE : "emite"
TEMEI_LEGAL ||--o{ INTERDICTIE : "fundamentează"
INTERDICTIE ||--o| CONDITIE : "(OneToOne, opțional)"
CONDITIE }o--o| ACT : "îndeplinită prin"
INTERDICTIE ||--o{ ACTIUNE_INTERZISA : "blochează"
INTERDICTIE ||--o{ INTERDICTIE_ACT : ""
ACT ||--o{ INTERDICTIE_ACT : ""
INTERDICTIE ||--o{ EVENIMENT_INTERDICTIE : "audit append-only"
INTERDICTIE ||--o{ NOTIFICARE : ""
ENTITATE {
bigint id PK
varchar categorie "enum CategorieEntitate (9)"
varchar identificator "64 - IDNP/IDNO/VIN/IBAN/..."
varchar nume_afisat "255"
text atribute "jsonb după §6"
}
INTERDICTIE {
bigint id PK
varchar statut "enum 11 valori"
varchar categorie_jurisdictie
varchar categorie_domeniu
varchar durata
varchar intensitate
date start_date
date end_date
text description
bigint entitate_id FK
bigint autoritate_id FK
bigint temei_legal_id FK
bigint conditie_id FK "UNIQUE - OneToOne"
}
ACTIUNE_INTERZISA {
bigint id PK
varchar actiune "enum 32 valori"
text scope "jsonb"
varchar intensitate
bigint interdictie_id FK
}
CONDITIE {
bigint id PK
varchar tip
text descriere
boolean indeplinita
timestamptz data_indeplinire
bigint act_indeplinire_id FK
}
TEMEI_LEGAL {
bigint id PK
varchar act_normativ "255"
varchar articol "64"
varchar alineat "64"
text descriere
}
AUTORITATE_EMITENTA {
bigint id PK
varchar name "255"
varchar familie "enum 4"
varchar nivel "enum 2"
varchar cod_idno "13"
}
ACT {
bigint id PK
varchar tip
varchar numar "64"
date data
varchar storage_uri "512"
varchar hash_sha256 "64"
varchar signature "2048"
text descriere
}
INTERDICTIE_ACT {
bigint id PK
varchar rol "enum 9"
date data_atasare
bigint interdictie_id FK
bigint act_id FK
}
EVENIMENT_INTERDICTIE {
bigint id PK
varchar tip "enum 13"
varchar actor "64"
timestamptz data
text payload "jsonb"
bigint interdictie_id FK
}
NOTIFICARE {
bigint id PK
varchar canal
varchar destinatar "255"
varchar status
timestamptz sent_at
text payload "jsonb"
bigint interdictie_id FK
}
APP_USER {
bigint id PK
varchar username "64 UK"
varchar password "255"
varchar statut
varchar rol
}

2.1 entitate — entitatea polimorfică al interdicției

Section titled “2.1 entitate — entitatea polimorfică al interdicției”
CREATE TABLE entitate (
id BIGINT NOT NULL PRIMARY KEY,
categorie VARCHAR(32) NOT NULL,
identificator VARCHAR(64) NOT NULL,
nume_afisat VARCHAR(255) NOT NULL,
atribute TEXT, -- → jsonb (§6)
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT chk_entitate_categorie CHECK (categorie IN (
'PF','PJ','ADMIN','FUNCTIONAR_PUBLIC','STRAIN',
'MARFA','ARMA','VEHICUL','ACTIV_FINANCIAR'
))
);
-- Unicitate per categorie: același IDNP poate exista ca PF și ADMIN
CREATE UNIQUE INDEX ux_entitate_categorie_identificator
ON entitate (categorie, identificator);
CREATE INDEX ix_entitate_nume_afisat ON entitate (nume_afisat);
CREATE TABLE interdictie (
id BIGINT NOT NULL PRIMARY KEY,
statut VARCHAR(32) NOT NULL,
categorie_jurisdictie VARCHAR(32) NOT NULL,
categorie_domeniu VARCHAR(32) NOT NULL,
durata VARCHAR(32) NOT NULL,
intensitate VARCHAR(32) NOT NULL,
start_date DATE NOT NULL,
end_date DATE,
description TEXT,
entitate_id BIGINT NOT NULL,
autoritate_id BIGINT NOT NULL,
temei_legal_id BIGINT,
conditie_id BIGINT, -- OneToOne
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_interdictie_entitate FOREIGN KEY (entitate_id) REFERENCES entitate(id),
CONSTRAINT fk_interdictie_autoritate FOREIGN KEY (autoritate_id) REFERENCES autoritate_emitenta(id),
CONSTRAINT fk_interdictie_temei_legal FOREIGN KEY (temei_legal_id) REFERENCES temei_legal(id),
CONSTRAINT fk_interdictie_conditie FOREIGN KEY (conditie_id) REFERENCES conditie(id),
CONSTRAINT ux_interdictie_conditie UNIQUE (conditie_id), -- OneToOne enforced
CONSTRAINT chk_interdictie_statut CHECK (statut IN (
'DRAFT','CONDITIONATA','ACTIV','SUSPENDAT','CONTESTATA','PRELUNGITA',
'RIDICATA','REVOCATA','EXECUTATA','EXPIRAT','ANULAT'
)),
CONSTRAINT chk_interdictie_durata CHECK (durata IN (
'TEMPORARA','PERMANENTA','CONDITIONATA','PANA_LA_EXECUTARE'
)),
-- regulă de integritate: durata=CONDITIONATA ⇒ conditie_id NOT NULL
CONSTRAINT chk_interdictie_conditie_req CHECK (
durata <> 'CONDITIONATA' OR conditie_id IS NOT NULL
)
);
CREATE INDEX ix_interdictie_statut ON interdictie (statut);
CREATE INDEX ix_interdictie_entitate ON interdictie (entitate_id);
CREATE INDEX ix_interdictie_autoritate ON interdictie (autoritate_id);
CREATE INDEX ix_interdictie_validitate ON interdictie (start_date, end_date);
CREATE INDEX ix_interdictie_statut_active ON interdictie (statut)
WHERE statut IN ('ACTIV','SUSPENDAT','CONTESTATA','PRELUNGITA','CONDITIONATA');

2.3 actiune_interzisa — acțiunea concretă blocată + scope

Section titled “2.3 actiune_interzisa — acțiunea concretă blocată + scope”
CREATE TABLE actiune_interzisa (
id BIGINT NOT NULL PRIMARY KEY,
actiune VARCHAR(48) NOT NULL,
scope TEXT, -- → jsonb (§6)
intensitate VARCHAR(32),
interdictie_id BIGINT NOT NULL,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_actiune_interdictie FOREIGN KEY (interdictie_id) REFERENCES interdictie(id) ON DELETE CASCADE
);
CREATE INDEX ix_actiune_interdictie ON actiune_interzisa (interdictie_id);
CREATE INDEX ix_actiune_actiune ON actiune_interzisa (actiune);

2.4 conditie — pentru durata=CONDITIONATA

Section titled “2.4 conditie — pentru durata=CONDITIONATA”
CREATE TABLE conditie (
id BIGINT NOT NULL PRIMARY KEY,
tip VARCHAR(32) NOT NULL,
descriere TEXT NOT NULL,
indeplinita BOOLEAN NOT NULL DEFAULT FALSE,
data_indeplinire TIMESTAMPTZ,
act_indeplinire_id BIGINT,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_conditie_act FOREIGN KEY (act_indeplinire_id) REFERENCES act(id),
CONSTRAINT chk_conditie_tip CHECK (tip IN (
'EVENIMENT','INDEPLINIRE_OBLIGATIE','DECIZIE_AUTORITATE','PRAG_TEMPORAL'
)),
-- consistență: indeplinita=true ⇒ data_indeplinire NOT NULL
CONSTRAINT chk_conditie_data CHECK (
indeplinita = FALSE OR data_indeplinire IS NOT NULL
)
);
CREATE INDEX ix_conditie_pending ON conditie (indeplinita) WHERE indeplinita = FALSE;
CREATE TABLE temei_legal (
id BIGINT NOT NULL PRIMARY KEY,
act_normativ VARCHAR(255) NOT NULL,
articol VARCHAR(64) NOT NULL,
alineat VARCHAR(64),
descriere TEXT,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ
);
CREATE UNIQUE INDEX ux_temei_legal ON temei_legal (act_normativ, articol, COALESCE(alineat, ''));
CREATE TABLE autoritate_emitenta (
id BIGINT NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
familie VARCHAR(32) NOT NULL,
nivel VARCHAR(32) NOT NULL,
cod_idno VARCHAR(13),
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT chk_autoritate_familie CHECK (familie IN (
'JUDICIARA','ADMINISTRATIVA','CONTROL','SPECIALA'
)),
CONSTRAINT chk_autoritate_nivel CHECK (nivel IN ('CENTRALA','TERITORIALA'))
);
CREATE INDEX ix_autoritate_familie ON autoritate_emitenta (familie);
CREATE INDEX ix_autoritate_cod_idno ON autoritate_emitenta (cod_idno) WHERE cod_idno IS NOT NULL;
CREATE TABLE act (
id BIGINT NOT NULL PRIMARY KEY,
tip VARCHAR(32) NOT NULL,
numar VARCHAR(64) NOT NULL,
data DATE NOT NULL,
storage_uri VARCHAR(512),
hash_sha256 VARCHAR(64),
signature VARCHAR(2048),
descriere TEXT,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT chk_act_tip CHECK (tip IN (
'HOTARARE','SENTINTA','DECIZIE','PROCES_VERBAL','ORDIN','DISPOZITIE','ALTUL'
))
);
-- hash unic (deduplica fișiere identice)
CREATE UNIQUE INDEX ux_act_hash ON act (hash_sha256) WHERE hash_sha256 IS NOT NULL;
CREATE INDEX ix_act_numar_data ON act (numar, data);

2.8 interdictie_act — join cu rol (M2M atributat)

Section titled “2.8 interdictie_act — join cu rol (M2M atributat)”
CREATE TABLE interdictie_act (
id BIGINT NOT NULL PRIMARY KEY,
rol VARCHAR(32) NOT NULL,
data_atasare DATE NOT NULL,
interdictie_id BIGINT NOT NULL,
act_id BIGINT NOT NULL,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_ia_interdictie FOREIGN KEY (interdictie_id) REFERENCES interdictie(id) ON DELETE CASCADE,
CONSTRAINT fk_ia_act FOREIGN KEY (act_id) REFERENCES act(id),
CONSTRAINT chk_ia_rol CHECK (rol IN (
'EMITERE','MODIFICARE','PRELUNGIRE','SUSPENDARE','REACTIVARE',
'CONTESTARE','RIDICARE','REVOCARE','EXECUTARE'
))
);
-- un act poate avea același rol pentru o interdicție o singură dată
CREATE UNIQUE INDEX ux_ia_interdictie_act_rol ON interdictie_act (interdictie_id, act_id, rol);
CREATE INDEX ix_ia_interdictie ON interdictie_act (interdictie_id);
CREATE INDEX ix_ia_act ON interdictie_act (act_id);

2.9 eveniment_interdictie — event log append-only

Section titled “2.9 eveniment_interdictie — event log append-only”
CREATE TABLE eveniment_interdictie (
id BIGINT NOT NULL PRIMARY KEY,
tip VARCHAR(48) NOT NULL,
actor VARCHAR(64) NOT NULL,
data TIMESTAMPTZ NOT NULL,
payload TEXT, -- → jsonb (§6)
interdictie_id BIGINT NOT NULL,
-- audit (redundant cu actor/data dar păstrat pentru uniformitate)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_ev_interdictie FOREIGN KEY (interdictie_id) REFERENCES interdictie(id),
CONSTRAINT chk_ev_tip CHECK (tip IN (
'CREATA','APROBATA','SUSPENDATA','REACTIVATA','CONTESTATA',
'PRELUNGIRE_CERUTA','PRELUNGITA','REVOCATA','RIDICATA','EXECUTATA',
'EXPIRATA','ANULATA','CONDITIE_INDEPLINITA'
))
);
CREATE INDEX ix_ev_interdictie_data ON eveniment_interdictie (interdictie_id, data DESC);
CREATE INDEX ix_ev_tip ON eveniment_interdictie (tip);
CREATE TABLE notificare (
id BIGINT NOT NULL PRIMARY KEY,
canal VARCHAR(32) NOT NULL,
destinatar VARCHAR(255) NOT NULL,
status VARCHAR(32) NOT NULL,
sent_at TIMESTAMPTZ,
payload TEXT,
interdictie_id BIGINT NOT NULL,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_notif_interdictie FOREIGN KEY (interdictie_id) REFERENCES interdictie(id),
CONSTRAINT chk_notif_canal CHECK (canal IN ('EMAIL','SMS','MCONNECT','WEBHOOK')),
CONSTRAINT chk_notif_status CHECK (status IN ('PENDING','SENT','DELIVERED','FAILED'))
);
CREATE INDEX ix_notif_pending ON notificare (status, sent_at) WHERE status = 'PENDING';
CREATE INDEX ix_notif_interdictie ON notificare (interdictie_id);

2.11 app_user — operator aplicație (≠ jhi_user)

Section titled “2.11 app_user — operator aplicație (≠ jhi_user)”
CREATE TABLE app_user (
id BIGINT NOT NULL PRIMARY KEY,
username VARCHAR(64) NOT NULL,
password VARCHAR(255) NOT NULL,
statut VARCHAR(32) NOT NULL,
rol VARCHAR(32) NOT NULL,
-- audit
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT ux_app_user_username UNIQUE (username),
CONSTRAINT chk_app_user_statut CHECK (statut IN ('ACTIV','INACTIV','BLOCAT')),
CONSTRAINT chk_app_user_rol CHECK (rol IN ('ADMIN','EMITENT'))
);

Generate automat — listate aici doar pentru referință, nu se editează manual.

  • jhi_user — autentificare (login, password_hash, email, activated, lang_key).
  • jhi_authority — name PK ∈ {ROLE_ADMIN, ROLE_USER}.
  • jhi_user_authority — M2M user ↔ authority.
  • jhi_persistent_audit_event, jhi_persistent_audit_evt_data — audit Spring Boot.
  • databasechangelog, databasechangeloglock — Liquibase.

Diferența vs. app_user: jhi_user e pentru autentificare (cine se loghează); app_user e entitate de domeniu (operator de registru) cu rol funcțional și statut administrativ.

-- Căutare frecventă în registru public după IDNP/IDNO (filtrat doar la
-- interdicții care produc efecte juridice).
CREATE INDEX ix_entitate_pf_idnp_active ON entitate (identificator)
WHERE categorie IN ('PF','ADMIN','FUNCTIONAR_PUBLIC');
CREATE INDEX ix_entitate_pj_idno ON entitate (identificator)
WHERE categorie = 'PJ';
-- Pentru raport „interdicții care expiră în următoarele N zile"
CREATE INDEX ix_interdictie_expirare ON interdictie (end_date)
WHERE statut = 'ACTIV' AND end_date IS NOT NULL;
-- Pentru consultare cronologică a evenimentelor unei interdicții
-- (deja acoperit de ix_ev_interdictie_data — verificare).
-- Full-text local (înainte de Elasticsearch sau ca fallback)
CREATE INDEX ix_entitate_nume_fts ON entitate USING gin (to_tsvector('simple', nume_afisat));

5. Trigger append-only pentru eveniment_interdictie

Section titled “5. Trigger append-only pentru eveniment_interdictie”

Cerință din interdictii.jdl (audit/forensic/Legea 133) — la nivel de DB, nu doar repository:

CREATE OR REPLACE FUNCTION eveniment_interdictie_no_modify()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'eveniment_interdictie este append-only (% blocat)', TG_OP;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tr_ev_no_update BEFORE UPDATE ON eveniment_interdictie
FOR EACH ROW EXECUTE FUNCTION eveniment_interdictie_no_modify();
CREATE TRIGGER tr_ev_no_delete BEFORE DELETE ON eveniment_interdictie
FOR EACH ROW EXECUTE FUNCTION eveniment_interdictie_no_modify();

Notă: Liquibase rulează cu utilizator privilegiat; pentru migrări care chiar trebuie să modifice istoricul (caz extrem), trigger-ul se dezactivează temporar într-un changelog cu runOnChange=false și apoi se reactivează.

Toate entitățile au (de la AbstractAuditingEntity + SpringSecurityAuditorAware):

ColoanăTipPopulare
created_byVARCHAR(50) NOT NULLlogin al utilizatorului la INSERT
created_dateTIMESTAMPTZInstant.now() la INSERT
last_modified_byVARCHAR(50)login al utilizatorului la UPDATE
last_modified_dateTIMESTAMPTZInstant.now() la UPDATE

JDL nu are tip jsonb; câmpurile TextBlob sunt generate ca TEXT + @Lob. După regenerare, adaugă un changelog Liquibase manual:

<!-- src/main/resources/config/liquibase/changelog/<timestamp>_jsonb_conversion.xml -->
<changeSet id="jsonb-conversion-001" author="manual">
<sql>
ALTER TABLE entitate ALTER COLUMN atribute TYPE jsonb USING atribute::jsonb;
ALTER TABLE actiune_interzisa ALTER COLUMN scope TYPE jsonb USING scope::jsonb;
ALTER TABLE eveniment_interdictie ALTER COLUMN payload TYPE jsonb USING payload::jsonb;
ALTER TABLE notificare ALTER COLUMN payload TYPE jsonb USING payload::jsonb;
</sql>
<rollback>
ALTER TABLE notificare ALTER COLUMN payload TYPE text USING payload::text;
ALTER TABLE eveniment_interdictie ALTER COLUMN payload TYPE text USING payload::text;
ALTER TABLE actiune_interzisa ALTER COLUMN scope TYPE text USING scope::text;
ALTER TABLE entitate ALTER COLUMN atribute TYPE text USING atribute::text;
</rollback>
</changeSet>

Apoi, în cod Java, adaugă pe câmpurile corespunzătoare:

@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "atribute", columnDefinition = "jsonb")
private String atribute;

(Sau folosește io.hypersistence:hypersistence-utils-hibernate-63 cu @Type(JsonType.class) și un tip Map<String, Object> în loc de String.)

Indici GIN pe câmpurile jsonb pentru căutare (ex. „interdicții pe vehicule din anul X”):

CREATE INDEX ix_entitate_atribute_gin ON entitate USING gin (atribute jsonb_path_ops);
CREATE INDEX ix_actiune_scope_gin ON actiune_interzisa USING gin (scope jsonb_path_ops);
  • Unicitate entitate (categorie, identificator) — corect, sau ai nevoie de unicitate globală pe identificator? (IDNP repetat în PF + ADMIN e legitim sau nu?)
  • interdictie.conditie_id UNIQUE — păstrăm OneToOne sau lăsăm mai multe condiții per interdicție? (atunci → mută FK pe conditie și permite ManyToMany cu logică AND/OR).
  • ON DELETE CASCADE pe actiune_interzisa și interdictie_act — cascadă e OK pentru acțiuni (parte integrantă) dar pentru eveniment_interdictie am evitat-o (audit nu se șterge niciodată). Confirmă.
  • Soft delete vs. hard delete pentru interdictie — pentru registru juridic, hard delete e probabil interzis (păstrăm istoricul prin ANULAT/REVOCATA). Dacă da, adaugă coloană deleted_at TIMESTAMPTZ și o vizualizare interdictie_active.
  • Retenție evenimente — eveniment_interdictie crește nelimitat. Politica de arhivare (partitioning pe data)?
  • Index pe actor în eveniment_interdictie — pentru raport „ce a făcut user X în perioada Y”?
  • Constraint cross-table durata↔conditie — chk_interdictie_conditie_req asigură că CONDITIONATA ⇒ conditie_id. Verifică dacă reciproca e dorită: conditie_id NOT NULL ⇒ durata = CONDITIONATA?
  • statut = EXPIRAT automat — job @Scheduled zilnic care setează statut = EXPIRAT la interdicțiile cu end_date < CURRENT_DATE și generează EvenimentInterdictie(tip=EXPIRATA, actor='SYSTEM'). Asta este logică aplicație, dar verifică indexul ix_interdictie_expirare.
  • autoritate_emitenta.cod_idno — autoritățile guvernamentale au IDNO? Dacă nu, transformă în cod_extern mai generic.
  • Lungimi VARCHAR pentru enum-uri — am ales 32/48; verifică dacă cea mai lungă valoare încape (OCUPARE_FUNCTIE_CONDUCERE = 24, CONDITIE_INDEPLINITA = 21 — OK pentru 32; INDEPLINIRE_OBLIGATIE = 21).