From c289bd50034a8b81d4b8b04d954f931f36a88db7 Mon Sep 17 00:00:00 2001 From: AV77web Date: Wed, 24 Jun 2026 21:53:15 +0200 Subject: [PATCH] modifiche a required dataRitiro e dataConsegna --- initdb/01_schema.sql | 27 ++++++++---- initdb/02_seed.sql | 7 ++-- src/errors/db.ts | 10 +++++ src/routes/consegne.ts | 26 ++++++++++-- src/routes/verifica-consegna.ts | 7 ++-- src/types/index.ts | 4 +- src/validation/consegna.ts | 74 ++++++++++++++++++++------------- src/validation/constraints.ts | 6 +-- 8 files changed, 109 insertions(+), 52 deletions(-) diff --git a/initdb/01_schema.sql b/initdb/01_schema.sql index 975191f..893ff7c 100644 --- a/initdb/01_schema.sql +++ b/initdb/01_schema.sql @@ -50,14 +50,16 @@ CREATE TABLE IF NOT EXISTS cliente ( -- ---------------------------------------------------------------- -- Tabella consegna (API: GET/POST/PUT/DELETE /consegna) --- DataRitiro e DataConsegna sono eventi indipendenti: nessun vincolo --- di ordine tra le due date, solo retroattività rispetto a oggi. +-- DataRitiro = giorno in cui il corriere ritira dal cliente. +-- DataConsegna = giorno in cui il corriere consegna al cliente. +-- Sono eventi indipendenti (nessun vincolo di ordine); almeno una delle due +-- date deve essere valorizzata. Se presente, la data non può essere retroattiva. -- ---------------------------------------------------------------- CREATE TABLE IF NOT EXISTS consegna ( ConsegnaID INT UNSIGNED NOT NULL AUTO_INCREMENT, ClienteID INT UNSIGNED NOT NULL, - DataRitiro DATE NOT NULL, - DataConsegna DATE NOT NULL, + DataRitiro DATE NULL, + DataConsegna DATE NULL, Stato ENUM('da ritirare', 'in deposito', 'in consegna', 'in giacenza') NOT NULL DEFAULT 'in deposito', ChiaveConsegna VARCHAR(8) NOT NULL, @@ -69,7 +71,10 @@ CREATE TABLE IF NOT EXISTS consegna ( FOREIGN KEY (ClienteID) REFERENCES cliente (ClienteID) ON UPDATE CASCADE - ON DELETE RESTRICT + ON DELETE RESTRICT, + + CONSTRAINT chk_consegna_almeno_una_data + CHECK (DataRitiro IS NOT NULL OR DataConsegna IS NOT NULL) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 @@ -89,12 +94,12 @@ CREATE TRIGGER trg_consegna_no_retroattivita_insert BEFORE INSERT ON consegna FOR EACH ROW BEGIN - IF NEW.DataRitiro < CURDATE() THEN + IF NEW.DataRitiro IS NOT NULL AND NEW.DataRitiro < CURDATE() THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'DataRitiro non può essere anteriore alla data odierna'; END IF; - IF NEW.DataConsegna < CURDATE() THEN + IF NEW.DataConsegna IS NOT NULL AND NEW.DataConsegna < CURDATE() THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'DataConsegna non può essere anteriore alla data odierna'; END IF; @@ -104,12 +109,16 @@ CREATE TRIGGER trg_consegna_no_retroattivita_update BEFORE UPDATE ON consegna FOR EACH ROW BEGIN - IF NEW.DataRitiro <> OLD.DataRitiro AND NEW.DataRitiro < CURDATE() THEN + IF NEW.DataRitiro IS NOT NULL + AND (OLD.DataRitiro IS NULL OR NEW.DataRitiro <> OLD.DataRitiro) + AND NEW.DataRitiro < CURDATE() THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'DataRitiro non può essere anteriore alla data odierna'; END IF; - IF NEW.DataConsegna <> OLD.DataConsegna AND NEW.DataConsegna < CURDATE() THEN + IF NEW.DataConsegna IS NOT NULL + AND (OLD.DataConsegna IS NULL OR NEW.DataConsegna <> OLD.DataConsegna) + AND NEW.DataConsegna < CURDATE() THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'DataConsegna non può essere anteriore alla data odierna'; END IF; diff --git a/initdb/02_seed.sql b/initdb/02_seed.sql index 3203f7e..0f162a2 100644 --- a/initdb/02_seed.sql +++ b/initdb/02_seed.sql @@ -35,12 +35,13 @@ INSERT INTO cliente (Nome, Cognome, Via, Comune, Provincia, Telefono, Email, Not -- ClienteID: 1=Mario, 2=Laura, 3=Marco, 4=Giulia, 5=Paolo -- ------------------------------------------------------------ -- Date dinamiche: compatibili con i trigger di retroattività (>= CURDATE()) --- DataRitiro e DataConsegna sono indipendenti (es. riga 4: consegna prima del ritiro) +-- DataRitiro e DataConsegna sono indipendenti; almeno una delle due è obbligatoria. +-- Esempi: solo ritiro (CNSG2406), solo consegna (CNSG5522), entrambe (CNSG9001). INSERT INTO consegna (ClienteID, DataRitiro, DataConsegna, Stato, ChiaveConsegna) VALUES (1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 2 DAY), 'in deposito', 'CNSG9001'), (2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 1 DAY), 'in consegna', 'CNSG3344'), - (3, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 1 DAY), 'da ritirare', 'CNSG2406'), + (3, CURDATE(), NULL, 'da ritirare', 'CNSG2406'), (4, DATE_ADD(CURDATE(), INTERVAL 5 DAY), DATE_ADD(CURDATE(), INTERVAL 2 DAY), 'in giacenza', 'CNSG7788'), - (5, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 1 DAY), 'in deposito', 'CNSG5522'), + (5, NULL, DATE_ADD(CURDATE(), INTERVAL 1 DAY), 'in deposito', 'CNSG5522'), (1, DATE_ADD(CURDATE(), INTERVAL 1 DAY), DATE_ADD(CURDATE(), INTERVAL 3 DAY), 'da ritirare', 'CNSG9876'), (2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 2 DAY), 'in consegna', 'CNSG1122'); diff --git a/src/errors/db.ts b/src/errors/db.ts index 027b6db..bd63a44 100644 --- a/src/errors/db.ts +++ b/src/errors/db.ts @@ -58,6 +58,16 @@ export function mapDbError(error: unknown): DbErrorResponse | null { } } + if (err.code === "ER_CHECK_CONSTRAINT_VIOLATED") { + if (message.includes("chk_consegna_almeno_una_data")) { + return { + status: 400, + error: "Inserire almeno una data di ritiro o di consegna", + fields: { _form: "Inserire almeno una data di ritiro o di consegna" }, + }; + } + } + if (err.code === "ER_ROW_IS_REFERENCED_2") { return { status: 409, diff --git a/src/routes/consegne.ts b/src/routes/consegne.ts index 3b7d7d9..3eb755a 100644 --- a/src/routes/consegne.ts +++ b/src/routes/consegne.ts @@ -15,8 +15,8 @@ const router = Router(); interface ConsegnaRow extends RowDataPacket { ConsegnaID: number; ClienteID: number; - DataRitiro: Date | string; - DataConsegna: Date | string; + DataRitiro: Date | string | null; + DataConsegna: Date | string | null; Stato: string; ChiaveConsegna: string; ClienteNome: string; @@ -37,7 +37,8 @@ const SELECT_BASE = ` INNER JOIN cliente cl ON cl.ClienteID = c.ClienteID `; -function formatDate(value: Date | string): string { +function formatDate(value: Date | string | null): string | null { + if (value == null) return null; if (value instanceof Date) { return value.toISOString().slice(0, 10); } @@ -142,7 +143,7 @@ router.put("/:id", validateBody(validateConsegnaUpdate), async (req, res) => { const updates = res.locals.validatedBody as Partial; const setClauses: string[] = []; - const values: (string | number)[] = []; + const values: (string | number | null)[] = []; if (updates.dataRitiro !== undefined) { setClauses.push("DataRitiro = ?"); @@ -165,6 +166,23 @@ router.put("/:id", validateBody(validateConsegnaUpdate), async (req, res) => { values.push(updates.clienteId); } + const mergedRitiro = + updates.dataRitiro !== undefined + ? updates.dataRitiro + : formatDate(existing.DataRitiro); + const mergedConsegna = + updates.dataConsegna !== undefined + ? updates.dataConsegna + : formatDate(existing.DataConsegna); + + if (!mergedRitiro && !mergedConsegna) { + res.status(400).json({ + error: "Inserire almeno una data di ritiro o di consegna", + fields: { _form: "Inserire almeno una data di ritiro o di consegna" }, + }); + return; + } + try { await pool.execute( `UPDATE consegna SET ${setClauses.join(", ")} WHERE ConsegnaID = ?`, diff --git a/src/routes/verifica-consegna.ts b/src/routes/verifica-consegna.ts index 42ae06c..76ff7f2 100644 --- a/src/routes/verifica-consegna.ts +++ b/src/routes/verifica-consegna.ts @@ -12,15 +12,16 @@ import { const router = Router(); interface VerificaRow extends RowDataPacket { - DataRitiro: Date | string; - DataConsegna: Date | string; + DataRitiro: Date | string | null; + DataConsegna: Date | string | null; Stato: string; ClienteNome: string; ClienteCognome: string; Comune: string; } -function formatDate(value: Date | string): string { +function formatDate(value: Date | string | null): string | null { + if (value == null) return null; if (value instanceof Date) { return value.toISOString().slice(0, 10); } diff --git a/src/types/index.ts b/src/types/index.ts index 5eab934..6fcbb13 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -28,8 +28,8 @@ export interface Cliente { export interface Consegna { ConsegnaID: number; ClienteID: number; - DataRitiro: Date; - DataConsegna: Date; + DataRitiro: Date | null; + DataConsegna: Date | null; Stato: StatoConsegna; ChiaveConsegna: string; } diff --git a/src/validation/consegna.ts b/src/validation/consegna.ts index c997fc3..0cd543c 100644 --- a/src/validation/consegna.ts +++ b/src/validation/consegna.ts @@ -21,13 +21,18 @@ import { } from "./primitives.js"; export interface ConsegnaInput { - dataRitiro: string; - dataConsegna: string; + dataRitiro: string | null; + dataConsegna: string | null; stato: StatoConsegna; chiaveConsegna: string; clienteId: number; } +function normalizeOptionalDate(value: unknown): string | null { + const str = normalizeString(value); + return str || null; +} + function todayAtMidnight(): Date { const today = new Date(); today.setHours(0, 0, 0, 0); @@ -48,10 +53,31 @@ function validateDateNotRetroactive( } } +function validateOptionalDateField( + errors: FieldErrors, + field: string, + value: string | null, + label: string +): void { + if (!value) return; + validateDate(errors, field, value, label); + validateDateNotRetroactive(errors, field, value, label); +} + +function validateAtLeastOneDate( + errors: FieldErrors, + dataRitiro: string | null, + dataConsegna: string | null +): void { + if (!dataRitiro && !dataConsegna) { + errors._form = "Inserire almeno una data di ritiro o di consegna"; + } +} + export function normalizeConsegnaInput(raw: Record): ConsegnaInput { return { - dataRitiro: normalizeString(raw.dataRitiro), - dataConsegna: normalizeString(raw.dataConsegna), + dataRitiro: normalizeOptionalDate(raw.dataRitiro), + dataConsegna: normalizeOptionalDate(raw.dataConsegna), stato: normalizeString(raw.stato || "in deposito") as StatoConsegna, chiaveConsegna: normalizeString(raw.chiaveConsegna).toUpperCase(), clienteId: normalizePositiveInt(raw.clienteId), @@ -65,20 +91,17 @@ function validateConsegnaFields( options: { requireAll: boolean } ): void { if (options.requireAll || "dataRitiro" in raw) { - validateRequired(errors, "dataRitiro", data.dataRitiro, "Data ritiro obbligatoria"); - validateDate(errors, "dataRitiro", data.dataRitiro, "Data ritiro"); - validateDateNotRetroactive(errors, "dataRitiro", data.dataRitiro, "Data ritiro"); + validateOptionalDateField(errors, "dataRitiro", data.dataRitiro, "Data ritiro"); } if (options.requireAll || "dataConsegna" in raw) { - validateRequired( - errors, - "dataConsegna", - data.dataConsegna, - "Data consegna obbligatoria" - ); - validateDate(errors, "dataConsegna", data.dataConsegna, "Data consegna"); - validateDateNotRetroactive(errors, "dataConsegna", data.dataConsegna, "Data consegna"); + validateOptionalDateField(errors, "dataConsegna", data.dataConsegna, "Data consegna"); + } + + if (options.requireAll) { + validateAtLeastOneDate(errors, data.dataRitiro, data.dataConsegna); + } else if ("dataRitiro" in raw && "dataConsegna" in raw) { + validateAtLeastOneDate(errors, data.dataRitiro, data.dataConsegna); } if (options.requireAll || "stato" in raw) { @@ -129,22 +152,17 @@ export function validateConsegnaUpdate( const data: Partial = {}; if ("dataRitiro" in raw) { - data.dataRitiro = normalizeString(raw.dataRitiro); - validateRequired(errors, "dataRitiro", data.dataRitiro, "Data ritiro obbligatoria"); - validateDate(errors, "dataRitiro", data.dataRitiro, "Data ritiro"); - validateDateNotRetroactive(errors, "dataRitiro", data.dataRitiro, "Data ritiro"); + data.dataRitiro = normalizeOptionalDate(raw.dataRitiro); + validateOptionalDateField(errors, "dataRitiro", data.dataRitiro, "Data ritiro"); } if ("dataConsegna" in raw) { - data.dataConsegna = normalizeString(raw.dataConsegna); - validateRequired( - errors, - "dataConsegna", - data.dataConsegna, - "Data consegna obbligatoria" - ); - validateDate(errors, "dataConsegna", data.dataConsegna, "Data consegna"); - validateDateNotRetroactive(errors, "dataConsegna", data.dataConsegna, "Data consegna"); + data.dataConsegna = normalizeOptionalDate(raw.dataConsegna); + validateOptionalDateField(errors, "dataConsegna", data.dataConsegna, "Data consegna"); + } + + if ("dataRitiro" in raw && "dataConsegna" in raw) { + validateAtLeastOneDate(errors, data.dataRitiro ?? null, data.dataConsegna ?? null); } if ("stato" in raw) { diff --git a/src/validation/constraints.ts b/src/validation/constraints.ts index 30be227..bb02fca 100644 --- a/src/validation/constraints.ts +++ b/src/validation/constraints.ts @@ -27,9 +27,9 @@ export const UTENTE_CONSTRAINTS = { } as const; - export const CONSEGNA_CONSTRAINTS = { - dataconsegna: { required: true }, - dataritiro: {required: true}, +export const CONSEGNA_CONSTRAINTS = { + dataRitiro: { required: false }, + dataConsegna: { required: false }, stato: { allowedValues: ["da ritirare", "in consegna", "in giacenza", "in deposito"], required: true,