modifiche a required dataRitiro e dataConsegna
All checks were successful
Deploy to VPS / build (push) Successful in 16s
Deploy to VPS / deploy (push) Successful in 20s

This commit is contained in:
AV77web 2026-06-24 21:53:15 +02:00
parent 797f425a24
commit c289bd5003
8 changed files with 109 additions and 52 deletions

View file

@ -50,14 +50,16 @@ CREATE TABLE IF NOT EXISTS cliente (
-- ---------------------------------------------------------------- -- ----------------------------------------------------------------
-- Tabella consegna (API: GET/POST/PUT/DELETE /consegna) -- Tabella consegna (API: GET/POST/PUT/DELETE /consegna)
-- DataRitiro e DataConsegna sono eventi indipendenti: nessun vincolo -- DataRitiro = giorno in cui il corriere ritira dal cliente.
-- di ordine tra le due date, solo retroattività rispetto a oggi. -- 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 ( CREATE TABLE IF NOT EXISTS consegna (
ConsegnaID INT UNSIGNED NOT NULL AUTO_INCREMENT, ConsegnaID INT UNSIGNED NOT NULL AUTO_INCREMENT,
ClienteID INT UNSIGNED NOT NULL, ClienteID INT UNSIGNED NOT NULL,
DataRitiro DATE NOT NULL, DataRitiro DATE NULL,
DataConsegna DATE NOT NULL, DataConsegna DATE NULL,
Stato ENUM('da ritirare', 'in deposito', 'in consegna', 'in giacenza') NOT NULL DEFAULT 'in deposito', Stato ENUM('da ritirare', 'in deposito', 'in consegna', 'in giacenza') NOT NULL DEFAULT 'in deposito',
ChiaveConsegna VARCHAR(8) NOT NULL, ChiaveConsegna VARCHAR(8) NOT NULL,
@ -69,7 +71,10 @@ CREATE TABLE IF NOT EXISTS consegna (
FOREIGN KEY (ClienteID) FOREIGN KEY (ClienteID)
REFERENCES cliente (ClienteID) REFERENCES cliente (ClienteID)
ON UPDATE CASCADE 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 ) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4 DEFAULT CHARSET=utf8mb4
@ -89,12 +94,12 @@ CREATE TRIGGER trg_consegna_no_retroattivita_insert
BEFORE INSERT ON consegna BEFORE INSERT ON consegna
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
IF NEW.DataRitiro < CURDATE() THEN IF NEW.DataRitiro IS NOT NULL AND NEW.DataRitiro < CURDATE() THEN
SIGNAL SQLSTATE '45000' SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'DataRitiro non può essere anteriore alla data odierna'; SET MESSAGE_TEXT = 'DataRitiro non può essere anteriore alla data odierna';
END IF; END IF;
IF NEW.DataConsegna < CURDATE() THEN IF NEW.DataConsegna IS NOT NULL AND NEW.DataConsegna < CURDATE() THEN
SIGNAL SQLSTATE '45000' SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'DataConsegna non può essere anteriore alla data odierna'; SET MESSAGE_TEXT = 'DataConsegna non può essere anteriore alla data odierna';
END IF; END IF;
@ -104,12 +109,16 @@ CREATE TRIGGER trg_consegna_no_retroattivita_update
BEFORE UPDATE ON consegna BEFORE UPDATE ON consegna
FOR EACH ROW FOR EACH ROW
BEGIN 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' SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'DataRitiro non può essere anteriore alla data odierna'; SET MESSAGE_TEXT = 'DataRitiro non può essere anteriore alla data odierna';
END IF; 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' SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'DataConsegna non può essere anteriore alla data odierna'; SET MESSAGE_TEXT = 'DataConsegna non può essere anteriore alla data odierna';
END IF; END IF;

View file

@ -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 -- ClienteID: 1=Mario, 2=Laura, 3=Marco, 4=Giulia, 5=Paolo
-- ------------------------------------------------------------ -- ------------------------------------------------------------
-- Date dinamiche: compatibili con i trigger di retroattività (>= CURDATE()) -- 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 INSERT INTO consegna (ClienteID, DataRitiro, DataConsegna, Stato, ChiaveConsegna) VALUES
(1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 2 DAY), 'in deposito', 'CNSG9001'), (1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 2 DAY), 'in deposito', 'CNSG9001'),
(2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 1 DAY), 'in consegna', 'CNSG3344'), (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'), (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'), (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'); (2, CURDATE(), DATE_ADD(CURDATE(), INTERVAL 2 DAY), 'in consegna', 'CNSG1122');

View file

@ -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") { if (err.code === "ER_ROW_IS_REFERENCED_2") {
return { return {
status: 409, status: 409,

View file

@ -15,8 +15,8 @@ const router = Router();
interface ConsegnaRow extends RowDataPacket { interface ConsegnaRow extends RowDataPacket {
ConsegnaID: number; ConsegnaID: number;
ClienteID: number; ClienteID: number;
DataRitiro: Date | string; DataRitiro: Date | string | null;
DataConsegna: Date | string; DataConsegna: Date | string | null;
Stato: string; Stato: string;
ChiaveConsegna: string; ChiaveConsegna: string;
ClienteNome: string; ClienteNome: string;
@ -37,7 +37,8 @@ const SELECT_BASE = `
INNER JOIN cliente cl ON cl.ClienteID = c.ClienteID 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) { if (value instanceof Date) {
return value.toISOString().slice(0, 10); 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<ConsegnaInput>; const updates = res.locals.validatedBody as Partial<ConsegnaInput>;
const setClauses: string[] = []; const setClauses: string[] = [];
const values: (string | number)[] = []; const values: (string | number | null)[] = [];
if (updates.dataRitiro !== undefined) { if (updates.dataRitiro !== undefined) {
setClauses.push("DataRitiro = ?"); setClauses.push("DataRitiro = ?");
@ -165,6 +166,23 @@ router.put("/:id", validateBody(validateConsegnaUpdate), async (req, res) => {
values.push(updates.clienteId); 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 { try {
await pool.execute<ResultSetHeader>( await pool.execute<ResultSetHeader>(
`UPDATE consegna SET ${setClauses.join(", ")} WHERE ConsegnaID = ?`, `UPDATE consegna SET ${setClauses.join(", ")} WHERE ConsegnaID = ?`,

View file

@ -12,15 +12,16 @@ import {
const router = Router(); const router = Router();
interface VerificaRow extends RowDataPacket { interface VerificaRow extends RowDataPacket {
DataRitiro: Date | string; DataRitiro: Date | string | null;
DataConsegna: Date | string; DataConsegna: Date | string | null;
Stato: string; Stato: string;
ClienteNome: string; ClienteNome: string;
ClienteCognome: string; ClienteCognome: string;
Comune: 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) { if (value instanceof Date) {
return value.toISOString().slice(0, 10); return value.toISOString().slice(0, 10);
} }

View file

@ -28,8 +28,8 @@ export interface Cliente {
export interface Consegna { export interface Consegna {
ConsegnaID: number; ConsegnaID: number;
ClienteID: number; ClienteID: number;
DataRitiro: Date; DataRitiro: Date | null;
DataConsegna: Date; DataConsegna: Date | null;
Stato: StatoConsegna; Stato: StatoConsegna;
ChiaveConsegna: string; ChiaveConsegna: string;
} }

View file

@ -21,13 +21,18 @@ import {
} from "./primitives.js"; } from "./primitives.js";
export interface ConsegnaInput { export interface ConsegnaInput {
dataRitiro: string; dataRitiro: string | null;
dataConsegna: string; dataConsegna: string | null;
stato: StatoConsegna; stato: StatoConsegna;
chiaveConsegna: string; chiaveConsegna: string;
clienteId: number; clienteId: number;
} }
function normalizeOptionalDate(value: unknown): string | null {
const str = normalizeString(value);
return str || null;
}
function todayAtMidnight(): Date { function todayAtMidnight(): Date {
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); 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<string, unknown>): ConsegnaInput { export function normalizeConsegnaInput(raw: Record<string, unknown>): ConsegnaInput {
return { return {
dataRitiro: normalizeString(raw.dataRitiro), dataRitiro: normalizeOptionalDate(raw.dataRitiro),
dataConsegna: normalizeString(raw.dataConsegna), dataConsegna: normalizeOptionalDate(raw.dataConsegna),
stato: normalizeString(raw.stato || "in deposito") as StatoConsegna, stato: normalizeString(raw.stato || "in deposito") as StatoConsegna,
chiaveConsegna: normalizeString(raw.chiaveConsegna).toUpperCase(), chiaveConsegna: normalizeString(raw.chiaveConsegna).toUpperCase(),
clienteId: normalizePositiveInt(raw.clienteId), clienteId: normalizePositiveInt(raw.clienteId),
@ -65,20 +91,17 @@ function validateConsegnaFields(
options: { requireAll: boolean } options: { requireAll: boolean }
): void { ): void {
if (options.requireAll || "dataRitiro" in raw) { if (options.requireAll || "dataRitiro" in raw) {
validateRequired(errors, "dataRitiro", data.dataRitiro, "Data ritiro obbligatoria"); validateOptionalDateField(errors, "dataRitiro", data.dataRitiro, "Data ritiro");
validateDate(errors, "dataRitiro", data.dataRitiro, "Data ritiro");
validateDateNotRetroactive(errors, "dataRitiro", data.dataRitiro, "Data ritiro");
} }
if (options.requireAll || "dataConsegna" in raw) { if (options.requireAll || "dataConsegna" in raw) {
validateRequired( validateOptionalDateField(errors, "dataConsegna", data.dataConsegna, "Data consegna");
errors, }
"dataConsegna",
data.dataConsegna, if (options.requireAll) {
"Data consegna obbligatoria" validateAtLeastOneDate(errors, data.dataRitiro, data.dataConsegna);
); } else if ("dataRitiro" in raw && "dataConsegna" in raw) {
validateDate(errors, "dataConsegna", data.dataConsegna, "Data consegna"); validateAtLeastOneDate(errors, data.dataRitiro, data.dataConsegna);
validateDateNotRetroactive(errors, "dataConsegna", data.dataConsegna, "Data consegna");
} }
if (options.requireAll || "stato" in raw) { if (options.requireAll || "stato" in raw) {
@ -129,22 +152,17 @@ export function validateConsegnaUpdate(
const data: Partial<ConsegnaInput> = {}; const data: Partial<ConsegnaInput> = {};
if ("dataRitiro" in raw) { if ("dataRitiro" in raw) {
data.dataRitiro = normalizeString(raw.dataRitiro); data.dataRitiro = normalizeOptionalDate(raw.dataRitiro);
validateRequired(errors, "dataRitiro", data.dataRitiro, "Data ritiro obbligatoria"); validateOptionalDateField(errors, "dataRitiro", data.dataRitiro, "Data ritiro");
validateDate(errors, "dataRitiro", data.dataRitiro, "Data ritiro");
validateDateNotRetroactive(errors, "dataRitiro", data.dataRitiro, "Data ritiro");
} }
if ("dataConsegna" in raw) { if ("dataConsegna" in raw) {
data.dataConsegna = normalizeString(raw.dataConsegna); data.dataConsegna = normalizeOptionalDate(raw.dataConsegna);
validateRequired( validateOptionalDateField(errors, "dataConsegna", data.dataConsegna, "Data consegna");
errors, }
"dataConsegna",
data.dataConsegna, if ("dataRitiro" in raw && "dataConsegna" in raw) {
"Data consegna obbligatoria" validateAtLeastOneDate(errors, data.dataRitiro ?? null, data.dataConsegna ?? null);
);
validateDate(errors, "dataConsegna", data.dataConsegna, "Data consegna");
validateDateNotRetroactive(errors, "dataConsegna", data.dataConsegna, "Data consegna");
} }
if ("stato" in raw) { if ("stato" in raw) {

View file

@ -27,9 +27,9 @@ export const UTENTE_CONSTRAINTS = {
} as const; } as const;
export const CONSEGNA_CONSTRAINTS = { export const CONSEGNA_CONSTRAINTS = {
dataconsegna: { required: true }, dataRitiro: { required: false },
dataritiro: {required: true}, dataConsegna: { required: false },
stato: { stato: {
allowedValues: ["da ritirare", "in consegna", "in giacenza", "in deposito"], allowedValues: ["da ritirare", "in consegna", "in giacenza", "in deposito"],
required: true, required: true,