From 5f20f20a86976c7e6fbe86e59f1ad26fb6732153 Mon Sep 17 00:00:00 2001 From: simone castellan Date: Fri, 13 Nov 2026 16:03:50 +0100 Subject: [PATCH 1/3] =?UTF-8?q?feat(S14):=20controlli=20di=20coerenza=202?= =?UTF-8?q?=20=E2=80=94=20date=20nella=20scaletta=20e=20nel=20rider,=20ste?= =?UTF-8?q?ssa=20versione=20cambiata,=20allegati=20richiesti,=20clausole?= =?UTF-8?q?=20del=20rider=20cambiate=20nel=20=C2=ABCosa=20cambia=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- app.js | 68 +++++++++++++++++++++++++++++++++++++-- app/index.html | 15 +++++++++ index.template.html | 74 +++++++++++++++++++++++++++++++++++++++++-- src/styles.css | 9 ++++++ test/engines.test.mjs | 3 +- 5 files changed, 164 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 7104d93..3a6ce1d 100644 --- a/app.js +++ b/app.js @@ -4205,7 +4205,7 @@ function nextVariantName(){ var n=0; VARIANTS.forEach(function(v){ var m=/^Varia data e contatti non erano mai diversi. Restano nello stato di ogni variante — lo schema e i file già salvati non cambiano — ma a ogni sincronizzazione quelli della variante attiva si scrivono nelle altre. */ -var CAMPI_DOCUMENTO=["pdfOrganico","quoteDaTerra","titolo","luogo","evDate","evTime","tipoEvento","techContact","contacts","pdfHeader","pdfPesi","versione","latiTeatro","sonorizzazione","orari","scaletta","customModels"]; /* pdfPesi (06/10/2026): i kg nel PDF valgono per tutto il documento · scaletta (10/10): i brani sono della serata, non della scena · customModels (13/10): i modelli dell'elemento personalizzato sono del progetto, uguali in ogni scena (assente se vuoto) */ +var CAMPI_DOCUMENTO=["allegatiRichiesti","pdfOrganico","quoteDaTerra","titolo","luogo","evDate","evTime","tipoEvento","techContact","contacts","pdfHeader","pdfPesi","versione","latiTeatro","sonorizzazione","orari","scaletta","customModels"]; /* pdfPesi (06/10/2026): i kg nel PDF valgono per tutto il documento · scaletta (10/10): i brani sono della serata, non della scena · customModels (13/10): i modelli dell'elemento personalizzato sono del progetto, uguali in ogni scena (assente se vuoto) */ function propagaCampiDocumento(v){ var src=v&&v.state; if(!src) return; VARIANTS.forEach(function(o){ @@ -7370,6 +7370,7 @@ function normalizeState(s){ if(s.latiTeatro!==true) delete s.latiTeatro; /* giardino e corte: spenti di serie, solo un true esplicito li accende (12/11/2026) */ if(s.pdfOrganico!==true) delete s.pdfOrganico; /* «Organico: N + M» nel cartiglio (13/11): solo un true esplicito la accende */ if(s.quoteDaTerra!==true) delete s.quoteDaTerra; /* quote delle pedane «da terra» (13/11): solo un true esplicito le accende */ + s.allegatiRichiesti=allegatiPuliti(s.allegatiRichiesti); if(!s.allegatiRichiesti) delete s.allegatiRichiesti; /* «Allegati richiesti» (S14, 13/11): solo se l'utente li usa */ if(s.pdfPesi!==true) delete s.pdfPesi; /* kg nel PDF: solo un true esplicito li accende (06/10/2026) */ var _vers=(typeof s.versione==="number"||typeof s.versione==="string") ? +s.versione : NaN; /* versione del documento (12/11): intero 1-999, altrimenti assente */ if(_vers%1===0 && _vers>=1 && _vers<=999) s.versione=_vers; else delete s.versione; @@ -16868,6 +16869,30 @@ function controlliPrimaDelExport(){ add("data-nel-titolo","Il titolo dice "+dt[0].testo+" ma la data dell'evento è "+e[2]+"/"+e[1]+"/"+e[0]+"."); } } + /* 6b · la stessa data sbagliata dentro la scaletta o nei testi del rider (S14, 13/11): organico e testi riusati da una serata vecchia */ + if(state.evDate){ + var _e2=state.evDate.split("-"), _evTxt=_e2[2]+"/"+_e2[1]+"/"+_e2[0]; + var _sc=[]; try{ (scalettaNorm(state.scaletta)||[]).forEach(function(v){ if(v){ _sc.push(v.titolo); _sc.push(v.note); } }); }catch(_e){} + var _ds=[]; _sc.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _ds.push(d.testo); }); }); + if(_ds.length) add("data-nella-scaletta","La scaletta dice "+_ds[0]+" ma la data dell'evento è "+_evTxt+"."); + var _rt=[]; var _ri=(state.rider && typeof state.rider==="object") ? state.rider : {}; Object.keys(_ri).forEach(function(k){ if(typeof _ri[k]==="string") _rt.push(_ri[k]); }); + var _dr=[]; _rt.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _dr.push(d.testo); }); }); + if(_dr.length) add("data-nel-rider","Nel testo del rider c'è "+_dr[0]+" ma la data dell'evento è "+_evTxt+"."); + } + /* 6c · stessa versione già esportata, contenuto diverso (S14): chi ha il file ha qualcosa di diverso con lo stesso nome */ + try{ + var _vN=versioneNumero(); + if(_vN){ + var _voce=(storicoPulito(state.storico)||[]).filter(function(e){ return e.v===_vN; })[0]; + if(_voce && _voce.r && _voce.r.h){ + var _cur=impronteDocumenti(), _cambiati=Object.keys(_cur).filter(function(k){ return (k in _voce.r.h) && _voce.r.h[k]!==_cur[k]; }); + if(_cambiati.length) add("stessa-versione-cambiata","La v"+_vN+" è già stata esportata e da allora sono cambiati: "+_cambiati.map(function(k){ return DOC_NOMI[k]||k; }).join(", ")+". Chi ha il file della v"+_vN+" ha un contenuto diverso con lo stesso nome: aumenta la versione."); + } + } + }catch(_e){} + /* 6d · allegati richiesti ancora «da chiedere» (S14): solo se l'utente ha scritto l'elenco */ + var _al=allegatiPuliti(state.allegatiRichiesti), _dc=(_al||[]).filter(function(a){ return a.s==="da chiedere"; }); + if(_dc.length) add("allegati-da-chiedere",(_dc.length===1 ? "Allegato ancora da chiedere: " : _dc.length+" allegati ancora da chiedere: ")+_dc.map(function(a){ return a.n; }).join(", ")+"."); /* 7 · manca il luogo (L1-b, dietro «Controllo tecnico»): titolo o data scritti ma il luogo è vuoto, quindi il PDF esce senza il posto */ /* 8 · violini senza sezione accanto a violini con sezione (organico): il foglio non può dire I o II (Simone, 12/11) */ var _og=organicoDati(); @@ -37960,10 +37985,26 @@ function riassuntoDocumento(){ rows.forEach(function(r){ if(r.reserved || r.spare) return; can++; var m=r.micOff ? "" : String(r.mic||""); if(m){ m=m.slice(0,40); mic[m]=(mic[m]||0)+1; } }); try{ mix=busList().count; }catch(_e){} var st=state.stage||{}; - return { f:STORICO_FORMATO, pal:[Math.round(+st.w||0), Math.round(+st.d||0)], el:el, ped:ped, can:can, mix:mix, mic:mic, h:impronteDocumenti() }; + return { f:STORICO_FORMATO, pal:[Math.round(+st.w||0), Math.round(+st.d||0)], el:el, ped:ped, can:can, mix:mix, mic:mic, h:impronteDocumenti(), ri:impronteClausoleRider() }; } /* Impronta di CIASCUN documento del pacchetto (S10, richiesta di Simone 13/11: «cambiato / invariato da vN-1» accanto a ogni file): un hash breve del contenuto che quel documento stampa (non del progetto intero: un'asta in più non cambia il rider). Dentro il riassunto, quindi nello storico. */ +var DOC_NOMI={stageplot:"stage plot", inputlist:"channel list", outputlist:"output list", rider:"rider", organico:"organico", backline:"backline", lightslist:"lista luci", scaletta:"scaletta"}; +/* ═══ CONTROLLI DI COERENZA 2 (13/11/2026, specifica S14) ═══ «Allegati richiesti»: elenco libero (≤30 voci) con stato «ricevuto» / «da chiedere»; + se resta qualcosa «da chiedere» lo dice «Prima di esportare». Solo se l'utente lo usa (assente di serie). */ +function allegatiPuliti(l){ + if(!Array.isArray(l)) return null; + var out=l.filter(function(a){ return a && typeof a==="object" && typeof a.n==="string" && a.n.trim(); }).slice(0,30) + .map(function(a){ return {n:a.n.trim().slice(0,60), s:(a.s==="ricevuto") ? "ricevuto" : "da chiedere"}; }); + return out.length ? out : null; +} +function hashBreve(x){ var j; try{ j=JSON.stringify(x); }catch(_e){ j=""; } j=j||""; var n=5381; for(var i=0;i>>0).toString(36); } +function impronteClausoleRider(){ + var r=(state.rider && typeof state.rider==="object") ? state.rider : {}, out={}; + Object.keys(r).forEach(function(k){ if(typeof r[k]==="string" && r[k].trim()) out[k]=hashBreve(r[k]); }); + return out; +} +var RIDER_CLAUSOLE_NOMI={sistema:"sistema audio", luci:"luci", personale:"personale", sedie:"sedie", note:"note", camerini:"camerini", parcheggi:"parcheggi"}; function impronteDocumenti(){ function h(x){ var j; try{ j=JSON.stringify(x); }catch(_e){ j=""; } j=j||""; var n=5381; for(var i=0;i>>0).toString(36); } function g(f){ try{ return h(f()); }catch(_e){ return ""; } } @@ -38023,6 +38064,12 @@ function riassuntoDiff(a, b){ Object.keys(ma).concat(Object.keys(mb)).forEach(function(k){ ms[k]=1; }); Object.keys(ms).sort().forEach(function(k){ var d=(mb[k]||0)-(ma[k]||0); if(d) dm.push((d>0?"+":"−")+Math.abs(d)+" "+k); }); if(dm.length) righe.push("Microfoni: "+dm.join(", ")); + /* S14: clausole del rider cambiate (solo se entrambi i riassunti le hanno: i vecchi non hanno `ri`) */ + if(a.ri && b.ri){ + var rk={}, rc=[]; Object.keys(a.ri).concat(Object.keys(b.ri)).forEach(function(k){ rk[k]=1; }); + Object.keys(rk).sort().forEach(function(k){ if(a.ri[k]!==b.ri[k]) rc.push((RIDER_CLAUSOLE_NOMI[k]||k)+(!(k in a.ri) ? " (aggiunta)" : (!(k in b.ri) ? " (tolta)" : ""))); }); + if(rc.length) righe.push("Clausole del rider cambiate: "+rc.join(", ")); + } return righe; } function diffVersioniDati(){ @@ -38100,7 +38147,24 @@ function renderPdfUltimo(){ el.textContent=tx; el.hidden=!tx; el.classList.toggle("vecchio", /^Modificato/.test(tx)); try{ renderDiffVersioni(); }catch(_e){} + try{ renderAllegati(); }catch(_e){} +} +function renderAllegati(){ + var ul=document.getElementById("pdfAllegatiLista"); if(!ul) return; + ul.innerHTML=""; + (allegatiPuliti(state.allegatiRichiesti)||[]).forEach(function(a,i){ + var li=document.createElement("li"), nm=document.createElement("span"), st=document.createElement("button"), x=document.createElement("button"); + nm.textContent=a.n; st.type="button"; st.className="btn pdf-all-st"+(a.s==="ricevuto" ? " ok" : ""); st.textContent=a.s; st.title="Clic per cambiare: ricevuto / da chiedere"; + x.type="button"; x.className="btn pdf-all-x"; x.textContent="\u2715"; x.setAttribute("aria-label","Togli «"+a.n+"»"); + st.addEventListener("click", function(){ var l=allegatiPuliti(state.allegatiRichiesti)||[]; if(!l[i]) return; l[i].s=(l[i].s==="ricevuto") ? "da chiedere" : "ricevuto"; state.allegatiRichiesti=l; save(); renderAllegati(); }); + x.addEventListener("click", function(){ var l=allegatiPuliti(state.allegatiRichiesti)||[]; l.splice(i,1); if(l.length) state.allegatiRichiesti=l; else delete state.allegatiRichiesti; save(); renderAllegati(); }); + li.appendChild(nm); li.appendChild(st); li.appendChild(x); ul.appendChild(li); }); } +(function(){ + var b=document.getElementById("pdfAllegatiAgg"), n=document.getElementById("pdfAllegatiNome"); if(!b || !n) return; + function aggiungi(){ var t=n.value.trim(); if(!t) return; var l=allegatiPuliti(state.allegatiRichiesti)||[]; if(l.length>=30) return; l.push({n:t, s:"da chiedere"}); state.allegatiRichiesti=allegatiPuliti(l); n.value=""; save(); renderAllegati(); } + b.addEventListener("click", aggiungi); n.addEventListener("keydown", function(e){ if(e.key==="Enter"){ e.preventDefault(); aggiungi(); } }); +})(); function pdfGateErrori(run){ /* Gate sugli errori critici (M-04) — spostato qui dall'interno di exportPdf (audit 27/07). Prima era un confirm() NATIVO del browser: chi spunta «impedisci a questa pagina di creare altre diff --git a/app/index.html b/app/index.html index 98f1b34..d3dd8af 100644 --- a/app/index.html +++ b/app/index.html @@ -1656,6 +1656,15 @@ .pdf-diff{margin:10px 0;padding:10px 12px;border:1px solid var(--border,#e5e7eb);border-radius:10px;background:var(--surface-2,#f8fafc)} .pdf-diff-t{font-weight:700;font-size:13px;margin-bottom:4px} .pdf-diff-l{margin:0 0 8px;padding-left:18px;font-size:13px;line-height:1.5} + .pdf-all{margin:10px 0} + .pdf-all-t{font-weight:700;font-size:13px;margin-bottom:4px} + .pdf-all-l{list-style:none;margin:0 0 6px;padding:0} + .pdf-all-l li{display:flex;gap:8px;align-items:center;padding:2px 0;font-size:13px} + .pdf-all-l li span{flex:1 1 auto} + .pdf-all-st{font-size:11px;padding:1px 8px;border-radius:999px;background:#fef3c7;color:#92400e} + .pdf-all-st.ok{background:#dcfce7;color:#166534} + .pdf-all-add{display:flex;gap:8px} + .pdf-all-add input{flex:1 1 auto} .pkg-segno{margin-left:8px;font-size:11px;padding:1px 7px;border-radius:999px;background:var(--surface-2,#f1f5f9);color:var(--text-3)} .pkg-segno-cambiato{background:#fef3c7;color:#92400e} .pkg-segno-nuovo{background:#dcfce7;color:#166534} @@ -6467,6 +6476,12 @@

Esporta PDF in scala

+ +
+
Allegati richiesti — facoltativo: se qualcuno resta «da chiedere» te lo ricordo prima di esportare
+
    +
    +
    diff --git a/index.template.html b/index.template.html index 309a4d5..27063dd 100644 --- a/index.template.html +++ b/index.template.html @@ -2431,6 +2431,12 @@

    Esporta PDF in scala

    + +
    +
    Allegati richiesti — facoltativo: se qualcuno resta «da chiedere» te lo ricordo prima di esportare
    +
      +
      +
      @@ -6732,7 +6738,7 @@

      Esporta PDF in scala

      data e contatti non erano mai diversi. Restano nello stato di ogni variante — lo schema e i file già salvati non cambiano — ma a ogni sincronizzazione quelli della variante attiva si scrivono nelle altre. */ -var CAMPI_DOCUMENTO=["pdfOrganico","quoteDaTerra","titolo","luogo","evDate","evTime","tipoEvento","techContact","contacts","pdfHeader","pdfPesi","versione","latiTeatro","sonorizzazione","orari","scaletta","customModels"]; /* pdfPesi (06/10/2026): i kg nel PDF valgono per tutto il documento · scaletta (10/10): i brani sono della serata, non della scena · customModels (13/10): i modelli dell'elemento personalizzato sono del progetto, uguali in ogni scena (assente se vuoto) */ +var CAMPI_DOCUMENTO=["allegatiRichiesti","pdfOrganico","quoteDaTerra","titolo","luogo","evDate","evTime","tipoEvento","techContact","contacts","pdfHeader","pdfPesi","versione","latiTeatro","sonorizzazione","orari","scaletta","customModels"]; /* pdfPesi (06/10/2026): i kg nel PDF valgono per tutto il documento · scaletta (10/10): i brani sono della serata, non della scena · customModels (13/10): i modelli dell'elemento personalizzato sono del progetto, uguali in ogni scena (assente se vuoto) */ function propagaCampiDocumento(v){ var src=v&&v.state; if(!src) return; VARIANTS.forEach(function(o){ @@ -9897,6 +9903,7 @@

      Esporta PDF in scala

      if(s.latiTeatro!==true) delete s.latiTeatro; /* giardino e corte: spenti di serie, solo un true esplicito li accende (12/11/2026) */ if(s.pdfOrganico!==true) delete s.pdfOrganico; /* «Organico: N + M» nel cartiglio (13/11): solo un true esplicito la accende */ if(s.quoteDaTerra!==true) delete s.quoteDaTerra; /* quote delle pedane «da terra» (13/11): solo un true esplicito le accende */ + s.allegatiRichiesti=allegatiPuliti(s.allegatiRichiesti); if(!s.allegatiRichiesti) delete s.allegatiRichiesti; /* «Allegati richiesti» (S14, 13/11): solo se l'utente li usa */ if(s.pdfPesi!==true) delete s.pdfPesi; /* kg nel PDF: solo un true esplicito li accende (06/10/2026) */ var _vers=(typeof s.versione==="number"||typeof s.versione==="string") ? +s.versione : NaN; /* versione del documento (12/11): intero 1-999, altrimenti assente */ if(_vers%1===0 && _vers>=1 && _vers<=999) s.versione=_vers; else delete s.versione; @@ -19110,6 +19117,30 @@

      Esporta PDF in scala

      add("data-nel-titolo","Il titolo dice "+dt[0].testo+" ma la data dell'evento è "+e[2]+"/"+e[1]+"/"+e[0]+"."); } } + /* 6b · la stessa data sbagliata dentro la scaletta o nei testi del rider (S14, 13/11): organico e testi riusati da una serata vecchia */ + if(state.evDate){ + var _e2=state.evDate.split("-"), _evTxt=_e2[2]+"/"+_e2[1]+"/"+_e2[0]; + var _sc=[]; try{ (scalettaNorm(state.scaletta)||[]).forEach(function(v){ if(v){ _sc.push(v.titolo); _sc.push(v.note); } }); }catch(_e){} + var _ds=[]; _sc.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _ds.push(d.testo); }); }); + if(_ds.length) add("data-nella-scaletta","La scaletta dice "+_ds[0]+" ma la data dell'evento è "+_evTxt+"."); + var _rt=[]; var _ri=(state.rider && typeof state.rider==="object") ? state.rider : {}; Object.keys(_ri).forEach(function(k){ if(typeof _ri[k]==="string") _rt.push(_ri[k]); }); + var _dr=[]; _rt.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _dr.push(d.testo); }); }); + if(_dr.length) add("data-nel-rider","Nel testo del rider c'è "+_dr[0]+" ma la data dell'evento è "+_evTxt+"."); + } + /* 6c · stessa versione già esportata, contenuto diverso (S14): chi ha il file ha qualcosa di diverso con lo stesso nome */ + try{ + var _vN=versioneNumero(); + if(_vN){ + var _voce=(storicoPulito(state.storico)||[]).filter(function(e){ return e.v===_vN; })[0]; + if(_voce && _voce.r && _voce.r.h){ + var _cur=impronteDocumenti(), _cambiati=Object.keys(_cur).filter(function(k){ return (k in _voce.r.h) && _voce.r.h[k]!==_cur[k]; }); + if(_cambiati.length) add("stessa-versione-cambiata","La v"+_vN+" è già stata esportata e da allora sono cambiati: "+_cambiati.map(function(k){ return DOC_NOMI[k]||k; }).join(", ")+". Chi ha il file della v"+_vN+" ha un contenuto diverso con lo stesso nome: aumenta la versione."); + } + } + }catch(_e){} + /* 6d · allegati richiesti ancora «da chiedere» (S14): solo se l'utente ha scritto l'elenco */ + var _al=allegatiPuliti(state.allegatiRichiesti), _dc=(_al||[]).filter(function(a){ return a.s==="da chiedere"; }); + if(_dc.length) add("allegati-da-chiedere",(_dc.length===1 ? "Allegato ancora da chiedere: " : _dc.length+" allegati ancora da chiedere: ")+_dc.map(function(a){ return a.n; }).join(", ")+"."); /* 7 · manca il luogo (L1-b, dietro «Controllo tecnico»): titolo o data scritti ma il luogo è vuoto, quindi il PDF esce senza il posto */ /* 8 · violini senza sezione accanto a violini con sezione (organico): il foglio non può dire I o II (Simone, 12/11) */ var _og=organicoDati(); @@ -40202,10 +40233,26 @@

      Esporta PDF in scala

      rows.forEach(function(r){ if(r.reserved || r.spare) return; can++; var m=r.micOff ? "" : String(r.mic||""); if(m){ m=m.slice(0,40); mic[m]=(mic[m]||0)+1; } }); try{ mix=busList().count; }catch(_e){} var st=state.stage||{}; - return { f:STORICO_FORMATO, pal:[Math.round(+st.w||0), Math.round(+st.d||0)], el:el, ped:ped, can:can, mix:mix, mic:mic, h:impronteDocumenti() }; + return { f:STORICO_FORMATO, pal:[Math.round(+st.w||0), Math.round(+st.d||0)], el:el, ped:ped, can:can, mix:mix, mic:mic, h:impronteDocumenti(), ri:impronteClausoleRider() }; } /* Impronta di CIASCUN documento del pacchetto (S10, richiesta di Simone 13/11: «cambiato / invariato da vN-1» accanto a ogni file): un hash breve del contenuto che quel documento stampa (non del progetto intero: un'asta in più non cambia il rider). Dentro il riassunto, quindi nello storico. */ +var DOC_NOMI={stageplot:"stage plot", inputlist:"channel list", outputlist:"output list", rider:"rider", organico:"organico", backline:"backline", lightslist:"lista luci", scaletta:"scaletta"}; +/* ═══ CONTROLLI DI COERENZA 2 (13/11/2026, specifica S14) ═══ «Allegati richiesti»: elenco libero (≤30 voci) con stato «ricevuto» / «da chiedere»; + se resta qualcosa «da chiedere» lo dice «Prima di esportare». Solo se l'utente lo usa (assente di serie). */ +function allegatiPuliti(l){ + if(!Array.isArray(l)) return null; + var out=l.filter(function(a){ return a && typeof a==="object" && typeof a.n==="string" && a.n.trim(); }).slice(0,30) + .map(function(a){ return {n:a.n.trim().slice(0,60), s:(a.s==="ricevuto") ? "ricevuto" : "da chiedere"}; }); + return out.length ? out : null; +} +function hashBreve(x){ var j; try{ j=JSON.stringify(x); }catch(_e){ j=""; } j=j||""; var n=5381; for(var i=0;i>>0).toString(36); } +function impronteClausoleRider(){ + var r=(state.rider && typeof state.rider==="object") ? state.rider : {}, out={}; + Object.keys(r).forEach(function(k){ if(typeof r[k]==="string" && r[k].trim()) out[k]=hashBreve(r[k]); }); + return out; +} +var RIDER_CLAUSOLE_NOMI={sistema:"sistema audio", luci:"luci", personale:"personale", sedie:"sedie", note:"note", camerini:"camerini", parcheggi:"parcheggi"}; function impronteDocumenti(){ function h(x){ var j; try{ j=JSON.stringify(x); }catch(_e){ j=""; } j=j||""; var n=5381; for(var i=0;i>>0).toString(36); } function g(f){ try{ return h(f()); }catch(_e){ return ""; } } @@ -40265,6 +40312,12 @@

      Esporta PDF in scala

      Object.keys(ma).concat(Object.keys(mb)).forEach(function(k){ ms[k]=1; }); Object.keys(ms).sort().forEach(function(k){ var d=(mb[k]||0)-(ma[k]||0); if(d) dm.push((d>0?"+":"−")+Math.abs(d)+" "+k); }); if(dm.length) righe.push("Microfoni: "+dm.join(", ")); + /* S14: clausole del rider cambiate (solo se entrambi i riassunti le hanno: i vecchi non hanno `ri`) */ + if(a.ri && b.ri){ + var rk={}, rc=[]; Object.keys(a.ri).concat(Object.keys(b.ri)).forEach(function(k){ rk[k]=1; }); + Object.keys(rk).sort().forEach(function(k){ if(a.ri[k]!==b.ri[k]) rc.push((RIDER_CLAUSOLE_NOMI[k]||k)+(!(k in a.ri) ? " (aggiunta)" : (!(k in b.ri) ? " (tolta)" : ""))); }); + if(rc.length) righe.push("Clausole del rider cambiate: "+rc.join(", ")); + } return righe; } function diffVersioniDati(){ @@ -40342,7 +40395,24 @@

      Esporta PDF in scala

      el.textContent=tx; el.hidden=!tx; el.classList.toggle("vecchio", /^Modificato/.test(tx)); try{ renderDiffVersioni(); }catch(_e){} + try{ renderAllegati(); }catch(_e){} +} +function renderAllegati(){ + var ul=document.getElementById("pdfAllegatiLista"); if(!ul) return; + ul.innerHTML=""; + (allegatiPuliti(state.allegatiRichiesti)||[]).forEach(function(a,i){ + var li=document.createElement("li"), nm=document.createElement("span"), st=document.createElement("button"), x=document.createElement("button"); + nm.textContent=a.n; st.type="button"; st.className="btn pdf-all-st"+(a.s==="ricevuto" ? " ok" : ""); st.textContent=a.s; st.title="Clic per cambiare: ricevuto / da chiedere"; + x.type="button"; x.className="btn pdf-all-x"; x.textContent="\u2715"; x.setAttribute("aria-label","Togli «"+a.n+"»"); + st.addEventListener("click", function(){ var l=allegatiPuliti(state.allegatiRichiesti)||[]; if(!l[i]) return; l[i].s=(l[i].s==="ricevuto") ? "da chiedere" : "ricevuto"; state.allegatiRichiesti=l; save(); renderAllegati(); }); + x.addEventListener("click", function(){ var l=allegatiPuliti(state.allegatiRichiesti)||[]; l.splice(i,1); if(l.length) state.allegatiRichiesti=l; else delete state.allegatiRichiesti; save(); renderAllegati(); }); + li.appendChild(nm); li.appendChild(st); li.appendChild(x); ul.appendChild(li); }); } +(function(){ + var b=document.getElementById("pdfAllegatiAgg"), n=document.getElementById("pdfAllegatiNome"); if(!b || !n) return; + function aggiungi(){ var t=n.value.trim(); if(!t) return; var l=allegatiPuliti(state.allegatiRichiesti)||[]; if(l.length>=30) return; l.push({n:t, s:"da chiedere"}); state.allegatiRichiesti=allegatiPuliti(l); n.value=""; save(); renderAllegati(); } + b.addEventListener("click", aggiungi); n.addEventListener("keydown", function(e){ if(e.key==="Enter"){ e.preventDefault(); aggiungi(); } }); +})(); function pdfGateErrori(run){ /* Gate sugli errori critici (M-04) — spostato qui dall'interno di exportPdf (audit 27/07). Prima era un confirm() NATIVO del browser: chi spunta «impedisci a questa pagina di creare altre diff --git a/src/styles.css b/src/styles.css index 4714fdf..86dc1a6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1578,6 +1578,15 @@ svg.names-hidden .orc-name{display:none} .pdf-diff{margin:10px 0;padding:10px 12px;border:1px solid var(--border,#e5e7eb);border-radius:10px;background:var(--surface-2,#f8fafc)} .pdf-diff-t{font-weight:700;font-size:13px;margin-bottom:4px} .pdf-diff-l{margin:0 0 8px;padding-left:18px;font-size:13px;line-height:1.5} + .pdf-all{margin:10px 0} + .pdf-all-t{font-weight:700;font-size:13px;margin-bottom:4px} + .pdf-all-l{list-style:none;margin:0 0 6px;padding:0} + .pdf-all-l li{display:flex;gap:8px;align-items:center;padding:2px 0;font-size:13px} + .pdf-all-l li span{flex:1 1 auto} + .pdf-all-st{font-size:11px;padding:1px 8px;border-radius:999px;background:#fef3c7;color:#92400e} + .pdf-all-st.ok{background:#dcfce7;color:#166534} + .pdf-all-add{display:flex;gap:8px} + .pdf-all-add input{flex:1 1 auto} .pkg-segno{margin-left:8px;font-size:11px;padding:1px 7px;border-radius:999px;background:var(--surface-2,#f1f5f9);color:var(--text-3)} .pkg-segno-cambiato{background:#fef3c7;color:#92400e} .pkg-segno-nuovo{background:#dcfce7;color:#166534} diff --git a/test/engines.test.mjs b/test/engines.test.mjs index ea91818..82e2538 100644 --- a/test/engines.test.mjs +++ b/test/engines.test.mjs @@ -94,6 +94,7 @@ function reset() { delete A.state.scaletta; /* scaletta (10/10): campo che esiste solo con almeno una voce */ delete A.state.pdfOrganico; /* «Organico: N + M» nel cartiglio (13/11): opzione spenta di serie */ delete A.state.quoteDaTerra; /* quote «da terra» (13/11): opzione spenta di serie; un test che la accende non deve lasciarla ai successivi */ + delete A.state.allegatiRichiesti; /* «Allegati richiesti» (13/11, S14): solo se l'utente li usa */ delete A.state.storico; /* storico delle versioni (13/11, S10): si scrive solo esportando; un test che esporta non deve lasciarlo ai successivi */ delete A.state.versione; /* versione del documento (12/11): campo che esiste solo se impostata; un test che la imposta non deve lasciarla ai successivi */ delete A.state.customModels; /* modelli dell'elemento personalizzato (13/10): dal primo Salva di un elemento nuovo nasce anche il modello (Ruling 17), e un test che salva non deve lasciarli ai successivi */ @@ -5035,7 +5036,7 @@ t("Differenze versioni: il confronto in dettaglio (palco solo in profondità, or t("Differenze versioni: il riquadro dell'Esporta (nascosto di serie, solo lettura) e «Copia testo» sono nell'HTML e collegati", () => { const html = readFileSync(join(root, "app/index.html"), "utf8"); ok(/id="pdfDiff"[^>]*hidden/.test(html) && ["pdfDiffT", "pdfDiffL", "pdfDiffMotivo", "pdfDiffCopia"].every((i) => html.indexOf('id="' + i + '"') > -1), "il riquadro nascosto di serie, con titolo, elenco, motivo e pulsante"); - ok(/try\{ renderDiffVersioni\(\); \}catch\(_e\)\{\}\n\}/.test(appjs), "si aggiorna a ogni apertura dell'Esporta (renderPdfUltimo)"); + ok(/try\{ renderDiffVersioni\(\); \}catch\(_e\)\{\}\n try\{ renderAllegati\(\); \}catch\(_e\)\{\}\n\}/.test(appjs), "si aggiorna a ogni apertura dell'Esporta (renderPdfUltimo)"); ok(/navigator\.clipboard\.writeText\(t\)\.then\(function\(\)\{ showToast\("Testo copiato"\); \}/.test(appjs), "«Copia testo» copia negli appunti"); ok(/var dati=diffVersioniDati\(\); box\.hidden=!dati;/.test(appjs), "senza versione precedente il riquadro non compare"); }); From c31acc78ac78ee254fa6f5c44628a9aed02e269c Mon Sep 17 00:00:00 2001 From: simone castellan Date: Fri, 13 Nov 2026 16:04:29 +0100 Subject: [PATCH 2/3] test(S14): date, stessa versione, allegati, clausole --- test/engines.test.mjs | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/engines.test.mjs b/test/engines.test.mjs index 82e2538..1f0aa71 100644 --- a/test/engines.test.mjs +++ b/test/engines.test.mjs @@ -5430,6 +5430,62 @@ t("Quota da terra: opzione nel pannello del blocco, documento (non della scena), ok(/\+quotaDaTerraTesto\(it\) : scalaQuota\(it\)/.test(appjs), "l'etichetta della pedana la scrive"); const f0 = A.firmaPdf(A.state); A.state.quoteDaTerra = true; ok(A.firmaPdf(A.state) !== f0, "è contenuto del documento: cambia l'impronta (il PDF cambia davvero)"); reset(); }); +/* ═══ Controlli di coerenza 2 (13/11/2026, S14) ═══ */ +const ccId = (id) => A.controlliPrimaDelExport().filter((x) => x.id === id); +t("Coerenza 2: una data diversa da quella dell'evento nella scaletta o nel testo del rider; mai se è la stessa, mai senza data dell'evento", () => { + reset(); A.state.evDate = "2026-12-01"; A.state.titolo = "Gala"; + eq(ccId("data-nella-scaletta").length + ccId("data-nel-rider").length, 0, "senza scaletta né rider: niente"); + A.state.scaletta = [{ tipo: "brano", titolo: "Gran finale", durata: "3:30", note: "" }]; + eq(ccId("data-nella-scaletta").length, 0, "scaletta senza date: niente"); + A.state.scaletta = [{ tipo: "brano", titolo: "Gran finale 10/11/2024", durata: "3:30", note: "" }]; + const a1 = ccId("data-nella-scaletta"); eq(a1.length, 1, "data diversa nel titolo di un brano"); eq(a1[0].testo, "La scaletta dice 10/11/2024 ma la data dell'evento è 01/12/2026.", "il testo"); + A.state.scaletta = [{ tipo: "brano", titolo: "Brano", durata: "3:30", note: "Trieste 12.09.2021" }]; eq(ccId("data-nella-scaletta").length, 1, "e anche nelle note"); + A.state.scaletta = [{ tipo: "brano", titolo: "Brano 1/12/2026", durata: "3:30", note: "" }]; eq(ccId("data-nella-scaletta").length, 0, "la data dell'evento scritta in un altro modo: niente"); + A.state.scaletta = [{ tipo: "brano", titolo: "Brano 10/11/2024", durata: "3:30", note: "" }, { tipo: "brano", titolo: "Altro 2/2/2020", durata: "3:30", note: "" }]; eq(ccId("data-nella-scaletta").length, 1, "una sola riga, con la prima data"); + delete A.state.evDate; eq(ccId("data-nella-scaletta").length, 0, "senza data dell'evento: niente da confrontare"); A.state.evDate = "2026-12-01"; delete A.state.scaletta; + A.state.rider = { sistema: "Impianto come l'11/09/2004", luci: "bianco diffuso", personale: 5 }; + const r1 = ccId("data-nel-rider"); eq(r1.length, 1, "data diversa in una clausola"); eq(r1[0].testo, "Nel testo del rider c'è 11/09/2004 ma la data dell'evento è 01/12/2026.", "il testo"); + A.state.rider = { sistema: "Serata del 01/12/2026", note: "ok" }; eq(ccId("data-nel-rider").length, 0, "stessa data: niente"); + A.state.rider = {}; eq(ccId("data-nel-rider").length, 0, "rider vuoto: niente"); + eq(ccId("data-nel-titolo").length, 0, "la regola sul titolo (S1) resta"); A.state.titolo = "Gala 10/11/2024"; eq(ccId("data-nel-titolo").length, 1, "e funziona ancora"); + reset(); +}); +t("Coerenza 2: stessa versione già esportata ma contenuto cambiato → avviso con i documenti cambiati; non con versione nuova, né senza storico o impronte", () => { + dvFix(3); A.state.versione = 3; eq(ccId("stessa-versione-cambiata").length, 0, "mai esportata: niente"); + A.storicoRegistra(); eq(ccId("stessa-versione-cambiata").length, 0, "esportata e invariata: niente"); + add("astamic", 900, 600); A.__cabRes = null; + const w = ccId("stessa-versione-cambiata"); eq(w.length, 1, "cambiato dopo l'export della stessa versione"); + ok(/^La v3 è già stata esportata e da allora sono cambiati: stage plot, channel list\./.test(w[0].testo) && /aumenta la versione\.$/.test(w[0].testo), "dice quali documenti: " + w[0].testo); + A.state.versione = 4; eq(ccId("stessa-versione-cambiata").length, 0, "versione nuova: nessun avviso"); + A.state.versione = 3; A.storicoRegistra(); eq(ccId("stessa-versione-cambiata").length, 0, "riesportata: di nuovo allineata"); + delete A.state.storico[0].r.h; add("astamic", 960, 600); A.__cabRes = null; eq(ccId("stessa-versione-cambiata").length, 0, "voce senza impronte (storico vecchio): niente"); + delete A.state.versione; A.state.titolo = "Gala"; eq(ccId("stessa-versione-cambiata").length, 0, "senza versione: niente"); reset(); +}); +t("Coerenza 2: «Allegati richiesti» — elenco libero con stato, avviso se resta «da chiedere», solo se usato; normalizzazione; interfaccia", () => { + reset(); eq(ccId("allegati-da-chiedere").length, 0, "senza elenco: niente"); + const pul = A.allegatiPuliti([{ n: " Planimetria ", s: "ricevuto" }, { n: "Rider band" }, { n: "", s: "x" }, null, 5, { n: "x".repeat(100), s: "ricevuto" }]); + eq(pul.map((a) => [a.n.length, a.s]), [[11, "ricevuto"], [10, "da chiedere"], [60, "ricevuto"]], "pulizia: spazi tolti, vuoti scartati, nomi tagliati a 60, stato di serie «da chiedere»"); + eq(A.allegatiPuliti([]), null, "vuoto: null"); eq(A.allegatiPuliti("ciao"), null, "non un elenco: null"); eq(A.allegatiPuliti(Array.from({ length: 40 }, (_, i) => ({ n: "a" + i }))).length, 30, "al massimo 30"); + A.state.allegatiRichiesti = [{ n: "Planimetria", s: "ricevuto" }]; eq(ccId("allegati-da-chiedere").length, 0, "tutto ricevuto: niente"); + A.state.allegatiRichiesti = [{ n: "Planimetria", s: "da chiedere" }]; eq(ccId("allegati-da-chiedere")[0].testo, "Allegato ancora da chiedere: Planimetria.", "uno da chiedere"); + A.state.allegatiRichiesti = [{ n: "Planimetria", s: "da chiedere" }, { n: "Piano luci", s: "da chiedere" }, { n: "Rider band", s: "ricevuto" }]; eq(ccId("allegati-da-chiedere")[0].testo, "2 allegati ancora da chiedere: Planimetria, Piano luci.", "due da chiedere, il ricevuto non conta"); + const st = JSON.parse(JSON.stringify(A.state)); eq(A.normalizeState(st).allegatiRichiesti.length, 3, "normalizeState li tiene"); st.allegatiRichiesti = [{ n: "", s: "x" }]; ok(!("allegatiRichiesti" in A.normalizeState(st)), "voci vuote: il campo sparisce"); st.allegatiRichiesti = "ciao"; ok(!("allegatiRichiesti" in A.normalizeState(st)), "non un elenco: via"); + const html = readFileSync(join(root, "app/index.html"), "utf8"); + ok(/id="pdfAllegati"/.test(html) && ["pdfAllegatiLista", "pdfAllegatiNome", "pdfAllegatiAgg"].every((i) => html.indexOf('id="' + i + '"') > -1), "l'elenco nell'Esporta: lista, nome, Aggiungi"); + ok(/l\.push\(\{n:t, s:"da chiedere"\}\)/.test(appjs) && /l\[i\]\.s=\(l\[i\]\.s==="ricevuto"\) \? "da chiedere" : "ricevuto"/.test(appjs), "si aggiunge «da chiedere» e il clic alterna lo stato"); + ok(/var CAMPI_DOCUMENTO=\[[^\]]*"allegatiRichiesti"/.test(appjs), "campo del documento"); reset(); +}); +t("Coerenza 2: «Cosa cambia da vN a vM» elenca le clausole del rider cambiate (con aggiunta e tolta); non se uguali o se il vecchio riassunto non le ha", () => { + dvFix(3); A.state.versione = 3; A.state.rider = { sistema: "Line array", note: "uno" }; A.storicoRegistra(); + A.state.versione = 4; eq(A.diffVersioniDati().righe.filter((x) => /Clausole/.test(x)), [], "uguali: nessuna riga"); + A.state.rider = { sistema: "Line array e sub", luci: "bianco", note: "uno" }; + eq(A.diffVersioniDati().righe.filter((x) => /Clausole/.test(x)), ["Clausole del rider cambiate: luci (aggiunta), sistema audio"], "cambiata e aggiunta, con i nomi in italiano"); + A.state.rider = { sistema: "Line array" }; eq(A.diffVersioniDati().righe.filter((x) => /Clausole/.test(x)), ["Clausole del rider cambiate: note (tolta)"], "tolta"); + delete A.state.storico[0].r.ri; A.state.rider = { sistema: "x" }; eq(A.diffVersioniDati().righe.filter((x) => /Clausole/.test(x)), [], "riassunto vecchio senza clausole: niente di inventato"); + eq(A.riassuntoDiff({ ri: { a: "1" } }, { ri: { a: "1" } }), [], "puro: uguali"); eq(A.riassuntoDiff({ ri: { zz: "1" } }, { ri: { zz: "2" } }), ["Clausole del rider cambiate: zz"], "una clausola sconosciuta col suo nome"); + eq(A.impronteClausoleRider(), { sistema: A.hashBreve("x") }, "impronte brevi delle sole clausole di testo non vuote"); A.state.rider = { sistema: " ", luci: 4 }; eq(A.impronteClausoleRider(), {}, "vuote o non testo: niente"); + reset(); +}); t("Output list: bus console -> porte out (auto consecutive, pin, conflitti, mai duplicati)", () => { reset(); A.state.cab.on = true; A.state.cab.mode = "auto"; A.state.cab.mixer = "dm3"; From 82c47b0b4865c3cfe9e5876b45fb7aac5a639c7e Mon Sep 17 00:00:00 2001 From: simone castellan Date: Fri, 13 Nov 2026 16:22:13 +0100 Subject: [PATCH 3/3] S14: niente controllo date sul testo del rider (falso allarme su Valdagno); test e mutazioni --- app.js | 6 ++---- index.template.html | 6 ++---- test/engines.test.mjs | 13 ++++++------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/app.js b/app.js index 3a6ce1d..18a0ef9 100644 --- a/app.js +++ b/app.js @@ -16869,15 +16869,13 @@ function controlliPrimaDelExport(){ add("data-nel-titolo","Il titolo dice "+dt[0].testo+" ma la data dell'evento è "+e[2]+"/"+e[1]+"/"+e[0]+"."); } } - /* 6b · la stessa data sbagliata dentro la scaletta o nei testi del rider (S14, 13/11): organico e testi riusati da una serata vecchia */ + /* 6b · la stessa data sbagliata dentro la scaletta (S14, 13/11): scaletta riusata da una serata vecchia */ if(state.evDate){ var _e2=state.evDate.split("-"), _evTxt=_e2[2]+"/"+_e2[1]+"/"+_e2[0]; var _sc=[]; try{ (scalettaNorm(state.scaletta)||[]).forEach(function(v){ if(v){ _sc.push(v.titolo); _sc.push(v.note); } }); }catch(_e){} var _ds=[]; _sc.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _ds.push(d.testo); }); }); if(_ds.length) add("data-nella-scaletta","La scaletta dice "+_ds[0]+" ma la data dell'evento è "+_evTxt+"."); - var _rt=[]; var _ri=(state.rider && typeof state.rider==="object") ? state.rider : {}; Object.keys(_ri).forEach(function(k){ if(typeof _ri[k]==="string") _rt.push(_ri[k]); }); - var _dr=[]; _rt.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _dr.push(d.testo); }); }); - if(_dr.length) add("data-nel-rider","Nel testo del rider c'è "+_dr[0]+" ma la data dell'evento è "+_evTxt+"."); + /* (il testo del rider NON si controlla: cita volutamente date di altre edizioni, «sullo stage plot di Valdagno 22/12/2025»: falso allarme provato su un progetto vero, 13/11) */ } /* 6c · stessa versione già esportata, contenuto diverso (S14): chi ha il file ha qualcosa di diverso con lo stesso nome */ try{ diff --git a/index.template.html b/index.template.html index 27063dd..95a026b 100644 --- a/index.template.html +++ b/index.template.html @@ -19117,15 +19117,13 @@

      Esporta PDF in scala

      add("data-nel-titolo","Il titolo dice "+dt[0].testo+" ma la data dell'evento è "+e[2]+"/"+e[1]+"/"+e[0]+"."); } } - /* 6b · la stessa data sbagliata dentro la scaletta o nei testi del rider (S14, 13/11): organico e testi riusati da una serata vecchia */ + /* 6b · la stessa data sbagliata dentro la scaletta (S14, 13/11): scaletta riusata da una serata vecchia */ if(state.evDate){ var _e2=state.evDate.split("-"), _evTxt=_e2[2]+"/"+_e2[1]+"/"+_e2[0]; var _sc=[]; try{ (scalettaNorm(state.scaletta)||[]).forEach(function(v){ if(v){ _sc.push(v.titolo); _sc.push(v.note); } }); }catch(_e){} var _ds=[]; _sc.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _ds.push(d.testo); }); }); if(_ds.length) add("data-nella-scaletta","La scaletta dice "+_ds[0]+" ma la data dell'evento è "+_evTxt+"."); - var _rt=[]; var _ri=(state.rider && typeof state.rider==="object") ? state.rider : {}; Object.keys(_ri).forEach(function(k){ if(typeof _ri[k]==="string") _rt.push(_ri[k]); }); - var _dr=[]; _rt.forEach(function(t){ dateNelTitolo(t).forEach(function(d){ if(d.iso!==state.evDate) _dr.push(d.testo); }); }); - if(_dr.length) add("data-nel-rider","Nel testo del rider c'è "+_dr[0]+" ma la data dell'evento è "+_evTxt+"."); + /* (il testo del rider NON si controlla: cita volutamente date di altre edizioni, «sullo stage plot di Valdagno 22/12/2025»: falso allarme provato su un progetto vero, 13/11) */ } /* 6c · stessa versione già esportata, contenuto diverso (S14): chi ha il file ha qualcosa di diverso con lo stesso nome */ try{ diff --git a/test/engines.test.mjs b/test/engines.test.mjs index 1f0aa71..cfca36d 100644 --- a/test/engines.test.mjs +++ b/test/engines.test.mjs @@ -5432,7 +5432,7 @@ t("Quota da terra: opzione nel pannello del blocco, documento (non della scena), }); /* ═══ Controlli di coerenza 2 (13/11/2026, S14) ═══ */ const ccId = (id) => A.controlliPrimaDelExport().filter((x) => x.id === id); -t("Coerenza 2: una data diversa da quella dell'evento nella scaletta o nel testo del rider; mai se è la stessa, mai senza data dell'evento", () => { +t("Coerenza 2: una data diversa da quella dell'evento nella scaletta (non nel testo del rider); mai se è la stessa, mai senza data dell'evento", () => { reset(); A.state.evDate = "2026-12-01"; A.state.titolo = "Gala"; eq(ccId("data-nella-scaletta").length + ccId("data-nel-rider").length, 0, "senza scaletta né rider: niente"); A.state.scaletta = [{ tipo: "brano", titolo: "Gran finale", durata: "3:30", note: "" }]; @@ -5441,12 +5441,9 @@ t("Coerenza 2: una data diversa da quella dell'evento nella scaletta o nel testo const a1 = ccId("data-nella-scaletta"); eq(a1.length, 1, "data diversa nel titolo di un brano"); eq(a1[0].testo, "La scaletta dice 10/11/2024 ma la data dell'evento è 01/12/2026.", "il testo"); A.state.scaletta = [{ tipo: "brano", titolo: "Brano", durata: "3:30", note: "Trieste 12.09.2021" }]; eq(ccId("data-nella-scaletta").length, 1, "e anche nelle note"); A.state.scaletta = [{ tipo: "brano", titolo: "Brano 1/12/2026", durata: "3:30", note: "" }]; eq(ccId("data-nella-scaletta").length, 0, "la data dell'evento scritta in un altro modo: niente"); - A.state.scaletta = [{ tipo: "brano", titolo: "Brano 10/11/2024", durata: "3:30", note: "" }, { tipo: "brano", titolo: "Altro 2/2/2020", durata: "3:30", note: "" }]; eq(ccId("data-nella-scaletta").length, 1, "una sola riga, con la prima data"); + A.state.scaletta = [{ tipo: "brano", titolo: "Brano 10/11/2024", durata: "3:30", note: "" }, { tipo: "brano", titolo: "Altro 2/2/2020", durata: "3:30", note: "" }]; eq(ccId("data-nella-scaletta").length, 1, "una sola riga, con la prima data"); ok(/La scaletta dice 10\/11\/2024 ma/.test(ccId("data-nella-scaletta")[0].testo), "la prima data trovata: " + ccId("data-nella-scaletta")[0].testo); delete A.state.evDate; eq(ccId("data-nella-scaletta").length, 0, "senza data dell'evento: niente da confrontare"); A.state.evDate = "2026-12-01"; delete A.state.scaletta; - A.state.rider = { sistema: "Impianto come l'11/09/2004", luci: "bianco diffuso", personale: 5 }; - const r1 = ccId("data-nel-rider"); eq(r1.length, 1, "data diversa in una clausola"); eq(r1[0].testo, "Nel testo del rider c'è 11/09/2004 ma la data dell'evento è 01/12/2026.", "il testo"); - A.state.rider = { sistema: "Serata del 01/12/2026", note: "ok" }; eq(ccId("data-nel-rider").length, 0, "stessa data: niente"); - A.state.rider = {}; eq(ccId("data-nel-rider").length, 0, "rider vuoto: niente"); + A.state.rider = { sistema: "Impianto come l'11/09/2004", note: "sullo stage plot di Valdagno 22/12/2025" }; eq(ccId("data-nel-rider").length, 0, "il testo del rider non si controlla: cita volutamente date di altre edizioni (falso allarme provato su Valdagno)"); eq(ccId("data-nel-titolo").length, 0, "la regola sul titolo (S1) resta"); A.state.titolo = "Gala 10/11/2024"; eq(ccId("data-nel-titolo").length, 1, "e funziona ancora"); reset(); }); @@ -5458,12 +5455,14 @@ t("Coerenza 2: stessa versione già esportata ma contenuto cambiato → avviso c ok(/^La v3 è già stata esportata e da allora sono cambiati: stage plot, channel list\./.test(w[0].testo) && /aumenta la versione\.$/.test(w[0].testo), "dice quali documenti: " + w[0].testo); A.state.versione = 4; eq(ccId("stessa-versione-cambiata").length, 0, "versione nuova: nessun avviso"); A.state.versione = 3; A.storicoRegistra(); eq(ccId("stessa-versione-cambiata").length, 0, "riesportata: di nuovo allineata"); + { const voce = A.state.storico[0]; const hh = voce.r.h; voce.r.h = Object.assign({}, hh); delete voce.r.h.stageplot; delete voce.r.h.inputlist; + eq(ccId("stessa-versione-cambiata").length, 0, "documenti che nell'export precedente non c'erano: non sono «cambiati»"); voce.r.h = hh; } delete A.state.storico[0].r.h; add("astamic", 960, 600); A.__cabRes = null; eq(ccId("stessa-versione-cambiata").length, 0, "voce senza impronte (storico vecchio): niente"); delete A.state.versione; A.state.titolo = "Gala"; eq(ccId("stessa-versione-cambiata").length, 0, "senza versione: niente"); reset(); }); t("Coerenza 2: «Allegati richiesti» — elenco libero con stato, avviso se resta «da chiedere», solo se usato; normalizzazione; interfaccia", () => { reset(); eq(ccId("allegati-da-chiedere").length, 0, "senza elenco: niente"); - const pul = A.allegatiPuliti([{ n: " Planimetria ", s: "ricevuto" }, { n: "Rider band" }, { n: "", s: "x" }, null, 5, { n: "x".repeat(100), s: "ricevuto" }]); + const pul = A.allegatiPuliti([{ n: " Planimetria ", s: "ricevuto" }, { n: "Rider band" }, { n: "", s: "x" }, null, 5, { n: "x".repeat(100), s: "ricevuto" }, { n: 5, s: "ricevuto" }, { n: ["a"], s: "x" }]); eq(pul.map((a) => [a.n.length, a.s]), [[11, "ricevuto"], [10, "da chiedere"], [60, "ricevuto"]], "pulizia: spazi tolti, vuoti scartati, nomi tagliati a 60, stato di serie «da chiedere»"); eq(A.allegatiPuliti([]), null, "vuoto: null"); eq(A.allegatiPuliti("ciao"), null, "non un elenco: null"); eq(A.allegatiPuliti(Array.from({ length: 40 }, (_, i) => ({ n: "a" + i }))).length, 30, "al massimo 30"); A.state.allegatiRichiesti = [{ n: "Planimetria", s: "ricevuto" }]; eq(ccId("allegati-da-chiedere").length, 0, "tutto ricevuto: niente");