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)
-- 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;

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
-- ------------------------------------------------------------
-- 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');

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") {
return {
status: 409,

View file

@ -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<ConsegnaInput>;
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<ResultSetHeader>(
`UPDATE consegna SET ${setClauses.join(", ")} WHERE ConsegnaID = ?`,

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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<string, unknown>): 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<ConsegnaInput> = {};
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) {

View file

@ -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,