commit per adattare il data model alla web app Academy aziendale
This commit is contained in:
parent
d5bd50ef39
commit
3e94fdb6d9
9 changed files with 340 additions and 112 deletions
|
|
@ -1,28 +1,94 @@
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Schema tabelle – esameits24-26
|
-- Schema tabelle – esameits24-26 (Academy aziendale)
|
||||||
-- Allineato alle API: /utenti,
|
-- Allineato alle API: /utenti, /corsi, /assegnazioni-corsi, /statistiche
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
USE `esameits24-26`;
|
USE `esameits24-26`;
|
||||||
|
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
-- Tabella UTENTI
|
-- Tabella UTENTE
|
||||||
-- Ruoli: Operatore | Amministratore
|
-- Ruoli: Dipendente | Referente Academy
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS utente (
|
CREATE TABLE IF NOT EXISTS utente (
|
||||||
UtenteID INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
UtenteID INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
Nome VARCHAR(100) NOT NULL,
|
Nome VARCHAR(100) NOT NULL,
|
||||||
Cognome VARCHAR(100) NOT NULL,
|
Cognome VARCHAR(100) NOT NULL,
|
||||||
Email VARCHAR(255) NOT NULL,
|
Email VARCHAR(255) NOT NULL,
|
||||||
PasswordHash VARCHAR(255) NOT NULL COMMENT 'Password salvata come hash (es. bcrypt)',
|
PasswordHash VARCHAR(255) NOT NULL COMMENT 'Password salvata come hash (es. bcrypt)',
|
||||||
Ruolo ENUM("Amministratore", "Operatore") NOT NULL DEFAULT "Operatore",
|
Ruolo ENUM('Dipendente', 'Referente Academy') NOT NULL DEFAULT 'Dipendente',
|
||||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
PRIMARY KEY (UtenteID),
|
PRIMARY KEY (UtenteID),
|
||||||
UNIQUE KEY uk_utente_email (Email)
|
UNIQUE KEY uk_utente_email (Email),
|
||||||
|
KEY idx_utente_ruolo (Ruolo)
|
||||||
) ENGINE=InnoDB
|
) ENGINE=InnoDB
|
||||||
DEFAULT CHARSET=utf8mb4
|
DEFAULT CHARSET=utf8mb4
|
||||||
COLLATE=utf8mb4_unicode_ci;
|
COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Tabella CORSO (catalogo Academy)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS corso (
|
||||||
|
CorsoID INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
Titolo VARCHAR(200) NOT NULL,
|
||||||
|
Descrizione TEXT NULL,
|
||||||
|
Categoria VARCHAR(100) NOT NULL,
|
||||||
|
DurataOre SMALLINT UNSIGNED NOT NULL,
|
||||||
|
Obbligatorio TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
Attivo TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
PRIMARY KEY (CorsoID),
|
||||||
|
KEY idx_corso_categoria (Categoria),
|
||||||
|
KEY idx_corso_attivo (Attivo),
|
||||||
|
CONSTRAINT chk_corso_durata_ore CHECK (DurataOre > 0)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Tabella ASSEGNAZIONE_CORSO
|
||||||
|
-- Stati: Assegnato | Completato | Scaduto | Annullato
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS assegnazione_corso (
|
||||||
|
AssegnazioneID INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
CorsoID INT UNSIGNED NOT NULL,
|
||||||
|
DipendenteID INT UNSIGNED NOT NULL,
|
||||||
|
DataAssegnazione DATE NOT NULL,
|
||||||
|
DataScadenza DATE NOT NULL,
|
||||||
|
Stato ENUM('Assegnato', 'Completato', 'Scaduto', 'Annullato') NOT NULL DEFAULT 'Assegnato',
|
||||||
|
DataCompletamento DATE NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
PRIMARY KEY (AssegnazioneID),
|
||||||
|
KEY idx_assegnazione_corso (CorsoID),
|
||||||
|
KEY idx_assegnazione_dipendente (DipendenteID),
|
||||||
|
KEY idx_assegnazione_stato (Stato),
|
||||||
|
KEY idx_assegnazione_data_assegnazione (DataAssegnazione),
|
||||||
|
KEY idx_assegnazione_data_scadenza (DataScadenza),
|
||||||
|
|
||||||
|
CONSTRAINT fk_assegnazione_corso
|
||||||
|
FOREIGN KEY (CorsoID) REFERENCES corso (CorsoID)
|
||||||
|
ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
CONSTRAINT fk_assegnazione_dipendente
|
||||||
|
FOREIGN KEY (DipendenteID) REFERENCES utente (UtenteID)
|
||||||
|
ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
CONSTRAINT chk_assegnazione_scadenza
|
||||||
|
CHECK (DataScadenza >= DataAssegnazione),
|
||||||
|
|
||||||
|
CONSTRAINT chk_assegnazione_completamento
|
||||||
|
CHECK (DataCompletamento IS NULL OR DataCompletamento >= DataAssegnazione),
|
||||||
|
|
||||||
|
CONSTRAINT chk_assegnazione_completato
|
||||||
|
CHECK (
|
||||||
|
(Stato = 'Completato' AND DataCompletamento IS NOT NULL)
|
||||||
|
OR (Stato <> 'Completato' AND DataCompletamento IS NULL)
|
||||||
|
)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Dati iniziali di test – esameits24-26
|
-- Dati iniziali di test – esameits24-26 (Academy aziendale)
|
||||||
-- Password per tutti gli utenti: Password123!
|
-- Password per tutti gli utenti: Password123!
|
||||||
-- 1 Andrea Villari – admin@example.it (amministratore)
|
--
|
||||||
-- 2 Maria Bianchi – maria.bianchi@example.it (operatore)
|
-- 1 Andrea Villari – admin@academy.it (Referente Academy)
|
||||||
-- 3 Luca Conti – luca.conti@example.it (operatore)
|
-- 2 Maria Bianchi – maria.bianchi@academy.it (Dipendente)
|
||||||
|
-- 3 Luca Conti – luca.conti@academy.it (Dipendente)
|
||||||
|
-- 4 Elena Rossi – elena.rossi@academy.it (Dipendente)
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
USE `esameits24-26`;
|
USE `esameits24-26`;
|
||||||
|
|
@ -12,6 +14,82 @@ USE `esameits24-26`;
|
||||||
-- Utenti
|
-- Utenti
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
INSERT INTO utente (Nome, Cognome, Email, PasswordHash, Ruolo) VALUES
|
INSERT INTO utente (Nome, Cognome, Email, PasswordHash, Ruolo) VALUES
|
||||||
('Andrea', 'Villari', 'admin@example.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Amministratore'),
|
('Andrea', 'Villari', 'admin@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Referente Academy'),
|
||||||
('Maria', 'Bianchi', 'maria.bianchi@example.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Operatore'),
|
('Maria', 'Bianchi', 'maria.bianchi@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Dipendente'),
|
||||||
('Luca', 'Conti', 'luca.conti@example.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Operatore');
|
('Luca', 'Conti', 'luca.conti@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Dipendente'),
|
||||||
|
('Elena', 'Rossi', 'elena.rossi@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Dipendente');
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Catalogo corsi
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
INSERT INTO corso (Titolo, Descrizione, Categoria, DurataOre, Obbligatorio, Attivo) VALUES
|
||||||
|
(
|
||||||
|
'Sicurezza sul lavoro',
|
||||||
|
'Norme base su rischi, DPI e procedure di emergenza in azienda.',
|
||||||
|
'Sicurezza',
|
||||||
|
8,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'GDPR e privacy',
|
||||||
|
'Trattamento dei dati personali, diritti degli interessati e documentazione privacy.',
|
||||||
|
'Compliance',
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Comunicazione efficace',
|
||||||
|
'Tecniche di ascolto attivo, feedback e gestione dei conflitti nel team.',
|
||||||
|
'Soft Skills',
|
||||||
|
6,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Excel avanzato',
|
||||||
|
'Tabelle pivot, funzioni avanzate e automazioni per report aziendali.',
|
||||||
|
'IT',
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Primo soccorso (edizione 2023)',
|
||||||
|
'Corso storico non più disponibile per nuove assegnazioni.',
|
||||||
|
'Sicurezza',
|
||||||
|
4,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Assegnazioni corsi
|
||||||
|
-- UtenteID: 2 Maria, 3 Luca, 4 Elena
|
||||||
|
-- CorsoID: 1 Sicurezza, 2 GDPR, 3 Comunicazione, 4 Excel, 5 Primo soccorso
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
INSERT INTO assegnazione_corso (
|
||||||
|
CorsoID,
|
||||||
|
DipendenteID,
|
||||||
|
DataAssegnazione,
|
||||||
|
DataScadenza,
|
||||||
|
Stato,
|
||||||
|
DataCompletamento
|
||||||
|
) VALUES
|
||||||
|
-- Maria: mix di stati per test filtri e statistiche maggio 2026
|
||||||
|
(1, 2, '2026-05-01', '2026-06-30', 'Assegnato', NULL),
|
||||||
|
(2, 2, '2026-04-10', '2026-05-20', 'Completato', '2026-05-12'),
|
||||||
|
(4, 2, '2026-01-15', '2026-03-01', 'Scaduto', NULL),
|
||||||
|
(3, 2, '2026-03-01', '2026-04-30', 'Annullato', NULL),
|
||||||
|
|
||||||
|
-- Luca: completamenti e corsi in corso
|
||||||
|
(1, 3, '2026-05-05', '2026-06-15', 'Completato', '2026-05-18'),
|
||||||
|
(2, 3, '2026-05-08', '2026-06-30', 'Assegnato', NULL),
|
||||||
|
(3, 3, '2026-02-01', '2026-03-15', 'Scaduto', NULL),
|
||||||
|
|
||||||
|
-- Elena: dati aggiuntivi per riepiloghi per categoria
|
||||||
|
(1, 4, '2026-05-10', '2026-07-01', 'Completato', '2026-05-25'),
|
||||||
|
(2, 4, '2026-05-12', '2026-06-20', 'Completato', '2026-05-28'),
|
||||||
|
(4, 4, '2026-05-15', '2026-07-15', 'Assegnato', NULL),
|
||||||
|
(3, 4, '2026-04-01', '2026-05-10', 'Completato', '2026-05-05');
|
||||||
|
|
|
||||||
68
scripts/apply-academy-schema.sql
Normal file
68
scripts/apply-academy-schema.sql
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
-- ============================================================
|
||||||
|
-- Migrazione manuale per database già inizializzati
|
||||||
|
-- Eseguire se il volume MySQL esisteva prima dell'aggiornamento schema Academy.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
USE `esameits24-26`;
|
||||||
|
|
||||||
|
-- Allinea enum ruoli utente (se la tabella esisteva con valori precedenti)
|
||||||
|
ALTER TABLE utente
|
||||||
|
MODIFY Ruolo ENUM('Dipendente', 'Referente Academy') NOT NULL DEFAULT 'Dipendente';
|
||||||
|
|
||||||
|
UPDATE utente SET Ruolo = 'Referente Academy' WHERE Ruolo IN ('Amministratore', 'Refernete Academy');
|
||||||
|
UPDATE utente SET Ruolo = 'Dipendente' WHERE Ruolo = 'Operatore';
|
||||||
|
|
||||||
|
-- Catalogo corsi
|
||||||
|
CREATE TABLE IF NOT EXISTS corso (
|
||||||
|
CorsoID INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
Titolo VARCHAR(200) NOT NULL,
|
||||||
|
Descrizione TEXT NULL,
|
||||||
|
Categoria VARCHAR(100) NOT NULL,
|
||||||
|
DurataOre SMALLINT UNSIGNED NOT NULL,
|
||||||
|
Obbligatorio TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
Attivo TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (CorsoID),
|
||||||
|
KEY idx_corso_categoria (Categoria),
|
||||||
|
KEY idx_corso_attivo (Attivo),
|
||||||
|
CONSTRAINT chk_corso_durata_ore CHECK (DurataOre > 0)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Assegnazioni corsi
|
||||||
|
CREATE TABLE IF NOT EXISTS assegnazione_corso (
|
||||||
|
AssegnazioneID INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
CorsoID INT UNSIGNED NOT NULL,
|
||||||
|
DipendenteID INT UNSIGNED NOT NULL,
|
||||||
|
DataAssegnazione DATE NOT NULL,
|
||||||
|
DataScadenza DATE NOT NULL,
|
||||||
|
Stato ENUM('Assegnato', 'Completato', 'Scaduto', 'Annullato') NOT NULL DEFAULT 'Assegnato',
|
||||||
|
DataCompletamento DATE NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (AssegnazioneID),
|
||||||
|
KEY idx_assegnazione_corso (CorsoID),
|
||||||
|
KEY idx_assegnazione_dipendente (DipendenteID),
|
||||||
|
KEY idx_assegnazione_stato (Stato),
|
||||||
|
KEY idx_assegnazione_data_assegnazione (DataAssegnazione),
|
||||||
|
KEY idx_assegnazione_data_scadenza (DataScadenza),
|
||||||
|
CONSTRAINT fk_assegnazione_corso
|
||||||
|
FOREIGN KEY (CorsoID) REFERENCES corso (CorsoID)
|
||||||
|
ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||||
|
CONSTRAINT fk_assegnazione_dipendente
|
||||||
|
FOREIGN KEY (DipendenteID) REFERENCES utente (UtenteID)
|
||||||
|
ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||||
|
CONSTRAINT chk_assegnazione_scadenza
|
||||||
|
CHECK (DataScadenza >= DataAssegnazione),
|
||||||
|
CONSTRAINT chk_assegnazione_completamento
|
||||||
|
CHECK (DataCompletamento IS NULL OR DataCompletamento >= DataAssegnazione),
|
||||||
|
CONSTRAINT chk_assegnazione_completato
|
||||||
|
CHECK (
|
||||||
|
(Stato = 'Completato' AND DataCompletamento IS NOT NULL)
|
||||||
|
OR (Stato <> 'Completato' AND DataCompletamento IS NULL)
|
||||||
|
)
|
||||||
|
) ENGINE=InnoDB
|
||||||
|
DEFAULT CHARSET=utf8mb4
|
||||||
|
COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
USE `esameits24-26`;
|
USE `esameits24-26`;
|
||||||
|
|
||||||
INSERT IGNORE INTO utente (Nome, Cognome, Email, PasswordHash, Ruolo) VALUES
|
INSERT IGNORE INTO utente (Nome, Cognome, Email, PasswordHash, Ruolo) VALUES
|
||||||
('Andrea', 'Villari', 'admin@example.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Amministratore'),
|
('Andrea', 'Villari', 'admin@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Referente Academy'),
|
||||||
('Maria', 'Bianchi', 'maria.bianchi@example.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Operatore'),
|
('Maria', 'Bianchi', 'maria.bianchi@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Dipendente'),
|
||||||
('Luca', 'Conti', 'luca.conti@example.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Operatore');
|
('Luca', 'Conti', 'luca.conti@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Dipendente'),
|
||||||
|
('Elena', 'Rossi', 'elena.rossi@academy.it', '$2b$10$7Z80qZcDydZCdLrOcaH/VOa9knKWjQVyyg8MtEgBAvgfNMsiLEZze', 'Dipendente');
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,6 @@ export function mapDbError(error: unknown): DbErrorResponse | null {
|
||||||
const message = err.sqlMessage ?? err.message ?? "";
|
const message = err.sqlMessage ?? err.message ?? "";
|
||||||
|
|
||||||
if (err.code === "ER_DUP_ENTRY") {
|
if (err.code === "ER_DUP_ENTRY") {
|
||||||
if (message.includes("uk_cliente_email")) {
|
|
||||||
return {
|
|
||||||
status: 409,
|
|
||||||
error: "Email già registrata",
|
|
||||||
fields: { email: "Email già registrata" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (message.includes("uk_utente_email")) {
|
if (message.includes("uk_utente_email")) {
|
||||||
return {
|
return {
|
||||||
status: 409,
|
status: 409,
|
||||||
|
|
@ -31,39 +24,36 @@ export function mapDbError(error: unknown): DbErrorResponse | null {
|
||||||
fields: { email: "Email già registrata" },
|
fields: { email: "Email già registrata" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (message.includes("uk_chiaveConsegna")) {
|
|
||||||
return {
|
|
||||||
status: 409,
|
|
||||||
error: "Chiave consegna già registrata",
|
|
||||||
fields: { chiaveConsegna: "Chiave consegna già registrata" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { status: 409, error: "Valore duplicato" };
|
return { status: 409, error: "Valore duplicato" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.code === "ER_SIGNAL_EXCEPTION" || err.errno === 1644) {
|
|
||||||
if (message.includes("DataRitiro")) {
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
error: "Data ritiro non valida",
|
|
||||||
fields: { dataRitiro: "Data ritiro non può essere anteriore alla data odierna" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (message.includes("DataConsegna")) {
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
error: "Data consegna non valida",
|
|
||||||
fields: { dataConsegna: "Data consegna non può essere anteriore alla data odierna" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === "ER_CHECK_CONSTRAINT_VIOLATED") {
|
if (err.code === "ER_CHECK_CONSTRAINT_VIOLATED") {
|
||||||
if (message.includes("chk_consegna_almeno_una_data")) {
|
if (message.includes("chk_corso_durata_ore")) {
|
||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
error: "Inserire almeno una data di ritiro o di consegna",
|
error: "Durata corso non valida",
|
||||||
fields: { _form: "Inserire almeno una data di ritiro o di consegna" },
|
fields: { durataOre: "La durata prevista deve essere maggiore di zero" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (message.includes("chk_assegnazione_scadenza")) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
error: "Date assegnazione non valide",
|
||||||
|
fields: { dataScadenza: "La data di scadenza non può essere precedente alla data di assegnazione" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (message.includes("chk_assegnazione_completamento")) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
error: "Data completamento non valida",
|
||||||
|
fields: { dataCompletamento: "La data di completamento non può essere precedente alla data di assegnazione" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (message.includes("chk_assegnazione_completato")) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
error: "Stato assegnazione non coerente",
|
||||||
|
fields: { stato: "Lo stato Completato richiede una data di completamento" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,16 +61,23 @@ export function mapDbError(error: unknown): DbErrorResponse | null {
|
||||||
if (err.code === "ER_ROW_IS_REFERENCED_2") {
|
if (err.code === "ER_ROW_IS_REFERENCED_2") {
|
||||||
return {
|
return {
|
||||||
status: 409,
|
status: 409,
|
||||||
error: "Impossibile eliminare: esistono riferimenti collegati",
|
error: "Impossibile eliminare: esistono assegnazioni collegate al corso",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.code === "ER_NO_REFERENCED_ROW_2") {
|
if (err.code === "ER_NO_REFERENCED_ROW_2") {
|
||||||
if (message.includes("fk_consegna_cliente")) {
|
if (message.includes("fk_assegnazione_corso")) {
|
||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
error: "Cliente non valido",
|
error: "Corso non valido",
|
||||||
fields: { clienteId: "Cliente non trovato" },
|
fields: { corsoId: "Corso non trovato" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (message.includes("fk_assegnazione_dipendente")) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
error: "Dipendente non valido",
|
||||||
|
fields: { dipendenteId: "Dipendente non trovato" },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { status: 400, error: "Riferimento non valido" };
|
return { status: 400, error: "Riferimento non valido" };
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
|
//====================================================
|
||||||
|
// File: auth.ts
|
||||||
|
// File middleware per gestionre dell'autenticazione
|
||||||
|
// e le autorizzazini dei dipendenti/referenti academy
|
||||||
|
// author: "villari.andrea@libero.it"
|
||||||
|
// version: "1.0.0 2026-07-14"
|
||||||
|
//====================================================
|
||||||
import { getSession } from "@auth/express";
|
import { getSession } from "@auth/express";
|
||||||
import type { NextFunction, Request, Response } from "express";
|
import type { NextFunction, Request, Response } from "express";
|
||||||
import { authConfig } from "../auth/config.js";
|
import { authConfig } from "../auth/config.js";
|
||||||
|
|
@ -18,14 +25,17 @@ export async function requireAuth(req: Request, res: Response, next: NextFunctio
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requireAmministratore(req: Request, res: Response, next: NextFunction) {
|
export function requireReferenteAcademy(req: Request, res: Response, next: NextFunction) {
|
||||||
if (req.user?.ruolo !== "Amministratore") {
|
if (req.user?.ruolo !== "Referente Academy") {
|
||||||
res.status(403).json({ error: "Operazione riservata agli amministratori" });
|
res.status(403).json({ error: "Operazione riservata ai referenti Academy" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated Usare requireReferenteAcademy */
|
||||||
|
export const requireAmministratore = requireReferenteAcademy;
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
|
//=============================================
|
||||||
|
// File: index.ts
|
||||||
|
// Script per la gestine degli endpoint /utente
|
||||||
|
// author: "villari.andrea@libero.it"
|
||||||
|
// version: "1.0.0 2026-07-14"
|
||||||
|
//=============================================
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
import { Router } from "express";
|
import { Router } from "express";
|
||||||
import type { ResultSetHeader, RowDataPacket } from "mysql2";
|
import type { ResultSetHeader, RowDataPacket } from "mysql2";
|
||||||
import pool from "../db/pool.js";
|
import pool from "../db/pool.js";
|
||||||
import { mapDbError } from "../errors/db.js";
|
import { mapDbError } from "../errors/db.js";
|
||||||
import { requireAmministratore, requireAuth } from "../middleware/auth.js";
|
import { requireAuth, requireReferenteAcademy } from "../middleware/auth.js";
|
||||||
import type { Ruolo } from "../types/index.js";
|
import type { Ruolo } from "../types/index.js";
|
||||||
import { validateBody } from "../validation/middleware.js";
|
import { validateBody } from "../validation/middleware.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -41,7 +47,7 @@ async function findUtenteById(id: number): Promise<UtenteRow | undefined> {
|
||||||
return rows[0];
|
return rows[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
router.use(requireAuth, requireAmministratore);
|
router.use(requireAuth, requireReferenteAcademy);
|
||||||
|
|
||||||
router.get("/", async (_req, res) => {
|
router.get("/", async (_req, res) => {
|
||||||
const [rows] = await pool.execute<UtenteRow[]>(
|
const [rows] = await pool.execute<UtenteRow[]>(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
import { CONSEGNA_CONSTRAINTS, UTENTE_CONSTRAINTS } from "../validation/constraints.js";
|
//==============================================
|
||||||
|
// File: index.ts
|
||||||
|
// Script con i tipi relativi alle varie entità
|
||||||
|
// author: "villari.andrea@libero.it"
|
||||||
|
// version: "1.0.0 2026-07-14"
|
||||||
|
//==============================================
|
||||||
|
import {
|
||||||
|
ASSEGNAZIONE_CONSTRAINTS,
|
||||||
|
UTENTE_CONSTRAINTS,
|
||||||
|
} from "../validation/constraints.js";
|
||||||
|
|
||||||
export type Ruolo = (typeof UTENTE_CONSTRAINTS.ruolo.allowedValues)[number];
|
export type Ruolo = (typeof UTENTE_CONSTRAINTS.ruolo.allowedValues)[number];
|
||||||
|
|
||||||
export type StatoConsegna = (typeof CONSEGNA_CONSTRAINTS.stato.allowedValues)[number];
|
export type StatoAssegnazione =
|
||||||
|
(typeof ASSEGNAZIONE_CONSTRAINTS.stato.allowedValues)[number];
|
||||||
|
|
||||||
export interface Utente {
|
export interface Utente {
|
||||||
UtenteID: number;
|
UtenteID: number;
|
||||||
|
|
@ -13,23 +23,22 @@ export interface Utente {
|
||||||
Ruolo: Ruolo;
|
Ruolo: Ruolo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Cliente {
|
export interface Corso {
|
||||||
ClienteID: number;
|
CorsoID: number;
|
||||||
Nome: string;
|
Titolo: string;
|
||||||
Cognome: string;
|
Descrizione: string | null;
|
||||||
Via: string;
|
Categoria: string;
|
||||||
Comune: string;
|
DurataOre: number;
|
||||||
Provincia: string;
|
Obbligatorio: boolean;
|
||||||
Telefono: string;
|
Attivo: boolean;
|
||||||
Email: string;
|
|
||||||
Note: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Consegna {
|
export interface AssegnazioneCorso {
|
||||||
ConsegnaID: number;
|
AssegnazioneID: number;
|
||||||
ClienteID: number;
|
CorsoID: number;
|
||||||
DataRitiro: Date | null;
|
DipendenteID: number;
|
||||||
DataConsegna: Date | null;
|
DataAssegnazione: Date;
|
||||||
Stato: StatoConsegna;
|
DataScadenza: Date;
|
||||||
ChiaveConsegna: string;
|
Stato: StatoAssegnazione;
|
||||||
|
DataCompletamento: Date | null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,33 @@
|
||||||
//=========================================
|
//=========================================
|
||||||
// File: constraints.ts
|
// File: constraints.ts
|
||||||
// Vincoli derivati da initdb/01_schema.sql.
|
// Vincoli derivati da initdb/01_schema.sql.
|
||||||
// author: "villari.andrea@libero.it"
|
// author: "villari.andrea@libero.it"
|
||||||
// version: "1.0.0 2026-06-18"
|
// version: "1.0.0 2026-14-07"
|
||||||
//=========================================
|
//=========================================
|
||||||
|
|
||||||
export const UTENTE_CONSTRAINTS = {
|
export const UTENTE_CONSTRAINTS = {
|
||||||
nome: { maxLength: 100, required: true },
|
nome: { maxLength: 100, required: true },
|
||||||
cognome: { maxLength: 100, required: true },
|
cognome: { maxLength: 100, required: true },
|
||||||
email: { maxLength: 255, required: true },
|
email: { maxLength: 255, required: true },
|
||||||
password: { minLength: 8, maxLength: 72, required: true },
|
password: { minLength: 8, maxLength: 72, required: true },
|
||||||
ruolo: {allowedValues: ["Operatore", "Amministratore"],
|
ruolo: {
|
||||||
|
allowedValues: ["Dipendente", "Referente Academy"] as const,
|
||||||
required: true,
|
required: true,
|
||||||
}
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const CLIENTE_CONSTRAINTS = {
|
export const CORSO_CONSTRAINTS = {
|
||||||
nome: { maxLength: 100, required: true },
|
titolo: { maxLength: 200, required: true },
|
||||||
cognome: { maxLength: 100, required: true },
|
descrizione: { maxLength: 65535, required: false },
|
||||||
via: {maxLength: 100, required: true},
|
categoria: { maxLength: 100, required: true },
|
||||||
comune: {maxLength: 100, required: true},
|
durataOre: { min: 1, max: 65535, required: true },
|
||||||
provincia: {maxLength: 2, required: true},
|
obbligatorio: { required: false },
|
||||||
telefono: { maxLength: 30, required: true },
|
attivo: { required: false },
|
||||||
email: { maxLength: 255, required: true },
|
} as const;
|
||||||
note: { required: false },
|
|
||||||
} as const;
|
export const ASSEGNAZIONE_CONSTRAINTS = {
|
||||||
|
stato: {
|
||||||
|
allowedValues: ["Assegnato", "Completato", "Scaduto", "Annullato"] as const,
|
||||||
export const CONSEGNA_CONSTRAINTS = {
|
required: true,
|
||||||
dataRitiro: { required: false },
|
},
|
||||||
dataConsegna: { required: false },
|
} as const;
|
||||||
stato: {
|
|
||||||
allowedValues: ["da ritirare", "in consegna", "in giacenza", "in deposito"],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
chiaveConsegna: { maxLength: 8, required: true },
|
|
||||||
clienteId: { required: true },
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue