Flussi operativi
Il ciclo di vita di un'analisi dall'arrivo del seme fino al certificato
Flusso completo ad alto livello
┌─────────────────────┐
│ ARRIVO SEME │ Magazzino (o fornitore esterno) consegna seme
│ ───────────────── │
│ Magazziniere crea │
│ LOTTO │ /lottos/lotto/new (specie, varietà, produttore, kg, data arrivo)
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ RICHIESTA ANALISI │ Tecnico laboratorio apre nuova analisi
│ ───────────────── │
│ FT auto-generato │ /analisis/lotto/{id}/new (compilati lotto, dataArrivoLab)
│ Compila parametri │ Tipologia seme, Prova, Categoria, UR, Totale giorni
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ CONTE GIORNALIERE │ Per ogni ripetizione (A,B,C,D) e giorno
│ ───────────────── │
│ Giorno 5 → lettura 1 (germinati, duri, freschi, anormali, morti, vigore)
│ Giorno 10 → lettura 2 (idem)
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ CHIUSURA ANALISI │
│ ───────────────── │
│ Calcola germ media │ germinato1_medio + germinato2_medio (normalizzato a 100)
│ Tecnico imposta │ Risultato (POS/NEG), Scostamento, Vigore finale
│ data = oggi │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ STAMPA │
│ ───────────────── │
│ /scheda │ PDF scheda di controllo interna
│ /certificato │ DOCX certificato per cliente (template con $$_$$)
└─────────────────────┘
Flussi secondari:
• Lotto esaurito → /lottos/settaEsaurito (bulk) o /analisis/lottoEsaurito/{id}
• Cancellazione analisi → soft delete (cancella = motivo)
• Ripristino cancellata → /amministrazione/analisis/cancellate
Diagramma di stato dell'analisi
[nessuna entity]
│
│ POST /analisis/lotto/{id}/new
▼
┌──────────────────┐
│ DA ANALIZZARE │ data = NULL
│ (iniziale) │ cancella = NULL
└────────┬─────────┘
│
Aggiunta di conte
│
▼
┌──────────────────┐
│ IN CORSO │ data = NULL
│ │ cancella = NULL
│ │ lotto.esaurito = false
└────────┬─────────┘
│
┌─────┴─────┐
"CHIUDI_ANALISI" │ "Cancella"
▼ ▼
┌───────────────┐ ┌──────────────────┐
│ CHIUSA │ │ CANCELLATA │
│ data = oggi │ │ cancella = "..." │ (soft)
│ risultato │ │ data ANY │
│ POSITIVO/NEG │ └───────┬──────────┘
└───────────────┘ │
"Cancellazione definitiva"
│
▼
[DELETED hard]
Le 3 query "magiche"
Query "In Corso"
-- Criteria Hibernate equivalent:
SELECT sa.* FROM semiorto_analisi sa
LEFT JOIN semiorto_lotto sl ON sa.id_lotto = sl.id_lotto
WHERE sa.data IS NULL
AND sa.cancella IS NULL
AND (sl.esaurito IS NULL OR sl.esaurito = FALSE);
Risultato: analisi aperte, non cancellate, su lotti attivi.
Query "Da Analizzare"
Questa è la più complessa. Risponde a: "Quali lotti dovrebbero essere rianalizzati questo mese ma non lo sono stati?"
-- Pseudo-codice dal sorgente Java:
SET @month = MONTH(NOW()); -- 1..12
SET @first_of_month = ;
SET @day_of_month = ;
SELECT sa.*
FROM semiorto_analisi sa
JOIN (
-- subquery: ultima analisi chiusa per ogni lotto attivo
-- che corrisponde al calendario del mese corrente
SELECT MAX(a.id) id, l.id_lotto, s.giorniTConta
FROM semiorto_analisi a
JOIN semiorto_lotto l ON a.id_lotto = l.id_lotto
JOIN semiorto_varieta v ON l.varieta = v.id
JOIN semiorto_specie s ON v.specie = s.id
JOIN semiorto_map_specie_analisi_ripetizione_tipologia_seme m
ON s.id = m.id_specie
WHERE l.esaurito = FALSE
AND m.id_analisi_ripetizione = @month -- mese corrente
GROUP BY l.id_lotto
) q ON sa.id = q.id
WHERE sa.data IS NOT NULL
AND DATE_ADD(sa.data, INTERVAL (@day_of_month - q.giorniTConta) DAY)
< @first_of_month
ORDER BY sa.id;
Decodifica step-by-step
- Per ogni lotto attivo (esaurito=false) prendi l'ID dell'ultima analisi chiusa
- Filtra per mese: solo se la combinazione (specie del lotto, mese corrente) è presente nella tabella calendario
map. In altre parole: solo se per quella specie è previsto un check in questo mese. - Calcola "scadenza": l'analisi era "valida" per
giorniTConta(tipicamente 10-14 giorni). Dopo tanti giorni deve essere rifatta. - Test temporale: l'ultima analisi è "in scadenza" se la sua data di chiusura + (giorni nel mese precedente − giorniTConta) < primo giorno del mese corrente.
- Risultato: tutti i lotti che "dovrebbero" avere un'analisi nuova questo mese ma non ce l'hanno.
Oggi è 20 Aprile 2026. Mese = 4.
- Subquery: trova ultima analisi chiusa per ogni lotto non esaurito. Filtra solo lotti di specie che prevedono test ad Aprile (configurato nella tabella map).
- Es.: lotto "VSR1710/TS" di Peperone, ultima analisi chiusa il 10/02/2026 con giorniTConta=10.
- Check temporale:
2026-02-10 + (31 - 10) giorni = 2026-03-03, che è minore di2026-04-01→ ✅ appare in "Da Analizzare".
Query "Cancellate"
SELECT * FROM semiorto_analisi
WHERE cancella IS NOT NULL
ORDER BY specie.italiano, varieta.nome, lotto.id, id DESC;
Generazione FT (fattura tecnica)
Ogni analisi ha un identificativo ft nel formato NNN/YY (es. 3561/20).
anno = format(now(), "yy") // 20, 21, 22, ..., 26
ultimoFT = query "SELECT ft FROM semiorto_analisi
WHERE ft LIKE '%/" + anno + "'
ORDER BY id DESC LIMIT 1"
if (ultimoFT == null) {
numero = 1
} else {
numero = int(ultimoFT.split("/")[0]) + 1
}
ft = numero + "/" + anno // es. "3561/20"
Il numero è globale sull'anno, non resettato per specie o laboratorio.
Flusso stampa scheda di controllo (PDF)
- Utente clicca "Stampa Scheda" sul dettaglio analisi
- Browser fa GET a
/analisis/analisi/{id}/{azienda}/scheda - Controller chiama
StampaSchedaControllo.schedaControllo(…)→ classe custom che costruisce PDF - Response:
Content-Type: application/pdf, body = bytes PDF - Browser mostra o scarica
Flusso stampa certificato (DOCX)
- GET
/analisis/analisi/{id}/{azienda}/certificato - Controller popola mappa
substitutionDatacon 20 chiavi (NOME_SPECIE,FT,DATA_ARRIVO, ecc. — vedi Calcoli) - Legge template
WEB-INF/TEMPLATES_DIRECTORY/modello-certificato-{azienda}.docx DocxManipulator.generateAndSendDocx()sostituisce placeholder$$_NOME_SPECIE_$$ecc. con i valori- Response:
Content-Type: application/vnd.openxmlformats-officedocument..., filenameQuality-Certificate-{lotto}-YYYY/MM/YY.docx
La cartella TEMPLATES_DIRECTORY/ è vuota nel nostro WAR ricevuto. Per la migrazione TopSeed serve chiedere a Diego il file .docx aggiornato o ricostruirlo dai certificati già emessi (scan PDF).
Flusso login + routing iniziale
GET / (root)
│
▼
Spring Security: utente non autenticato?
├── SÌ → redirect a /accesso/login → form → POST /j_spring_security_check
│ └── OK → set JSESSIONID cookie → redirect a /
│
└── NO → DefaultController.index():
│
▼
Query utente.cambiaPassword?
│
├── SÌ → redirect a /utente/cambioPassword (forzato)
│
└── NO → costruisce menu via MenuService.menu(utente)
│
▼
salva menu + utente in session
│
▼
redirect alla PRIMA voce menu abilitata per l'utente:
• Amministrazione → /amministrazione/utenti
• Laboratorio → /analisis/inCorso
• Magazzino → /analisis/inCorso
(fallback: /index)
Avvisi in navbar (AJAX badge)
Ogni pagina include template/menu.jsp che alla fine esegue:
$.getJSON("/analisis/avvisi/json", function(data) {
$('#item-2').html(... + 'inCorso badge: ' + data.inCorso + '...');
$('#item-8').html(... + 'daAnalizzare badge: ' + data.daAnalizzare + '...');
});
Endpoint backend:
@RequestMapping("/analisis/avvisi/json")
@ResponseBody
public Map<String,Integer> avvisi() {
return Map.of(
"inCorso", analisiService.inCorsoNumber(),
"daAnalizzare", analisiService.daAnalizzareNumber()
);
}
Questo spiega i badge rossi che appaiono di fianco alle voci "Analisi" e "Da Analizzare" nella navbar.