aziendasanitaria-utils 1.2.39 → 1.2.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "engines": {
4
4
  "node": ">=14.0.0"
5
5
  },
6
- "version": "1.2.39",
6
+ "version": "1.2.40",
7
7
  "repository": "deduzzo/aziendasanitaria-utils",
8
8
  "description": "Un utility per gestire i flussi sanitari Siciliani e non solo..",
9
9
  "main": "index.js",
@@ -18,7 +18,8 @@
18
18
  "scripts": {
19
19
  "test": "echo \"Error: no test specified\"",
20
20
  "run-private": "node ./private_example.js",
21
- "bun-run-private": "bun ./private_example.js"
21
+ "bun-run-private": "bun ./private_example.js",
22
+ "genera-strutture": "node ./load_strutture.js"
22
23
  },
23
24
  "dependencies": {
24
25
  "@freiraum/msgreader": "^1.1.1",
package/src/Procedure.js CHANGED
@@ -132,8 +132,9 @@ class Procedure {
132
132
  for (let assistito of assistitiNar[codReg].assistiti) {
133
133
  allAssistitiDistrettuali[distretto].nar.push({...assistito, ...medico});
134
134
  }
135
- for (let assistito of assistitiTs[codReg]) {
136
- allAssistitiDistrettuali[distretto].ts.push({...assistito, ...medico});
135
+ for (let assistiti of assistitiTs[codReg] ?? []) {
136
+ if (assistiti)
137
+ allAssistitiDistrettuali[distretto].ts.push({...assistiti, ...medico});
137
138
  }
138
139
  allAssistitiDistrettuali[distretto].codRegNar[codReg] = assistitiNar[codReg].assistiti;
139
140
  allAssistitiDistrettuali[distretto].codRegTs[codReg] = assistitiTs[codReg];
@@ -188,7 +189,7 @@ class Procedure {
188
189
  *
189
190
  * @return {Promise<number>} Returns 0 upon successful processing.
190
191
  */
191
- static async getControlliEsenzione(pathElenco, anno,impostazioniServizi, config={}){
192
+ static async getControlliEsenzione(pathElenco, anno, impostazioniServizi, config = {}) {
192
193
  let {
193
194
  colonnaProtocolli = "PROTOCOLLO",
194
195
  colonnaEsenzione = "ESENZIONE",
@@ -394,7 +395,7 @@ class Procedure {
394
395
  }
395
396
  }
396
397
  }
397
- if (Object.values(outData) >0)
398
+ if (Object.values(outData) > 0)
398
399
  outFinal.push(outData);
399
400
  da = da.add(1, "month");
400
401
  } while (da.isSameOrBefore(a) && !singoloCedolino);
package/src/Utils.js CHANGED
@@ -853,13 +853,67 @@ const riunisciJsonDaTag = async (path, tag, filter = null) => {
853
853
  return out;
854
854
  }
855
855
 
856
- const riunisciExcelDaTag = async (path, tag, filter = null) => {
857
- let files = getAllFilesRecursive(path, ".xlsx", filter);
858
- let out = {};
859
- out[tag] = [];
856
+ /**
857
+ * Riunisci dati da tutti i file Excel trovati ricorsivamente in `path`.
858
+ *
859
+ * - Cerca tutti i file `.xlsx` all'interno della cartella `path` (ricorsivamente),
860
+ * applicando opzionalmente un `filter` sui nomi dei file.
861
+ * - Per ogni file trovato legge il primo foglio usando `getObjectFromFileExcel`.
862
+ * - Se `config.tags` è un array non vuoto, per ogni `tag` crea un array in output
863
+ * e ci inserisce i valori corrispondenti (per ogni riga prende `row[tag]`).
864
+ * - Se `config.tags` è vuoto, costruisce l'output con tutte le colonne presenti
865
+ * nella prima riga del primo file (`data[0]`).
866
+ * - Se `config.salvaInNuovoFileExcel` è true, salva il risultato in
867
+ * un file `riunito.xlsx` nella cartella `path`.
868
+ *
869
+ * @param {string} path - Cartella radice dove cercare i file Excel.
870
+ * @param {Object} [config={}] - Configurazione opzionale.
871
+ * @param {string[]} [config.tags=[]] - Array di colonne (nomi) da raccogliere.
872
+ * @param {string|null} [config.filter=null] - Filtro per i nomi dei file (opzionale).
873
+ * @param {boolean} [config.salvaInNuovoFileExcel=false] - Se true salva l'output in `riunito.xlsx`.
874
+ * @param {string|null} [config.soloValoriUniciPerCampo=null] - Se specificato, mantiene solo righe con valori unici per questo campo.
875
+ * @returns {Promise<Object>} Oggetto con chiavi corrispondenti ai tag (o colonne) e valori come array dei relativi valori.
876
+ *
877
+ * @example
878
+ * // Unisce le colonne "nome" e "cognome" da tutti gli xlsx nella cartella
879
+ * const out = await riunisciExcelDaTag(`/Users/deduzzo/dati`, { tags: ['nome','cognome'] });
880
+ */
881
+ const riunisciExcelDaTag = async (folderPath, config = {}) => {
882
+ let {
883
+ tags = [],
884
+ filter = null,
885
+ salvaInNuovoFileExcel = false,
886
+ soloValoriUniciPerCampo = null
887
+ } = config;
888
+ let files = getAllFilesRecursive(folderPath, ".xlsx", filter);
889
+ let out = [];
860
890
  for (let file of files) {
861
891
  let data = await getObjectFromFileExcel(file);
862
- out[tag].push(...data);
892
+ if (tags.length === 0) {
893
+ // get all keys from data[0]
894
+ let keys = Object.keys(data[0]);
895
+ tags = keys;
896
+ }
897
+ // add every object of data but only the tags we want, ex. if tag is ['cf', 'data'] we want to put only object type {cf: 'xxx', data: 'yyy'} in out
898
+ for (let obj of data) {
899
+ let objTemp = {};
900
+ tags.forEach(tag => {
901
+ objTemp[tag] = obj[tag];
902
+ });
903
+ // prevent to push empty object
904
+ if (Object.values(objTemp).some(value => value !== ""))
905
+ out.push(objTemp);
906
+ }
907
+ }
908
+ if (soloValoriUniciPerCampo) {
909
+ let out2 = {};
910
+ for (let riga of out)
911
+ if (!out2.hasOwnProperty(riga[soloValoriUniciPerCampo]))
912
+ out2[riga[soloValoriUniciPerCampo]] = riga;
913
+ out = Object.values(out2);
914
+ }
915
+ if (salvaInNuovoFileExcel) {
916
+ await scriviOggettoSuNuovoFileExcel(folderPath + path.sep + "riunito.xlsx", out);
863
917
  }
864
918
  return out;
865
919
  }
package/src/m/FlussoM.js CHANGED
@@ -10,6 +10,7 @@ import {DatiStruttureProgettoTs} from "./DatiStruttureProgettoTs.js";
10
10
  import ExcelJS from "exceljs"
11
11
  import loki from 'lokijs';
12
12
  import {hashFile} from "hasha";
13
+ import * as cheerio from 'cheerio';
13
14
 
14
15
  export class FlussoM {
15
16
  /**
@@ -403,27 +404,76 @@ export class FlussoM {
403
404
  }
404
405
 
405
406
 
406
- #loadStruttureFromFlowlookDB() {
407
- const buffer = fs.readFileSync(this._settings.flowlookDBFilePath);
408
- const reader = new MDBReader(buffer);
407
+ // DEPRECATO: sostituito da #loadStruttureFromLatestFile()
408
+ // #loadStruttureFromFlowlookDB() {
409
+ // const buffer = fs.readFileSync(this._settings.flowlookDBFilePath);
410
+ // const reader = new MDBReader(buffer);
411
+ // const strutture = reader.getTable(this._settings.flowlookDBTableSTS11).getData();
412
+ // let struttureFiltrate = strutture.filter(p => p["CodiceAzienda"] === this._settings.codiceAzienda && p["CodiceRegione"] === this._settings.codiceRegione && (parseInt(p["Anno"]) >= (new Date().getFullYear()) - 2));
413
+ // let mancanti = []
414
+ // let struttureOut = {}
415
+ // struttureFiltrate.forEach(p => {
416
+ // if (this._settings.datiStruttureRegione.comuniDistretti.hasOwnProperty(p["CodiceComune"])) {
417
+ // struttureOut[p['CodiceStruttura']] = {
418
+ // codiceRegione: p['CodiceRegione'],
419
+ // codiceAzienda: p['CodiceAzienda'],
420
+ // denominazione: p['DenominazioneStruttura'],
421
+ // codiceComune: p['CodiceComune'],
422
+ // idDistretto: this._settings.datiStruttureRegione.comuniDistretti[p["CodiceComune"]],
423
+ // dataUltimoAggiornamento: moment(p['DataAggiornamento'], 'DD/MM/YYYY')
424
+ // };
425
+ // } else
426
+ // mancanti.push(p);
427
+ // })
428
+ // return struttureOut;
429
+ // }
430
+
431
+ #loadStruttureFromLatestFile() {
432
+ const struttureDir = path.join("data", "strutture");
433
+ if (!fs.existsSync(struttureDir)) {
434
+ throw new Error(`Cartella ${struttureDir} non trovata. Eseguire prima la generazione delle strutture con load_strutture.js`);
435
+ }
409
436
 
410
- const strutture = reader.getTable(this._settings.flowlookDBTableSTS11).getData();
411
- let struttureFiltrate = strutture.filter(p => p["CodiceAzienda"] === this._settings.codiceAzienda && p["CodiceRegione"] === this._settings.codiceRegione && (parseInt(p["Anno"]) >= (new Date().getFullYear()) - 2));
412
- let mancanti = []
413
- let struttureOut = {}
414
- struttureFiltrate.forEach(p => {
415
- if (this._settings.datiStruttureRegione.comuniDistretti.hasOwnProperty(p["CodiceComune"])) {
416
- struttureOut[p['CodiceStruttura']] = {
417
- codiceRegione: p['CodiceRegione'],
418
- codiceAzienda: p['CodiceAzienda'],
419
- denominazione: p['DenominazioneStruttura'],
420
- codiceComune: p['CodiceComune'],
421
- idDistretto: this._settings.datiStruttureRegione.comuniDistretti[p["CodiceComune"]],
422
- dataUltimoAggiornamento: moment(p['DataAggiornamento'], 'DD/MM/YYYY')
423
- };
424
- } else
425
- mancanti.push(p);
426
- })
437
+ const latestFile = fs.readdirSync(struttureDir).find(f => f.startsWith("LATEST_"));
438
+ if (!latestFile) {
439
+ throw new Error(`Nessun file LATEST_ trovato in ${struttureDir}. Eseguire prima la generazione delle strutture con load_strutture.js`);
440
+ }
441
+
442
+ const strutture = JSON.parse(fs.readFileSync(path.join(struttureDir, latestFile), "utf-8"));
443
+
444
+ // Costruisci reverse lookup: nome distretto -> id numerico
445
+ const reverseDistretti = {};
446
+ for (const [id, nome] of Object.entries(this._settings.datiStruttureRegione.distretti)) {
447
+ reverseDistretti[nome.toLowerCase()] = parseInt(id);
448
+ }
449
+
450
+ const struttureOut = {};
451
+ for (const s of strutture) {
452
+ let idDistretto = null;
453
+ if (s.distretto) {
454
+ const distLower = s.distretto.toLowerCase();
455
+ // Match esatto
456
+ idDistretto = reverseDistretti[distLower];
457
+ // Match parziale (es. "Barcellona P.G." contiene "barcellona")
458
+ if (idDistretto === undefined) {
459
+ const found = Object.entries(reverseDistretti).find(([nome]) =>
460
+ distLower.startsWith(nome) || nome.startsWith(distLower)
461
+ );
462
+ if (found) idDistretto = found[1];
463
+ }
464
+ }
465
+
466
+ struttureOut[s.codice] = {
467
+ denominazione: s.denominazione,
468
+ codiceComune: s.codCatastale,
469
+ idDistretto: idDistretto,
470
+ distretto: s.distretto,
471
+ ambito: s.ambito,
472
+ stato: s.stato
473
+ };
474
+ }
475
+
476
+ console.log(`Caricate ${Object.keys(struttureOut).length} strutture da ${latestFile}`);
427
477
  return struttureOut;
428
478
  }
429
479
 
@@ -476,7 +526,7 @@ export class FlussoM {
476
526
  }
477
527
 
478
528
  async #ottieniStatDaFileFlussoM(file) {
479
- let strutture = this.#loadStruttureFromFlowlookDB();
529
+ let strutture = this.#loadStruttureFromLatestFile();
480
530
  let ricetteInFile = await this.#elaboraFileFlussoM(file, this._starts);
481
531
  let warn = "";
482
532
  if (ricetteInFile.error) {
@@ -486,13 +536,13 @@ export class FlussoM {
486
536
  let verificaDateStruttura = this.#checkMeseAnnoStruttura(Object.values(ricetteInFile.ricette))
487
537
  ricetteInFile.codiceStruttura = verificaDateStruttura.codiceStruttura;
488
538
  ricetteInFile.file = file;
489
- ricetteInFile.idDistretto = strutture[verificaDateStruttura.codiceStruttura]?.idDistretto.toString() ?? (ricetteInFile.datiDaFile?.idDistretto ?? "X");
539
+ ricetteInFile.idDistretto = strutture[verificaDateStruttura.codiceStruttura]?.idDistretto?.toString() ?? (ricetteInFile.datiDaFile?.idDistretto ?? "X");
490
540
  ricetteInFile.annoPrevalente = verificaDateStruttura.meseAnnoPrevalente.substr(2, 4);
491
541
  ricetteInFile.mesePrevalente = verificaDateStruttura.meseAnnoPrevalente.substr(0, 2);
492
542
  ricetteInFile.date = _.omitBy(verificaDateStruttura.date, _.isNil);
493
543
  if (!strutture.hasOwnProperty(verificaDateStruttura.codiceStruttura)) {
494
- console.log("STRUTTURA " + verificaDateStruttura.codiceStruttura + " non presente sul FLOWLOOK")
495
- warn = "STRUTTURA " + verificaDateStruttura.codiceStruttura + " non presente sul FLOWLOOK"
544
+ console.log("STRUTTURA " + verificaDateStruttura.codiceStruttura + " non presente nel file strutture")
545
+ warn = "STRUTTURA " + verificaDateStruttura.codiceStruttura + " non presente nel file strutture"
496
546
  }
497
547
  return {errore: false, warning: (warn === "" ? false : warn), out: ricetteInFile}
498
548
  }
@@ -536,7 +586,7 @@ export class FlussoM {
536
586
 
537
587
  async generaReportDaStats(salvaFileHtml = true, salvaFileExcel = true) {
538
588
  let idDistretti = Object.keys(this._settings.datiStruttureRegione.distretti);
539
- let strutture = this.#loadStruttureFromFlowlookDB();
589
+ let strutture = this.#loadStruttureFromLatestFile();
540
590
  let files = utils.getAllFilesRecursive(this._settings.out_folder, '.mstats');
541
591
  const table = {
542
592
  name: '',
@@ -897,11 +947,19 @@ export class FlussoM {
897
947
  }
898
948
  let lunghezzaRiga = utils.verificaLunghezzaRiga(this._starts);
899
949
  const outputFile = nomeFile === "" ? (outFolder + path.sep + '190205_000_XXXX_XX_M_AL_20XX_XX_XX.TXT') : outFolder + path.sep + nomeFile;
900
- var logger = fs.createWriteStream(outputFile, {
901
- flags: 'a' // 'a' means appending (old data will be preserved)
902
- })
903
- var writeLine = (line) => logger.write(`${line}\n`);
904
- for (var file of allFiles) {
950
+
951
+ // Rimuovi file esistente per evitare append duplicato
952
+ if (fs.existsSync(outputFile)) {
953
+ fs.unlinkSync(outputFile);
954
+ }
955
+
956
+ console.log(`unisciFileTxt: ${allFiles.length} file da ${inFolder}`);
957
+
958
+ const logger = fs.createWriteStream(outputFile, { flags: 'w' });
959
+ const writeLine = (line) => logger.write(`${line}\n`);
960
+ let totalLines = 0;
961
+
962
+ for (const file of allFiles) {
905
963
  const fileStream = fs.createReadStream(file);
906
964
  const rl = readline.createInterface({
907
965
  input: fileStream,
@@ -911,7 +969,6 @@ export class FlussoM {
911
969
  let nLine = 0;
912
970
  for await (const line of rl) {
913
971
  writeLine(line);
914
- // Each line in input.txt will be successively available here as `line`.
915
972
  nLine++;
916
973
  if (line.length !== lunghezzaRiga) {
917
974
  console.log("file: " + file);
@@ -920,10 +977,14 @@ export class FlussoM {
920
977
  errors.push({file: file, lunghezza: line.length, linea: nLine})
921
978
  }
922
979
  }
980
+ totalLines += nLine;
923
981
  }
924
982
  logger.end();
983
+
984
+ const fileSize = fs.existsSync(outputFile) ? fs.statSync(outputFile).size : 0;
985
+ console.log(`unisciFileTxt: ${totalLines} righe totali, ${(fileSize / 1024).toFixed(1)} KB -> ${outputFile}`);
986
+
925
987
  if (errors.length === 0) {
926
- //verifica
927
988
  console.log("verifica.. ");
928
989
  errors = [...errors, ...await this.#processLineByLine(outputFile, lunghezzaRiga)]
929
990
  if (errors.length === 0)
@@ -933,7 +994,7 @@ export class FlussoM {
933
994
  console.table(errors);
934
995
  }
935
996
  }
936
- return {error: errors.length !== 0, errors: errors}
997
+ return {error: errors.length !== 0, errors: errors, totalFiles: allFiles.length, totalLines, fileSize}
937
998
  }
938
999
 
939
1000
  async inviaMailAiDistretti(distretti, meseAnno = "", mailGlobale = "", nomeFileCompleto = "CONSEGNE_GLOBALI") {
@@ -1440,7 +1501,7 @@ export class FlussoM {
1440
1501
 
1441
1502
 
1442
1503
  async generaFileExcelPerAnno(nomeFile, anno, cosaGenerare = [FlussoM.PER_STRUTTURA_ANNO_MESE, FlussoM.TAB_CONSEGNE_PER_CONTENUTO, FlussoM.TAB_CONSEGNE_PER_NOME_FILE, FlussoM.TAB_DIFFERENZE_CONTENUTO_NOMEFILE]) {
1443
- const strutture = this.#loadStruttureFromFlowlookDB();
1504
+ const strutture = this.#loadStruttureFromLatestFile();
1444
1505
  let files = utils.getAllFilesRecursive(this._settings.out_folder, '.mstats');
1445
1506
  let data = [];
1446
1507
  for (let file of files) {
@@ -1616,7 +1677,7 @@ export class FlussoM {
1616
1677
  }
1617
1678
  }
1618
1679
 
1619
- const strutture = this.#loadStruttureFromFlowlookDB();
1680
+ const strutture = this.#loadStruttureFromLatestFile();
1620
1681
 
1621
1682
  const buffer = fs.readFileSync(this.settings.flowlookDBFilePath);
1622
1683
  const reader = new MDBReader(buffer);
@@ -1748,4 +1809,175 @@ export class FlussoM {
1748
1809
  }
1749
1810
  }
1750
1811
 
1812
+ /**
1813
+ * Esegue lo scraping di una pagina HTML dell'elenco strutture del Progetto Tessera Sanitaria
1814
+ * e genera un file JSON con i dati estratti.
1815
+ * @param {string} htmlContent - Il contenuto HTML della pagina
1816
+ * @param {string} outputDir - La directory di output (default: "data")
1817
+ * @returns {Array<{codice: string, denominazione: string, indirizzo: string, localita: string, piva: string, stato: string}>}
1818
+ */
1819
+ static scrapingStruttureProgettoTs(htmlContent, outputDir = "data/strutture") {
1820
+ const strutture = [];
1821
+ const isHTML = /<tr[\s>]/i.test(htmlContent) || /<td[\s>]/i.test(htmlContent);
1822
+
1823
+ if (isHTML) {
1824
+ const $ = cheerio.load(htmlContent);
1825
+ $('tr').each((i, row) => {
1826
+ const tds = $(row).find('td');
1827
+ if (tds.length < 5) return;
1828
+
1829
+ const codice = $(tds[0]).text().trim();
1830
+ if (!codice || codice === "Codice") return;
1831
+
1832
+ const denominazione = $(tds[1]).text().trim();
1833
+ const indirizzo = $(tds[2]).text().trim();
1834
+ const localita = $(tds[3]).text().trim();
1835
+ const piva = $(tds[4]).text().trim();
1836
+
1837
+ const rigaTesto = $(row).text();
1838
+ const stato = rigaTesto.includes("Struttura chiusa") ? "chiuso" : "attivo";
1839
+
1840
+ strutture.push({codice, denominazione, indirizzo, localita, piva, stato});
1841
+ });
1842
+ } else {
1843
+ // Parsing testo piano (copia-incolla dal browser)
1844
+ const lines = htmlContent.split('\n');
1845
+ for (const line of lines) {
1846
+ const trimmed = line.trim();
1847
+ if (!trimmed || !/^\d{6}\s/.test(trimmed)) continue;
1848
+
1849
+ const stato = trimmed.includes("Struttura chiusa") ? "chiuso" : "attivo";
1850
+ const cleaned = trimmed.replace(/\s+Visualizza Dettaglio.*$/, '');
1851
+
1852
+ // Prova prima con tab, poi con 4+ spazi
1853
+ let parts = cleaned.split('\t').map(p => p.trim()).filter(p => p);
1854
+ if (parts.length < 5) {
1855
+ parts = cleaned.split(/\s{4,}/).map(p => p.trim()).filter(p => p);
1856
+ }
1857
+
1858
+ if (parts.length >= 5) {
1859
+ strutture.push({
1860
+ codice: parts[0],
1861
+ denominazione: parts[1],
1862
+ indirizzo: parts[2],
1863
+ localita: parts[3],
1864
+ piva: parts[4],
1865
+ stato
1866
+ });
1867
+ }
1868
+ }
1869
+ }
1870
+
1871
+ // Arricchimento con codice catastale e distretto dal CSV ambiti
1872
+ const csvPath = path.join(path.dirname(outputDir), "ambiti_distretti_messina.csv");
1873
+ if (fs.existsSync(csvPath)) {
1874
+ const csvContent = fs.readFileSync(csvPath, "utf-8");
1875
+ const ambiti = {};
1876
+ csvContent.split('\n').slice(1).forEach(line => {
1877
+ const parts = line.split(',');
1878
+ if (parts.length >= 3) {
1879
+ const ambito = parts[0].trim();
1880
+ if (ambito) {
1881
+ ambiti[ambito] = { codCatastale: parts[1].trim(), distretto: parts[2].trim() };
1882
+ }
1883
+ }
1884
+ });
1885
+
1886
+ // Carica mapping manuale da file JSON esterno
1887
+ const mappingPath = path.join(path.dirname(outputDir), "mapping_strutture_extra.json");
1888
+ let mappingVarianti = {};
1889
+ let mappingPerCodice = {};
1890
+ if (fs.existsSync(mappingPath)) {
1891
+ const mappingData = JSON.parse(fs.readFileSync(mappingPath, "utf-8"));
1892
+ mappingVarianti = mappingData.per_comune || {};
1893
+ mappingPerCodice = mappingData.per_codice_struttura || {};
1894
+ }
1895
+
1896
+ // Normalizza stringa per match fuzzy (rimuove apostrofi, trattini, spazi multipli)
1897
+ const normalizza = (s) => s.toUpperCase().replace(/['\-\u2019\u0060]/g, '').replace(/\s+/g, ' ').trim();
1898
+
1899
+ // Costruisci indice normalizzato degli ambiti
1900
+ const ambitiNorm = {};
1901
+ for (const [key, val] of Object.entries(ambiti)) {
1902
+ ambitiNorm[normalizza(key)] = { ...val, ambitoOriginale: key };
1903
+ }
1904
+
1905
+ let matchati = 0;
1906
+ let nonMatchati = [];
1907
+
1908
+ for (const s of strutture) {
1909
+ // Estrai comune dalla localita (rimuovi provincia es. "(ME)")
1910
+ let comune = s.localita.replace(/\s*\([A-Z]{2}\)\s*$/, '').trim();
1911
+
1912
+ // 0. Match per codice struttura (override manuale)
1913
+ if (mappingPerCodice[s.codice]) {
1914
+ comune = mappingPerCodice[s.codice];
1915
+ }
1916
+
1917
+ // 1. Match diretto
1918
+ if (ambiti[comune]) {
1919
+ s.codCatastale = ambiti[comune].codCatastale;
1920
+ s.distretto = ambiti[comune].distretto;
1921
+ s.ambito = comune;
1922
+ matchati++;
1923
+ continue;
1924
+ }
1925
+
1926
+ // 2. Match via mapping manuale per comune
1927
+ const mappato = mappingVarianti[comune];
1928
+ if (mappato && ambiti[mappato]) {
1929
+ s.codCatastale = ambiti[mappato].codCatastale;
1930
+ s.distretto = ambiti[mappato].distretto;
1931
+ s.ambito = mappato;
1932
+ matchati++;
1933
+ continue;
1934
+ }
1935
+
1936
+ // 3. Match fuzzy normalizzato (apostrofi, trattini)
1937
+ const comuneNorm = normalizza(comune);
1938
+ if (ambitiNorm[comuneNorm]) {
1939
+ const found = ambitiNorm[comuneNorm];
1940
+ s.codCatastale = found.codCatastale;
1941
+ s.distretto = found.distretto;
1942
+ s.ambito = found.ambitoOriginale;
1943
+ matchati++;
1944
+ continue;
1945
+ }
1946
+
1947
+ // Non trovato
1948
+ s.codCatastale = null;
1949
+ s.distretto = null;
1950
+ s.ambito = null;
1951
+ nonMatchati.push({ codice: s.codice, denominazione: s.denominazione, localita: s.localita });
1952
+ }
1953
+
1954
+ console.log(`Matching ambiti: ${matchati}/${strutture.length} matchati, ${nonMatchati.length} non matchati`);
1955
+ if (nonMatchati.length > 0) {
1956
+ console.log("Strutture senza match ambito:");
1957
+ nonMatchati.forEach(s => console.log(` ${s.codice} - ${s.denominazione} - ${s.localita}`));
1958
+ }
1959
+ }
1960
+
1961
+ const timestamp = moment().format("YYYYMMDD_HHmmss");
1962
+ const nomeFile = path.join(outputDir, `strutture_ts_${timestamp}.json`);
1963
+
1964
+ if (!fs.existsSync(outputDir)) {
1965
+ fs.mkdirSync(outputDir, {recursive: true});
1966
+ }
1967
+
1968
+ fs.writeFileSync(nomeFile, JSON.stringify(strutture, null, 2), "utf-8");
1969
+ console.log(`Salvate ${strutture.length} strutture in ${nomeFile}`);
1970
+
1971
+ // Rimuovi eventuali file LATEST_ precedenti e crea il nuovo
1972
+ const files = fs.readdirSync(outputDir);
1973
+ files.filter(f => f.startsWith("LATEST_")).forEach(f => {
1974
+ fs.unlinkSync(path.join(outputDir, f));
1975
+ });
1976
+ const latestFile = path.join(outputDir, `LATEST_strutture_ts_${timestamp}.json`);
1977
+ fs.copyFileSync(nomeFile, latestFile);
1978
+ console.log(`Creato ${latestFile}`);
1979
+
1980
+ return strutture;
1981
+ }
1982
+
1751
1983
  }
@@ -512,7 +512,8 @@ export class Medici {
512
512
  });
513
513
  if (!error && !error2) {
514
514
  await page.waitForSelector("body > div:nth-child(12) > div:nth-child(3) > div:nth-child(2)");
515
- await page.click("#menu_voci > ol > li:nth-child(1) > a");
515
+ //await page.click("#menu_voci > ol > li:nth-child(1) > a");
516
+ await page.goto("https://sistemats4.sanita.finanze.it/simossMedici/elencoAssistiti.do", {waitUntil: 'networkidle2'});
516
517
  // Attendiamo che il selettore #mef sia presente E che sia visibile
517
518
  await page.waitForSelector("#mef", {
518
519
  visibile: true,
@@ -573,7 +574,7 @@ export class Medici {
573
574
  numParallelsJobs = 20,
574
575
  visibile = false,
575
576
  } = config;
576
-
577
+ //numParallelsJobs = 1;
577
578
  EventEmitter.defaultMaxListeners = 40;
578
579
  let out = {};
579
580
  let jobs = [];
@@ -633,7 +634,7 @@ export class Medici {
633
634
  };
634
635
  allAssistitiNar.push(assistito.codiceFiscale);
635
636
  }
636
- for (let assistito of assistitiTs) {
637
+ for (let assistito of assistitiTs ?? []) {
637
638
  if (!allAssistiti.hasOwnProperty(assistito.cf))
638
639
  allAssistiti[assistito.cf] = {
639
640
  nome_assistito: assistito.nome,
@@ -1016,104 +1017,106 @@ export class Medici {
1016
1017
 
1017
1018
 
1018
1019
  async creaElenchiDeceduti(codToCfDistrettoMap, pathData, distretti, dataQuote = null) {
1019
- let lista = {perDistretto: {}, nonTrovati: []};
1020
- if (!dataQuote)
1021
- dataQuote = moment().format("YYYY-MM-DD");
1022
- let allfiles = utils.getAllFilesRecursive(pathData + path.sep + "elaborazioni" + path.sep, ".zip");
1023
- let allAssistiti = await utils.leggiOggettoDaFileJSON(pathData + path.sep + "assistitiNar.json");
1024
- for (let medico in codToCfDistrettoMap) {
1025
- let fileMedico = allfiles.filter(file => file.includes(medico));
1026
- if (fileMedico.length === 0)
1027
- lista.nonTrovati.push(codToCfDistrettoMap[medico]);
1028
- else if (fileMedico.length === 1) {
1029
- //let fileData = await utils.leggiOggettoDaFileJSON(fileMedico[0]);
1030
- let fileData = await utils.decomprimiELeggiFile(fileMedico[0])
1031
- if (!lista.perDistretto.hasOwnProperty(codToCfDistrettoMap[medico].distretto))
1032
- lista.perDistretto[codToCfDistrettoMap[medico].distretto] = {
1033
- recuperi: [],
1034
- problemi: [],
1035
- anagrafica: [],
1036
- medici: {}
1037
- };
1038
- for (let deceduto of Object.values(fileData.deceduti)) {
1039
- let allAssistitiMedicoMap = {};
1040
- for (let assistito of Object.values(allAssistiti[medico].assistiti)) {
1041
- allAssistitiMedicoMap[assistito.codiceFiscale] = assistito;
1042
- }
1043
- let numQuote = 0;
1044
- let dataScelta = moment(allAssistitiMedicoMap[deceduto.cf].data_scelta, "DD/MM/YYYY");
1020
+ if (!fs.existsSync(pathData + path.sep + "recuperi" + path.sep)) {
1021
+ let lista = {perDistretto: {}, nonTrovati: []};
1022
+ if (!dataQuote)
1023
+ dataQuote = moment().format("YYYY-MM-DD");
1024
+ let allfiles = utils.getAllFilesRecursive(pathData + path.sep + "elaborazioni" + path.sep, ".zip");
1025
+ let allAssistiti = await utils.leggiOggettoDaFileJSON(pathData + path.sep + "assistitiNar.json");
1026
+ for (let medico in codToCfDistrettoMap) {
1027
+ let fileMedico = allfiles.filter(file => file.includes(medico));
1028
+ if (fileMedico.length === 0)
1029
+ lista.nonTrovati.push(codToCfDistrettoMap[medico]);
1030
+ else if (fileMedico.length === 1) {
1031
+ //let fileData = await utils.leggiOggettoDaFileJSON(fileMedico[0]);
1032
+ let fileData = await utils.decomprimiELeggiFile(fileMedico[0])
1033
+ if (!lista.perDistretto.hasOwnProperty(codToCfDistrettoMap[medico].distretto))
1034
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto] = {
1035
+ recuperi: [],
1036
+ problemi: [],
1037
+ anagrafica: [],
1038
+ medici: {}
1039
+ };
1040
+ for (let deceduto of Object.values(fileData.deceduti)) {
1041
+ let allAssistitiMedicoMap = {};
1042
+ for (let assistito of Object.values(allAssistiti[medico].assistiti)) {
1043
+ allAssistitiMedicoMap[assistito.codiceFiscale] = assistito;
1044
+ }
1045
+ let numQuote = 0;
1046
+ let dataScelta = moment(allAssistitiMedicoMap[deceduto.cf].data_scelta, "DD/MM/YYYY");
1045
1047
 
1046
- if (!deceduto.hasOwnProperty('indirizzo') || deceduto.indirizzo === "" || deceduto.indirizzo === null)
1047
- deceduto.indirizzo = "-";
1048
- deceduto.dataScelta = dataScelta.format("DD/MM/YYYY");
1049
- deceduto.codMedico = medico;
1050
- deceduto.nomeCognomeMedico = codToCfDistrettoMap[medico].nome_cognome;
1051
- deceduto.distretto = distretti[codToCfDistrettoMap[medico].distretto];
1052
- deceduto.ambito = codToCfDistrettoMap[medico].ambito;
1053
- if (deceduto.hasOwnProperty('dataDecesso') && deceduto.dataDecesso !== "" && deceduto.dataDecesso !== null && moment(deceduto.dataDecesso, "DD/MM/YYYY").isAfter(moment("01/01/1900", "DD/MM/YYYY"))) {
1054
- let dataInizio = moment(deceduto.dataDecesso, "DD/MM/YYYY").isBefore(dataScelta) ? dataScelta.format("DD/MM/YYYY") : deceduto.dataDecesso;
1055
- // se la data di decesso è superiore al 1 gennaio 1900
1056
- numQuote = utils.calcolaMesiDifferenza(dataInizio, dataQuote);
1057
- deceduto.numQuoteDaRecuperare = numQuote;
1058
- deceduto.note = "";
1059
- if (moment(deceduto.dataDecesso, "DD/MM/YYYY").isBefore(dataScelta)) {
1060
- deceduto.note = "Data decesso precedente alla data di scelta";
1061
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].problemi.push(deceduto);
1048
+ if (!deceduto.hasOwnProperty('indirizzo') || deceduto.indirizzo === "" || deceduto.indirizzo === null)
1049
+ deceduto.indirizzo = "-";
1050
+ deceduto.dataScelta = dataScelta.format("DD/MM/YYYY");
1051
+ deceduto.codMedico = medico;
1052
+ deceduto.nomeCognomeMedico = codToCfDistrettoMap[medico].nome_cognome;
1053
+ deceduto.distretto = distretti[codToCfDistrettoMap[medico].distretto];
1054
+ deceduto.ambito = codToCfDistrettoMap[medico].ambito;
1055
+ if (deceduto.hasOwnProperty('dataDecesso') && deceduto.dataDecesso !== "" && deceduto.dataDecesso !== null && moment(deceduto.dataDecesso, "DD/MM/YYYY").isAfter(moment("01/01/1900", "DD/MM/YYYY"))) {
1056
+ let dataInizio = moment(deceduto.dataDecesso, "DD/MM/YYYY").isBefore(dataScelta) ? dataScelta.format("DD/MM/YYYY") : deceduto.dataDecesso;
1057
+ // se la data di decesso è superiore al 1 gennaio 1900
1058
+ numQuote = utils.calcolaMesiDifferenza(dataInizio, dataQuote);
1059
+ deceduto.numQuoteDaRecuperare = numQuote;
1060
+ deceduto.note = "";
1061
+ if (moment(deceduto.dataDecesso, "DD/MM/YYYY").isBefore(dataScelta)) {
1062
+ deceduto.note = "Data decesso precedente alla data di scelta";
1063
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].problemi.push(deceduto);
1064
+ } else {
1065
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].recuperi.push(deceduto);
1066
+ if (!lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici.hasOwnProperty(medico))
1067
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici[medico] = {
1068
+ codice: medico,
1069
+ distretto: distretti[codToCfDistrettoMap[medico].distretto],
1070
+ ambito: codToCfDistrettoMap[medico].ambito,
1071
+ nomeCognome: codToCfDistrettoMap[medico].nome_cognome,
1072
+ numDeceduti: 0,
1073
+ quote: 0
1074
+ };
1075
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici[medico].numDeceduti += 1;
1076
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici[medico].quote += numQuote;
1077
+ }
1062
1078
  } else {
1063
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].recuperi.push(deceduto);
1064
- if (!lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici.hasOwnProperty(medico))
1065
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici[medico] = {
1066
- codice: medico,
1067
- distretto: distretti[codToCfDistrettoMap[medico].distretto],
1068
- ambito: codToCfDistrettoMap[medico].ambito,
1069
- nomeCognome: codToCfDistrettoMap[medico].nome_cognome,
1070
- numDeceduti: 0,
1071
- quote: 0
1072
- };
1073
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici[medico].numDeceduti += 1;
1074
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].medici[medico].quote += numQuote;
1079
+ deceduto.numQuoteDaRecuperare = 0;
1080
+ deceduto.note = "Data di decesso non valida";
1081
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].problemi.push(deceduto);
1075
1082
  }
1076
- } else {
1077
- deceduto.numQuoteDaRecuperare = 0;
1078
- deceduto.note = "Data di decesso non valida";
1079
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].problemi.push(deceduto);
1080
1083
  }
1081
- }
1082
- // add ambito and distretto to ...Object.values(Object.values(fileData.vivi)
1083
- lista.perDistretto[codToCfDistrettoMap[medico].distretto].anagrafica.push(...Object.values(Object.values(fileData.vivi)));
1084
+ // add ambito and distretto to ...Object.values(Object.values(fileData.vivi)
1085
+ lista.perDistretto[codToCfDistrettoMap[medico].distretto].anagrafica.push(...Object.values(Object.values(fileData.vivi)));
1084
1086
 
1087
+ }
1085
1088
  }
1089
+ let out = {};
1090
+ for (let distretto in lista.perDistretto) {
1091
+ if (!out.hasOwnProperty(distretto))
1092
+ out[distretto] = {recuperi: [], problemi: []};
1093
+ out[distretto].recuperi.push(...lista.perDistretto[distretto].recuperi);
1094
+ out[distretto].problemi.push(...lista.perDistretto[distretto].problemi);
1095
+ }
1096
+ let global = {recuperi: [], problemi: []};
1097
+ for (let distretto in out) {
1098
+ // add column distretto in each recuperi and problemi
1099
+ for (let recupero of out[distretto].recuperi)
1100
+ global.recuperi.push(recupero);
1101
+ for (let problema of out[distretto].problemi)
1102
+ global.problemi.push(problema);
1103
+ }
1104
+ // create folder if exist pathData + path.sep + "recuperi" + path.sep
1105
+ if (!fs.existsSync(pathData + path.sep + "recuperi" + path.sep))
1106
+ fs.mkdirSync(pathData + path.sep + "recuperi" + path.sep);
1107
+ if (!fs.existsSync(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep))
1108
+ fs.mkdirSync(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep);
1109
+ if (!fs.existsSync(pathData + path.sep + "anagrafica"))
1110
+ fs.mkdirSync(pathData + path.sep + "anagrafica");
1111
+ let allAnagraficaTuttiDistretti = [];
1112
+ for (let distretto in out) {
1113
+ await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep + distretto + "_recuperi.xlsx", out[distretto].recuperi);
1114
+ await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep + distretto + "_problemi.xlsx", out[distretto].problemi);
1115
+ await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "anagrafica" + path.sep + distretto + "_anagrafica.xlsx", lista.perDistretto[distretto].anagrafica);
1116
+ allAnagraficaTuttiDistretti.push(...lista.perDistretto[distretto].anagrafica);
1117
+ }
1118
+ await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "global_recuperi.xlsx", global.recuperi);
1119
+ await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "global_problemi.xlsx", global.problemi);
1086
1120
  }
1087
- let out = {};
1088
- for (let distretto in lista.perDistretto) {
1089
- if (!out.hasOwnProperty(distretto))
1090
- out[distretto] = {recuperi: [], problemi: []};
1091
- out[distretto].recuperi.push(...lista.perDistretto[distretto].recuperi);
1092
- out[distretto].problemi.push(...lista.perDistretto[distretto].problemi);
1093
- }
1094
- let global = {recuperi: [], problemi: []};
1095
- for (let distretto in out) {
1096
- // add column distretto in each recuperi and problemi
1097
- for (let recupero of out[distretto].recuperi)
1098
- global.recuperi.push(recupero);
1099
- for (let problema of out[distretto].problemi)
1100
- global.problemi.push(problema);
1101
- }
1102
- // create folder if exist pathData + path.sep + "recuperi" + path.sep
1103
- if (!fs.existsSync(pathData + path.sep + "recuperi" + path.sep))
1104
- fs.mkdirSync(pathData + path.sep + "recuperi" + path.sep);
1105
- if (!fs.existsSync(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep))
1106
- fs.mkdirSync(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep);
1107
- if (!fs.existsSync(pathData + path.sep + "anagrafica"))
1108
- fs.mkdirSync(pathData + path.sep + "anagrafica");
1109
- let allAnagraficaTuttiDistretti = [];
1110
- for (let distretto in out) {
1111
- await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep + distretto + "_recuperi.xlsx", out[distretto].recuperi);
1112
- await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "per distretto" + path.sep + distretto + "_problemi.xlsx", out[distretto].problemi);
1113
- await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "anagrafica" + path.sep + distretto + "_anagrafica.xlsx", lista.perDistretto[distretto].anagrafica);
1114
- allAnagraficaTuttiDistretti.push(...lista.perDistretto[distretto].anagrafica);
1115
- }
1116
- await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "global_recuperi.xlsx", global.recuperi);
1117
- await utils.scriviOggettoSuNuovoFileExcel(pathData + path.sep + "recuperi" + path.sep + "global_problemi.xlsx", global.problemi);
1118
1121
  }
1119
1122
  }
@@ -343,28 +343,35 @@ export class Nar2 {
343
343
  let datiIdAssistito;
344
344
  const retry = 3;
345
345
  for (let i = 0; i < retry; i++) {
346
- if (!fallback) {
347
- datiIdAssistito = await this.getAssistitiFromParams({codiceFiscale: codiceFiscale});
348
- if (datiIdAssistito.ok && datiIdAssistito.data && datiIdAssistito.data.length === 1)
349
- datiAssistito = await this.getAssistitoFromId(datiIdAssistito.data[0].pz_id);
350
- } else {
351
- await this.getToken({fallback});
352
- const data = {cf: codiceFiscale, token: Nar2.#token, type: "nar2"};
353
- const cripted = CryptHelper.AESEncrypt(JSON.stringify(data), this._cryptData["KEY"], CryptHelper.convertBase64StringToByte(this._cryptData["IV"]));
354
- datiIdAssistito = await axios.request({
355
- method: "post",
356
- url: Nar2.GET_WS_FALLBACK_INTERNAL,
357
- data: {d: cripted},
358
- headers: {
359
- 'Content-Type': 'application/x-www-form-urlencoded'
346
+ try {
347
+ if (!fallback) {
348
+ datiIdAssistito = await this.getAssistitiFromParams({codiceFiscale: codiceFiscale});
349
+ if (datiIdAssistito.ok && datiIdAssistito.data && datiIdAssistito.data.length === 1)
350
+ datiAssistito = await this.getAssistitoFromId(datiIdAssistito.data[0].pz_id);
351
+ } else {
352
+ await this.getToken({fallback});
353
+ const data = {cf: codiceFiscale, token: Nar2.#token, type: "nar2"};
354
+ const cripted = CryptHelper.AESEncrypt(JSON.stringify(data), this._cryptData["KEY"], CryptHelper.convertBase64StringToByte(this._cryptData["IV"]));
355
+ datiIdAssistito = await axios.request({
356
+ method: "post",
357
+ url: Nar2.GET_WS_FALLBACK_INTERNAL,
358
+ data: {d: cripted},
359
+ headers: {
360
+ 'Content-Type': 'application/x-www-form-urlencoded'
361
+ }
362
+ });
363
+ if (datiIdAssistito.status === 200 && datiIdAssistito.data?.data?.hasOwnProperty('nar2'))
364
+ datiAssistito = {ok: true, data: datiIdAssistito.data.data.nar2.result};
365
+ else {
366
+ datiAssistito = {ok: false, data: datiIdAssistito.data};
367
+ console.log(`[getDatiAssistitoNar2FromCf] Risposta fallback non valida - status: ${datiIdAssistito.status}, hasData: ${!!datiIdAssistito.data?.data}, hasNar2: ${!!datiIdAssistito.data?.data?.nar2}`);
360
368
  }
361
- });
362
- if (datiIdAssistito.status === 200 && datiIdAssistito.data.data.hasOwnProperty('nar2'))
363
- datiAssistito = {ok: true, data: datiIdAssistito.data.data.nar2.result};
364
- else datiAssistito = {ok: false, data: datiIdAssistito.data};
369
+ }
370
+ } catch (e) {
371
+ console.log("[getDatiAssistitoNar2FromCf] Eccezione durante recupero Nar2:", e.message);
365
372
  }
366
373
  if (datiAssistito && datiAssistito.ok) break;
367
- else console.log("Errore durante il recupero dei dati assistito da Nar2, tentativi rimanenti:" + (retry - i));
374
+ else console.log("[getDatiAssistitoNar2FromCf] Errore Nar2, tentativi rimanenti:" + (retry - i));
368
375
  }
369
376
  if (datiAssistito && datiAssistito.ok) {
370
377
  try {
@@ -416,6 +423,7 @@ export class Nar2 {
416
423
  fullData: datiAssistito
417
424
  };
418
425
  } else {
426
+ console.log(`[getDatiAssistitoNar2FromCf] Nar2 fallito dopo ${retry} tentativi per CF: ${codiceFiscale?.substring(0, 6)}***`);
419
427
  assistito.erroreNar2 = "Nessun assistito trovato con il codice fiscale fornito";
420
428
  assistito.okNar2 = false;
421
429
  return {ok: false, data: null, fullData: datiIdAssistito};
@@ -535,13 +543,19 @@ export class Nar2 {
535
543
 
536
544
  await this.getToken({fallback});
537
545
  if (sogei && nar2) {
538
- await Promise.all([
546
+ const results = await Promise.allSettled([
539
547
  this.getDatiAssistitoFromCfSuSogeiNew(cf, assistito, fallback),
540
548
  this.getDatiAssistitoNar2FromCf(cf, assistito, fallback)
541
549
  ]);
550
+ results.forEach((result, index) => {
551
+ if (result.status === 'rejected') {
552
+ const source = index === 0 ? 'Sogei' : 'Nar2';
553
+ console.log(`[getDatiAssistitoCompleti] ${source} fallito:`, result.reason?.message || result.reason);
554
+ }
555
+ });
542
556
  } else {
543
- if (sogei) await this.getDatiAssistitoFromCfSuSogeiNew(cf, assistito, fallback);
544
- if (nar2) await this.getDatiAssistitoNar2FromCf(cf, assistito, fallback);
557
+ if (sogei) await this.getDatiAssistitoFromCfSuSogeiNew(cf, assistito, fallback).catch(e => console.log('[getDatiAssistitoCompleti] Sogei fallito:', e.message));
558
+ if (nar2) await this.getDatiAssistitoNar2FromCf(cf, assistito, fallback).catch(e => console.log('[getDatiAssistitoCompleti] Nar2 fallito:', e.message));
545
559
  }
546
560
 
547
561
  return assistito;
@@ -589,7 +603,7 @@ export class Nar2 {
589
603
  'Content-Type': 'application/x-www-form-urlencoded'
590
604
  }
591
605
  })
592
- out.data = {status: out.data.data.sogei.status || false, data: out.data.data.sogei.data};
606
+ out.data = {status: out.data?.data?.sogei?.status || false, data: out.data?.data?.sogei?.data};
593
607
  }
594
608
 
595
609
  if (!out.data || out.data === undefined || (fallback && out.data.status.toString().toLowerCase().includes("token is invalid")))
@@ -643,16 +657,20 @@ export class Nar2 {
643
657
  };
644
658
  }
645
659
  } catch (e) {
660
+ console.log(`[getDatiAssistitoFromCfSuSogeiNew] Errore tentativo Sogei:`, e.message);
646
661
  await this.getToken({fallback});
647
662
  }
648
663
  }
649
- if (!ok)
664
+ if (!ok) {
665
+ console.log(`[getDatiAssistitoFromCfSuSogeiNew] Sogei fallito dopo ${this._maxRetry} tentativi per CF: ${cf?.substring(0, 6)}***`);
650
666
  return {
651
667
  ok: false,
652
668
  fullData: null,
653
669
  data: assistito
654
670
  };
671
+ }
655
672
  }
656
673
 
657
674
 
675
+
658
676
  }
@@ -731,7 +731,7 @@ export class FlussoSIAD {
731
731
  * @param {string} [nomeFileTs="datiTS.mpdb"] (Opzionale) Il percorso del file dati TS da utilizzare per il confronto.
732
732
  * @return {Promise<void>} Una promessa che si risolve una volta completato il calcolo delle statistiche e la scrittura del file di output.
733
733
  */
734
- async statisticheFLS21(pathData, nomeFileTs = "datiTS.mpdb") {
734
+ async statisticheFLS21(pathData, nomeFileTs = "siad.mpdb") {
735
735
  let data = {
736
736
  allChiaviCasiTrattati: {},
737
737
  statsT1: {totali: 0, anziani: 0, palliativa: 0},
@@ -744,8 +744,8 @@ export class FlussoSIAD {
744
744
 
745
745
  let fileDatiTs = fs.existsSync(pathData + path.sep + nomeFileTs) ? await utils.leggiOggettoMP(pathData + path.sep + nomeFileTs) : null;
746
746
 
747
- let filesT1 = utils.getAllFilesRecursive(pathData, ".xml", "AP2");
748
- let filesT2 = utils.getAllFilesRecursive(pathData, ".xml", "AA2");
747
+ let filesT1 = utils.getAllFilesRecursive(pathData, ".xml", "AA2");
748
+ let filesT2 = utils.getAllFilesRecursive(pathData, ".xml", "AP2");
749
749
 
750
750
  filesT1.forEach(file => {
751
751
  let xml_string = fs.readFileSync(file, "utf8");
@@ -757,9 +757,9 @@ export class FlussoSIAD {
757
757
  const chiavePic = assistenze[i]['Eventi'][0]['PresaInCarico'][0]['Id_Rec'][0];
758
758
  console.log(chiavePic)
759
759
  const cf = assistenze[i]['Assistito'][0]['DatiAnagrafici'][0]['CUNI'][0];
760
- const presenteSuTs = fileDatiTs.out.vivi.hasOwnProperty(cf) || fileDatiTs.out.morti.hasOwnProperty(cf);
761
- const vivo = presenteSuTs ? fileDatiTs.out.vivi.hasOwnProperty(cf) : null;
762
- const datiTs = presenteSuTs ? (vivo ? fileDatiTs.out.vivi[cf] : fileDatiTs.out.morti[cf]) : null;
760
+ const presenteSuTs = fileDatiTs.fromTS.out.vivi.hasOwnProperty(cf) || fileDatiTs.fromTS.out.morti.hasOwnProperty(cf);
761
+ const vivo = presenteSuTs ? fileDatiTs.fromTS.out.vivi.hasOwnProperty(cf) : null;
762
+ const datiTs = presenteSuTs ? (vivo ? fileDatiTs.fromTS.out.vivi[cf] : fileDatiTs.fromTS.out.morti[cf]) : null;
763
763
  const eta = datiTs ? datiTs.eta : utils.getAgeFromCF(cf);
764
764
  const anziano = eta >= 65;
765
765
  const palliativa = assistenze[i]['Eventi'][0]['PresaInCarico'][0]['ATTR']['TipologiaPIC'] === "2";
@@ -954,21 +954,24 @@ export class FlussoSIAD {
954
954
  }
955
955
  }
956
956
 
957
- console.log("Totale prese in carico : " + chiavi.length);
958
- console.log("Totale prese in carico almeno un accesso: " + totalePreseIncaricoAlmenoUnAccesso);
959
- console.log("Totali geriatrica: " + (totalePreseIncaricoAlmenoUnAccesso - totalePalliativa));
960
- console.log("Totali palliativa: " + totalePalliativa);
961
- console.log("Totali accessi: " + (totaleAccessiPalliativa + totaleAccessiGeriatrica));
962
- console.log("Totali accessi Geriatrica: " + totaleAccessiGeriatrica);
963
- console.log("Totali accessi Palliativa: " + totaleAccessiPalliativa);
964
-
965
- console.log("Totale prese in carico over 65: " + chiavi65.length);
966
- console.log("Totale prese in carico almeno un accesso over 65: " + totalePreseIncaricoAlmenoUnAccesso65);
967
- console.log("Totali geriatrica over 65: " + (totalePreseIncaricoAlmenoUnAccesso65 - totalePalliativa65));
968
- console.log("Totali palliativa over 65: " + totalePalliativa65);
969
- console.log("Totali accessi over 65: " + (totaleAccessiPalliativa65 + totaleAccessiGeriatrica65));
970
- console.log("Totali accessi Geriatrica over 65: " + totaleAccessiGeriatrica65);
971
- console.log("Totali accessi Palliativa over 65: " + totaleAccessiPalliativa65);
957
+ const result = {
958
+ totalePreseInCarico: chiavi.length,
959
+ totalePreseInCaricoAlmenoUnAccesso: totalePreseIncaricoAlmenoUnAccesso,
960
+ totaliGeriatrica: (totalePreseIncaricoAlmenoUnAccesso - totalePalliativa),
961
+ totaliPalliativa: totalePalliativa,
962
+ totaliAccessi: (totaleAccessiPalliativa + totaleAccessiGeriatrica),
963
+ totaliAccessiGeriatrica: totaleAccessiGeriatrica,
964
+ totaliAccessiPalliativa: totaleAccessiPalliativa,
965
+ totalePreseInCaricoOver65: chiavi65.length,
966
+ totalePreseInCaricoAlmenoUnAccessoOver65: totalePreseIncaricoAlmenoUnAccesso65,
967
+ totaliGeriatricaOver65: (totalePreseIncaricoAlmenoUnAccesso65 - totalePalliativa65),
968
+ totaliPalliativaOver65: totalePalliativa65,
969
+ totaliAccessiOver65: (totaleAccessiPalliativa65 + totaleAccessiGeriatrica65),
970
+ totaliAccessiGeriatricaOver65: totaleAccessiGeriatrica65,
971
+ totaliAccessiPalliativaOver65: totaleAccessiPalliativa65
972
+ };
973
+ console.log(JSON.stringify(result, null, 2));
974
+ return result;
972
975
  }
973
976
 
974
977
  statisticheChiaviValide(pathFile) {
@@ -1107,6 +1110,7 @@ export class FlussoSIAD {
1107
1110
  * @param {boolean} [config.chiudiPicAnnoPrecedente=true] - Flag indicating whether to close the previous year PIC process.
1108
1111
  * @param {array} [config.trimestriDaConsiderare=[1, 2, 3, 4]] - Optional array of quarters to consider for processing.
1109
1112
  * @param {string|null} [config.nonConsiderareSeSuccessivoA=null] - Optional date to skip processing if the data is newer than this date.
1113
+ * @param {boolean} [config.creaFlussoPulito=false] - Flag indicating whether to create a clean flow without errors.
1110
1114
  *
1111
1115
  * @return {Promise<object>} A promise that resolves to an object containing the output data, structured logs, and mapping results or any errors encountered during processing.
1112
1116
  */
@@ -1119,7 +1123,8 @@ export class FlussoSIAD {
1119
1123
  dbFile = "siad.mpdb",
1120
1124
  chiudiPicAnnoPrecedente = true,
1121
1125
  trimestriDaConsiderare = [1, 2, 3, 4],
1122
- nonConsiderareSeSuccessivoA = null
1126
+ nonConsiderareSeSuccessivoA = null,
1127
+ creaFlussoPulito = false
1123
1128
  } = config;
1124
1129
  let out = {
1125
1130
  errors: {
@@ -1234,11 +1239,16 @@ export class FlussoSIAD {
1234
1239
  }
1235
1240
  // data.datiTracciatiDitte.T1byCf
1236
1241
  for (let t1row of data.datiTracciatiDitte.T1) {
1237
- const cf = t1row[tracciato1Maggioli[1]].trim();
1238
- if (!data.datiTracciatiDitte.T1byCf.hasOwnProperty(cf))
1239
- data.datiTracciatiDitte.T1byCf[cf] = [t1row]
1240
- else
1241
- data.datiTracciatiDitte.T1byCf[cf].push(t1row);
1242
+ try {
1243
+ const cf = t1row[tracciato1Maggioli[1]].trim();
1244
+ if (!data.datiTracciatiDitte.T1byCf.hasOwnProperty(cf))
1245
+ data.datiTracciatiDitte.T1byCf[cf] = [t1row]
1246
+ else
1247
+ data.datiTracciatiDitte.T1byCf[cf].push(t1row);
1248
+ } catch (e) {
1249
+ console.log("Errore su riga T1: " + JSON.stringify(t1row));
1250
+ process.exit(1);
1251
+ }
1242
1252
  }
1243
1253
 
1244
1254
  for (let file of allFilesT2) {
@@ -1327,12 +1337,14 @@ export class FlussoSIAD {
1327
1337
  const ultimaPicAttivitaPerAssistito = {};
1328
1338
  let mappingIdPicAperte = {};
1329
1339
  let cfNonValidiTs = {};
1330
- let dataDaNonConsiderare = config.nonConsiderareSeSuccessivoA ? moment(config.nonConsiderareSeSuccessivoA, "DD/MM/YYYY") : null;
1340
+ let dataDaNonConsiderare = nonConsiderareSeSuccessivoA ? moment(nonConsiderareSeSuccessivoA, "DD/MM/YYYY") : null;
1331
1341
  // remove the first 200.000 record of t2bykeyOrdered
1332
1342
  //t2bykeyOrdered = t2bykeyOrdered.slice(200000);
1333
1343
  // PER CREARE IL FLUSSO PULITO
1334
- //data.mappaDatiMinistero.perCf = {};
1335
- //data.mappaDatiMinistero.allCfTrattati.perCf = {};
1344
+ if (creaFlussoPulito) {
1345
+ data.mappaDatiMinistero.perCf = {};
1346
+ data.mappaDatiMinistero.allCfTrattati.perCf = {};
1347
+ }
1336
1348
 
1337
1349
  for (let key of t2bykeyOrdered) {
1338
1350
 
@@ -1437,7 +1449,7 @@ export class FlussoSIAD {
1437
1449
  }
1438
1450
  }
1439
1451
  //in caso di decisione di chiudere pic aperte negli anni precedenti
1440
- if (datiPicAperteMinistero && datiPicAperteMinistero.corrente && config.chiudiPicAnnoPrecedente &&
1452
+ if (datiPicAperteMinistero && datiPicAperteMinistero.corrente && chiudiPicAnnoPrecedente &&
1441
1453
  (moment(datiPicAperteMinistero.corrente.substring(6, 16), "YYYY-MM-DD").year() !== anno)) {
1442
1454
  datiPicAperteMinistero.precedenti.push(datiPicAperteMinistero.corrente);
1443
1455
  delete data.mappaDatiMinistero.perCf[cf].aperte[datiPicAperteMinistero.corrente];
@@ -2725,6 +2737,239 @@ export class FlussoSIAD {
2725
2737
  }
2726
2738
 
2727
2739
 
2740
+ /**
2741
+ * Sviluppa i dati delle vaccinazioni e genera tracciati di output a partire dai file di input.
2742
+ *
2743
+ * Legge file Excel/JSON in `pathCartellaIn`, normalizza codici fiscali (gestione sostituti),
2744
+ * filtra soggetti deceduti e crea i tracciati 1 e 2 per il flusso vaccinazioni.
2745
+ *
2746
+ * @param {string} pathCartellaIn - percorso della cartella con i file di input.
2747
+ * @param {string|null} pathChiaviValideAttive - percorso file chiavi valide attive (opzionale).
2748
+ * @param {number|string} anno - anno di riferimento per i tracciati.
2749
+ * @param {number|string} numTrimestre - numero del trimestre.
2750
+ * @param {Object} [config={}] - opzioni:
2751
+ * - `nomeFileVaccini` {string} nome file vaccini (default: "vaccini.xlsx")
2752
+ * - `nomeFileVivi` {string} nome file vivi (default: "vivi.xlsx")
2753
+ * - `nomeFileMorti` {string} nome file morti (default: "morti.xlsx")
2754
+ * - `nomeFileSostituti` {string} nome file sostituti (default: "sostituti.xlsx")
2755
+ * - `nomeColonnaCf` {string} nome colonna CF (default: "cf")
2756
+ * - `nomeColonnaData` {string} nome colonna data vaccinazione (default: "data")
2757
+ *
2758
+ * @returns {Promise<void>} Risolve quando i file di output sono stati scritti.
2759
+ * @throws {Error} Se mancano colonne richieste o ci sono errori nella lettura dei file.
2760
+ */
2761
+ async sviluppaDatiVaccinazioni(pathCartellaIn, pathChiaviValideAttive, anno, numTrimestre, config = {}) {
2762
+ let {
2763
+ nomeFileTracciatoADP = "datiVaccinazioni.xlsx",
2764
+ nomeFileMorti = "morti.xlsx",
2765
+ nomeFileVivi = "vivi.xlsx",
2766
+ nomeFileSostituti = "sostituti.xlsx",
2767
+ nomeColonnaCf = "cf",
2768
+ nomecolonnaCfSostituto = "cfOk",
2769
+ colonnaIdRecordChiaviValide = "Id Record",
2770
+ colonnaDataPresaInCaricoChiaviValide = "Data Presa In Carico",
2771
+ colonnaConclusioneChiaviValide = "Data Conclusione",
2772
+
2773
+ } = config;
2774
+
2775
+ // put int dataInizio the first day of the correct trimester
2776
+ let dataInizio = moment("01/01/" + anno, "DD/MM/YYYY").add(numTrimestre * 3 - 3, 'months');
2777
+ let dataFine = moment(dataInizio).add(3, 'months').subtract(1, 'days');
2778
+ let allChiaviValideAperte = {};
2779
+ if (fs.existsSync(pathChiaviValideAttive)) {
2780
+ let chiaviValide = await utils.getObjectFromFileExcel(pathChiaviValideAttive);
2781
+ for (let chiave of chiaviValide)
2782
+ if (typeof chiave[colonnaConclusioneChiaviValide] == "string" && chiave[colonnaConclusioneChiaviValide].includes("--"))
2783
+ allChiaviValideAperte[chiave[colonnaIdRecordChiaviValide]] = chiave;
2784
+ }
2785
+ let tracciato = await utils.getObjectFromFileExcel(pathCartellaIn + path.sep + nomeFileTracciatoADP, 0, false);
2786
+
2787
+ let allFileVivi = utils.getAllFilesRecursive(pathCartellaIn, ".xlsx", nomeFileVivi);
2788
+ let allFileMorti = utils.getAllFilesRecursive(pathCartellaIn, ".xlsx", nomeFileMorti);
2789
+ let allFileSostituti = utils.getAllFilesRecursive(pathCartellaIn, ".xlsx", nomeFileSostituti);
2790
+ let allVivi = {};
2791
+ let allMorti = {};
2792
+ let allSostituti = {};
2793
+ for (let file of allFileVivi) {
2794
+ let allViviTemp = await utils.getObjectFromFileExcel(file);
2795
+ for (let vivo of allViviTemp) {
2796
+ if (!vivo.hasOwnProperty(nomeColonnaCf))
2797
+ throw new Error("Errore in file " + file + " colonna " + nomeColonnaCf + " non presente");
2798
+ else
2799
+ allVivi[vivo[nomeColonnaCf]] = vivo;
2800
+ }
2801
+ }
2802
+ for (let file of allFileMorti) {
2803
+ let allMortiTemp = await utils.getObjectFromFileExcel(file);
2804
+ for (let morto of allMortiTemp) {
2805
+ if (!morto.hasOwnProperty(nomeColonnaCf))
2806
+ throw new Error("Errore in file " + file + " colonna " + nomeColonnaCf + " non presente");
2807
+ else
2808
+ allMorti[morto[nomeColonnaCf]] = morto;
2809
+ }
2810
+ }
2811
+ for (let file of allFileSostituti) {
2812
+ let allSostitutiTemp = await utils.getObjectFromFileExcel(file);
2813
+ for (let sostituto of allSostitutiTemp) {
2814
+ if (!sostituto.hasOwnProperty(nomecolonnaCfSostituto.trim().replaceAll(" ", "")) || !sostituto.hasOwnProperty(nomeColonnaCf))
2815
+ // error and break
2816
+ throw new Error("Errore in file " + file + " colonna " + nomecolonnaCfSostituto + " o " + nomeColonnaCf + " non presenti");
2817
+ allSostituti[sostituto[nomeColonnaCf].trim().replaceAll(" ", "")] = sostituto[nomecolonnaCfSostituto].trim().replaceAll(" ", "");
2818
+ }
2819
+ }
2820
+ let outTracciato1 = [];
2821
+ let rigaHeaderTracciato1 = {}
2822
+ for (let i = 0; i < Object.keys(tracciato1Maggioli).length; i++)
2823
+ rigaHeaderTracciato1[i] = tracciato1Maggioli[i];
2824
+ outTracciato1.push(rigaHeaderTracciato1);
2825
+ let allCf = {};
2826
+
2827
+ for (let riga of tracciato) {
2828
+ if (riga[1] !== "") {
2829
+ let chiaviValideAperte = Object.keys(allChiaviValideAperte).filter(key => key.includes(riga[0]));
2830
+
2831
+ if ((allSostituti.hasOwnProperty(riga[0].trim().replaceAll(" ", "")) && allSostituti[riga[0].trim().replaceAll(" ", "")].trim().replaceAll(" ", "") !== "") || Object.keys(allSostituti) === 0 || !allSostituti.hasOwnProperty(riga[0].trim().replaceAll(" ", ""))) {
2832
+ let codFiscale = allSostituti.hasOwnProperty(riga[0].trim().replaceAll(" ", "")) ? allSostituti[riga[0].trim().replaceAll(" ", "")] : riga[0].trim().replaceAll(" ", "");
2833
+ let dataDecesso = allMorti.hasOwnProperty(codFiscale) ? moment(allMorti[codFiscale]['dataDecesso'], "DD/MM/YYYY") : null;
2834
+ let dataNascita = allVivi.hasOwnProperty(codFiscale) ? moment(allVivi[codFiscale]['dataNascita'], "DD/MM/YYYY") : (allMorti.hasOwnProperty(codFiscale) ? moment(allMorti[codFiscale]['dataNascita'], "DD/MM/YYYY") : null);
2835
+ let annoNascita = (dataNascita && dataNascita.isValid()) ? dataNascita.year() : Parser.cfToBirthYear(codFiscale);
2836
+ if (dataDecesso !== null)
2837
+ console.log(dataDecesso.format("DD/MM/YYYY"))
2838
+
2839
+ if (!allCf.hasOwnProperty(codFiscale) && (dataDecesso == null || dataDecesso.isSameOrAfter(dataInizio))) {
2840
+ allCf[codFiscale] = riga[1];
2841
+ let rigaT1 = {};
2842
+ rigaT1[0] = ""; // tipo
2843
+ rigaT1[1] = codFiscale;
2844
+ rigaT1[2] = ""; // validita ci
2845
+ rigaT1[3] = ""; // tipologia ci
2846
+ rigaT1[4] = annoNascita;
2847
+ rigaT1[5] = Parser.cfToGender(codFiscale) === "M" ? "1" : "2";
2848
+ rigaT1[6] = codFiscale.substring(11, 12) !== "Z" ? "IT" : "XX";
2849
+ rigaT1[7] = "9";
2850
+ rigaT1[8] = "190";
2851
+ rigaT1[9] = "205";
2852
+ rigaT1[10] = "083048";
2853
+ rigaT1[11] = "1";
2854
+ rigaT1[12] = "2";
2855
+ rigaT1[13] = "190";
2856
+ rigaT1[14] = "205";
2857
+ rigaT1[15] = dataInizio.format("DD/MM/YYYY");
2858
+ rigaT1[16] = ""; // id record
2859
+ rigaT1[17] = "2";
2860
+ rigaT1[18] = "1";
2861
+ rigaT1[19] = dataInizio.format("DD/MM/YYYY");
2862
+ rigaT1[20] = "1";
2863
+ rigaT1[21] = "1";
2864
+ rigaT1[22] = "3";
2865
+ rigaT1[23] = "9";
2866
+ rigaT1[24] = "2";
2867
+ rigaT1[25] = "9";
2868
+ rigaT1[26] = "2";
2869
+ rigaT1[27] = "2";
2870
+ rigaT1[28] = "2";
2871
+ rigaT1[29] = "2";
2872
+ rigaT1[30] = "2";
2873
+ rigaT1[31] = "2";
2874
+ rigaT1[32] = "2";
2875
+ rigaT1[33] = "2";
2876
+ rigaT1[34] = "2";
2877
+ rigaT1[35] = "2";
2878
+ rigaT1[36] = "2";
2879
+ rigaT1[37] = "2";
2880
+ rigaT1[38] = "2";
2881
+ rigaT1[39] = "2";
2882
+ rigaT1[40] = "2";
2883
+ rigaT1[41] = "2";
2884
+ rigaT1[42] = "2";
2885
+ rigaT1[43] = "2";
2886
+ rigaT1[44] = "2";
2887
+ rigaT1[45] = "2";
2888
+ rigaT1[46] = "2";
2889
+ rigaT1[47] = "3";
2890
+ rigaT1[48] = "3";
2891
+ rigaT1[49] = "3";
2892
+ rigaT1[50] = "3";
2893
+ rigaT1[51] = "3";
2894
+ rigaT1[52] = "3";
2895
+ rigaT1[53] = "3";
2896
+ let patologie = [
2897
+ "401", // ipertensione
2898
+ "413", // angina
2899
+ "427", // tachicardia
2900
+ "715", // artrosi
2901
+ "518", // insufficenza respiratoria
2902
+ "493", // asma
2903
+ "715", // osteortrite
2904
+ "707", // ulcera da decubito
2905
+ ]
2906
+ // put random value of patologie
2907
+ rigaT1[54] = patologie[Math.floor(Math.random() * patologie.length)];
2908
+ rigaT1[55] = "";
2909
+ if (chiaviValideAperte.length > 0) {
2910
+ rigaT1[56] = chiaviValideAperte[0];
2911
+ rigaT1[57] = moment(allChiaviValideAperte[chiaviValideAperte[0]][colonnaDataPresaInCaricoChiaviValide]).format("DD/MM/YYYY");
2912
+ } else {
2913
+ rigaT1[56] = "";
2914
+ rigaT1[57] = "";
2915
+ }
2916
+ rigaT1[58] = allMorti.hasOwnProperty(codFiscale) ? allMorti[codFiscale]['dataDecesso'] : "";
2917
+
2918
+ outTracciato1.push(rigaT1);
2919
+ }
2920
+ }
2921
+ }
2922
+ }
2923
+ await utils.scriviOggettoSuNuovoFileExcel(pathCartellaIn + path.sep + "tracciato1_out.xlsx", outTracciato1, null, false);
2924
+
2925
+
2926
+ let outTracciato2 = [];
2927
+ let rigaHeaderTracciato2 = {}
2928
+ for (let i = 0; i < Object.keys(tracciato2Maggioli).length; i++)
2929
+ rigaHeaderTracciato2[i] = tracciato2Maggioli[i];
2930
+ outTracciato2.push(rigaHeaderTracciato2);
2931
+
2932
+
2933
+ let allCfKey = Object.keys(allCf);
2934
+ for (let codFiscale of allCfKey) {
2935
+ console.log(codFiscale)
2936
+ let dataFromT2 = allCf[codFiscale];
2937
+ let data = moment(dataFromT2, "DD/MM/YYYY");
2938
+ // verifica che data sia tra inizio e fine, in caso metti una data casuale in questo range
2939
+ if (!data.isBetween(dataInizio, dataFine)) {
2940
+ let diffDays = dataFine.diff(dataInizio, 'days');
2941
+ let randomDays = Math.floor(Math.random() * diffDays);
2942
+ data = dataInizio.clone().add(randomDays, 'days');
2943
+ }
2944
+ let dataDecesso = allMorti.hasOwnProperty(codFiscale) ? moment(allMorti[codFiscale]['dataDecesso'], "DD/MM/YYYY") : null;
2945
+ if (dataDecesso == null || dataDecesso.isAfter(data)) {
2946
+ let rigaT2 = {}
2947
+ rigaT2[0] = codFiscale;
2948
+ rigaT2[1] = ""; // tipo
2949
+ rigaT2[2] = "190";
2950
+ rigaT2[3] = "205";
2951
+ rigaT2[4] = dataInizio.format("DD/MM/YYYY");
2952
+ rigaT2[5] = "";
2953
+ rigaT2[6] = ""
2954
+ rigaT2[7] = ""
2955
+ rigaT2[8] = ""
2956
+ rigaT2[9] = "1"
2957
+ rigaT2[10] = data.format("DD/MM/YYYY")// data accesso
2958
+ rigaT2[11] = "1" // tipo operatore
2959
+ rigaT2[12] = "6"; // tipo prestazione
2960
+ rigaT2[13] = ""; // data sospensione
2961
+ rigaT2[14] = ""; //motivo sospensione
2962
+ rigaT2[15] = "";
2963
+ outTracciato2.push(rigaT2);
2964
+ }
2965
+ }
2966
+
2967
+ await utils.scriviOggettoSuNuovoFileExcel(pathCartellaIn + path.sep + "tracciato2_out.xlsx", outTracciato2, null, false);
2968
+
2969
+
2970
+ }
2971
+
2972
+
2728
2973
  /**
2729
2974
  * Processes and develops ADP data for a company by reading and analyzing various input Excel files
2730
2975
  * containing data for active keys, living individuals, deceased individuals, and replacements.