Sistemato magazzino

This commit is contained in:
Lorenzo Sanesi
2026-08-01 03:12:23 +02:00
parent de921abcd6
commit 0fdf395733
128 changed files with 9307 additions and 3076 deletions
+143 -36
View File
@@ -23,6 +23,68 @@ enum StatoMagazzinoVoce {
@@map("stato_magazzino_voce")
}
enum StatoLista {
bozza
privato
gruppo
pubblico
@@map("stato_lista")
}
enum StatoModerazioneLista {
proposto
approvato
rifiutato
@@map("stato_moderazione_lista")
}
enum StatoCategoria {
confermata
da_approvare
@@map("stato_categoria")
}
enum TipoNotifica {
MATERIALE_PROPOSTO
CATEGORIA_PROPOSTA
TIPO_EVENTO_PROPOSTO
LISTA_PROPOSTA
@@map("tipo_notifica")
}
// Coda di notifiche di moderazione (nuove proposte di materiale/categoria/tipo
// evento/lista in attesa di revisione): visibile solo a chi ha ruolo 'moderatore',
// non ci sono ancora notifiche personali nel dominio magazzino.
model Notifica {
id Int @id @default(autoincrement())
tipo TipoNotifica
messaggio String
link String?
letta Boolean @default(false)
dataCreazione DateTime @default(now()) @map("data_creazione")
@@map("notifica")
}
// Tassonomia delle categorie di materiale, gestita dal moderatore (CRUD diretto,
// sempre creata "confermata") con un flusso di proposta aperto a qualsiasi org
// autenticata (crea in "da_approvare", poi approvata/rifiutata dal moderatore).
// Materiale.categoria resta per ora un campo stringa libero, non collegato a questa
// tabella: introdurla come FK è un passo successivo, fuori scope qui.
model Categoria {
id String @id @default(uuid())
nome String
stato StatoCategoria @default(confermata)
creatoDaOrgId String? @map("creato_da_org_id")
creatoIl DateTime @default(now()) @map("creato_il")
@@map("categoria")
}
model Materiale {
id String @id @default(uuid())
nome String
@@ -32,56 +94,67 @@ model Materiale {
propostoDaOrgId String @map("proposto_da_org_id")
creatoIl DateTime @default(now()) @map("creato_il")
listaModelloVoci ListaModelloVoce[]
listaVoci ListaVoce[]
magazzinoVoci MagazzinoVoce[]
eventoCheck EventoCheck[]
listaVoci ListaVoce[]
magazzinoVoci MagazzinoVoce[]
eventoCheck EventoCheck[]
@@map("materiale")
}
model TipoEvento {
id String @id @default(uuid())
nome String
id String @id @default(uuid())
nome String
// Stessa semantica/enum di Categoria.stato: un tipo evento creato dal moderatore
// nasce 'confermata', uno proposto da un'organizzazione nasce 'da_approvare'.
stato StatoCategoria @default(confermata)
creatoDaOrgId String? @map("creato_da_org_id")
creatoIl DateTime @default(now()) @map("creato_il")
listeModello ListaModello[]
liste Lista[]
@@map("tipo_evento")
}
model ListaModello {
id String @id @default(uuid())
nome String
tipoEventoId String @map("tipo_evento_id")
pubblica Boolean @default(true)
tipoEvento TipoEvento @relation(fields: [tipoEventoId], references: [id])
voci ListaModelloVoce[]
@@map("lista_modello")
}
model ListaModelloVoce {
listaModelloId String @map("lista_modello_id")
materialeId String @map("materiale_id")
quantita Int
listaModello ListaModello @relation(fields: [listaModelloId], references: [id])
materiale Materiale @relation(fields: [materialeId], references: [id])
@@id([listaModelloId, materialeId])
@@map("lista_modello_voce")
}
model Lista {
id String @id @default(uuid())
nome String
orgId String @map("org_id")
creataIl DateTime @default(now()) @map("creata_il")
id String @id @default(uuid())
nome String
// Valorizzato solo per le liste in stato 'gruppo' (condivise con l'organizzazione):
// le liste 'bozza'/'privato'/'pubblico' sono personali e non hanno alcuna org, anche
// per un utente che ha un'organizzazione attiva sul token al momento della creazione.
orgId String? @map("org_id")
// Nullable perché le liste create prima di questo campo non hanno un autore noto;
// da qui in poi è sempre valorizzato da req.auth.userId alla creazione.
creataDaUserId String? @map("creata_da_user_id")
stato StatoLista @default(bozza)
creataIl DateTime @default(now()) @map("creata_il")
// Moderazione, ortogonale a `stato`: nullo per bozza/privato/gruppo, valorizzato
// solo quando stato = pubblico (proposto/approvato/rifiutato, vedi liste.service.ts).
statoModerazione StatoModerazioneLista? @map("stato_moderazione")
// Ex campo obbligatorio di ListaModello, ora opzionale su tutte le liste: serve solo
// a filtrare il catalogo pubblico per tipo evento.
tipoEventoId String? @map("tipo_evento_id")
tipoEvento TipoEvento? @relation(fields: [tipoEventoId], references: [id])
// Lineage self-referenziato: da quale lista pubblica approvata è stata forkata questa
// (fork = "usa come base"). Best-effort/storico: cancellare l'origine non deve
// bloccare né trascinare i figli, quindi SetNull.
parentId String? @map("parent_id")
parent Lista? @relation("ListaParent", fields: [parentId], references: [id], onDelete: SetNull)
figlie Lista[] @relation("ListaParent")
voci ListaVoce[]
eventi Evento[]
// Sotto-liste agganciate a questa lista personale: un solo livello di nesting
// (una sotto-lista non può avere a sua volta sotto-liste), validato in liste.service.ts.
sottoListe ListaSottoLista[] @relation("SottoListePadre")
usataComeSottoListaIn ListaSottoLista[] @relation("SottoListaFiglia")
@@index([stato, statoModerazione])
@@index([tipoEventoId])
@@index([parentId])
@@map("lista")
}
@@ -97,6 +170,35 @@ model ListaVoce {
@@map("lista_voce")
}
model ListaSottoLista {
listaId String @map("lista_id")
sottoListaId String @map("sotto_lista_id")
lista Lista @relation("SottoListePadre", fields: [listaId], references: [id])
sottoLista Lista @relation("SottoListaFiglia", fields: [sottoListaId], references: [id])
@@id([listaId, sottoListaId])
@@map("lista_sotto_lista")
}
model GruppoMagazzino {
id String @id @default(uuid())
orgId String @map("org_id")
nome String
// Un solo livello di nesting (uno scaffale contiene kit, un kit non contiene
// a sua volta altri gruppi), validato in gruppiMagazzino.service.ts.
parentId String? @map("parent_id")
creatoIl DateTime @default(now()) @map("creato_il")
parent GruppoMagazzino? @relation("GruppoMagazzinoParent", fields: [parentId], references: [id], onDelete: SetNull)
figli GruppoMagazzino[] @relation("GruppoMagazzinoParent")
voci MagazzinoVoce[]
@@index([orgId])
@@index([parentId])
@@map("gruppo_magazzino")
}
model MagazzinoVoce {
id String @id @default(uuid())
orgId String @map("org_id")
@@ -105,9 +207,14 @@ model MagazzinoVoce {
stato StatoMagazzinoVoce
posizione String?
note String?
// Nullo finché la giacenza non viene organizzata in un gruppo (scaffale/kit):
// eliminare il gruppo non elimina la voce, la riporta solo tra le "non organizzate".
gruppoId String? @map("gruppo_id")
materiale Materiale @relation(fields: [materialeId], references: [id])
materiale Materiale @relation(fields: [materialeId], references: [id])
gruppo GruppoMagazzino? @relation(fields: [gruppoId], references: [id], onDelete: SetNull)
@@index([gruppoId])
@@map("magazzino_voce")
}