diff --git a/.DS_Store b/.DS_Store index dce57f2..60709d6 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.env.development b/.env.development index 86c5b99..076722d 100644 --- a/.env.development +++ b/.env.development @@ -42,5 +42,5 @@ MIAB_HOST=box.lamiaposta.org MIAB_ADMIN_EMAIL=admin@lamiaposta.org MIAB_ADMIN_PASSWORD=passpao1pabox@1A DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7" -API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK" -SERVER_A_URL="http://51.77.156.69:3000" \ No newline at end of file +SERVER_A_URL="http://51.77.156.69:3000" +API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK" \ No newline at end of file diff --git a/emails/newsletter/it/html.pug b/emails/newsletter/it/html.pug index d8e525c..b700e41 100755 --- a/emails/newsletter/it/html.pug +++ b/emails/newsletter/it/html.pug @@ -18,232 +18,270 @@ - var baseimg = baseurl + '/' doctype html html - - if (dataemail.title) - head - title dataemail.subject + head + meta(charset="utf-8") + meta(name="viewport", content="width=device-width, initial-scale=1") + title= dataemail.title || "Email" + style. + /* embedded CSS */ + body { margin:0; padding:0; background:#E9F2F9; font-family:Tahoma, Geneva, sans-serif; color:#5b656e; } + a { color:#09c; text-decoration:none; } + table, td { border-collapse:collapse; } + h1,h2,h3,p { margin:0; padding:0; } - //- import css/scss stylesheets - //- these file names will be replace by gulp with proper css file paths - link(rel="stylesheet", href="../sass/basic.scss") - link(rel="stylesheet", href="../sass/one/styles.scss") + .logoContainer { text-align:center; padding:20px 0; } + .logoContainer img { max-width:200px; } - //- embdedded css allowed, but not sass - style. - .red { - background-color: #E84C50; - } + .testomail { padding:10px; font-size:0.75rem; line-height:1.4; } - .full-width { - width: 100%; - } + .clpromo { + background-color:orange; + text-align:center; + font-size:1rem; + padding:10px; + color:#fff; + font-weight:bold; + } + + .emailContainer { + background:#fff; + border-radius:10px; + padding:20px; + margin:20px auto; + } + + .sectionArticleImage img { + max-width:150px; + border:1px solid #ccc; + display:block; + margin-bottom:10px; + } + + .teacher { + font-style:italic; + font-size:0.75rem; + color:#555; + } + + .contrib { + font-size:0.75rem; + font-weight:bold; + color:#333; + } + + .button a { + display:inline-block; + padding:10px 20px; + background:#f75666; + color:#fff !important; + text-decoration:none; + border-radius:10px; + font-size:13px; + } + + .button2 a { + display:block; + padding:12px 20px; + background:#0000ff; + color:#fff !important; + text-decoration:none; + border-radius:10px; + font-size:1.15rem; + } + + .center_img img { + display:block; + margin:0 auto; + } + + .discContainer { + background:#fff; + border-radius:10px; + padding:20px; + margin:20px auto; + } + + .LinkDisc a { + text-decoration:none; + } + + .pDisc:hover { + background:#5c8ef4 !important; + color:#fff !important; + } + + .pDisc { + padding:5px 10px; + border-radius:10px; + display:inline-block; + } + + .socialMedia { + background:#8bafcb; + text-align:center; + padding:10px 0; + } + + .socialMedia img { + width:29px; + height:auto; + border:0; + } + + .firma-container { + background:#ffffff; + padding:15px; + text-align:center; + border-top:1px solid #e0e0e0; + font-size:0.85rem; + color:#313a42; + } + + .disclaimer-container { + background:#f9f9f9; + padding:15px; + text-align:center; + font-size:0.75rem; + color:#666; + border-top:1px solid #eee; + border-bottom:1px solid #eee; + } + + .bottom-container { + background:#e9f2f9; + padding:15px; + text-align:center; + font-size:0.7rem; + color:#999; + } + + .whitespace { + line-height:0; + font-size:0; + height:20px; + } + + @media only screen and (max-width:480px) { + .button a, .button2 a { + font-size:1rem !important; + width:100%; + } + .sectionArticleImage, + .column { + width:100% !important; + display:block !important; + } + } body(yahoofix) - span(id='body_style', style='display:block') - table(class="topHeader", cellpadding="0", cellspacing="0", width="100%") - - if (dataemail.height_logo) - tr - td - table(cellpadding="0", cellspacing="0", align="center", summary="") - tr - td.logoContainer - a(href=baseurl, title='logo') - img.logo(src=baseurl+"/images/logo.png", height=dataemail.height_logo) - + span#body_style(style='display:block') + // Header + table(width="100%", cellpadding="0", cellspacing="0", align="center") tr - td.testomail - p!= dataemail.templ.testoheadermail_out + td.logoContainer + a(href=baseurl) + img.logo(src=baseurl+"/public/images/logo.png", alt="Logo") - - if (dataemail.templ.options.includes('SHOW_PROMO')) + if dataemail.templ.testoheadermail_out + tr + td.testomail + p!= dataemail.templ.testoheadermail_out + + if dataemail.templ.options.includes('SHOW_PROMO') tr td.clpromo p!= dataemail.textpromo - - if (dataemail.templ.content) - table(cellpadding="0", cellspacing="0", width="95%", align="center") + // Main Content + if dataemail.templ.content + tr + td.emailContainer + p!=dataemail.templ.content + if dataemail.templ.img + img(src=baseimg + dataemail.templ.img, class="center_img") + if dataemail.templ.content2 + p!=dataemail.templ.content2 + if dataemail.templ.img2 + img(src=baseimg + dataemail.templ.img2, class="center_img") + + // Events + if dataemail.templ.options.includes('SHOW_EVENTS') + each event in arrevents tr - td(class="textIniContainer", valign="top") - p!=dataemail.templ.content - - if (dataemail.templ.img) - img(src=baseimg + dataemail.templ.img, alt="", class="myimg") - - if (dataemail.templ.content2) - p!=dataemail.templ.content2 - - if (dataemail.templ.img2) - img(src=baseimg + dataemail.templ.img2, alt="", class="myimg") - - table(cellpadding="0", cellspacing="0", width="640", align="center") - - if (dataemail.templ.options.includes('SHOW_EVENTS')) - tr - td(class="whitespace", height="10") - p   - tr - td(class="emailContainer", valign="top") - - each event in arrevents - - var urlevent = baseurl + '/event/' + event.typol + '?eventid=' + event._id - - var imgev = event.img_small - - var mydate = prettyDate(event.dateTimeStart) - unless (imgev) - - imgev = event.img - - var teacher1 = '' - - var teacher2 = '' - - var teacher3 = '' - - var teacher4 = '' - - var contrib = '' - - var myclteach = 'q-chip' - - if (event.op1[0] && event.op1[0].username !== 'nessuno') - - teacher1 = event.op1[0].name + ' ' + event.op1[0].surname - - if ((event.op2[0] && event.op2[0].username !== 'nessuno')) - - teacher2 = event.op2[0].name + ' ' + event.op2[0].surname - - myclteach = 'q-chip2' - - if (event.op3[0] && event.op3[0].username !== 'nessuno') - - teacher3 = "
" . event.op3[0].name + ' ' + event.op3[0].surname - - if (event.op4[0] && event.op4[0].username !== 'nessuno') - - teacher4 = "
" . event.op4[0].name + ' ' + event.op4[0].surname - - - if (event.contrib[0]) - - contrib = event.contrib[0].label - - if (event.contrib[0].showprice) - - contrib += ' ' + event.price + ' €' - - - table(cellpadding="0", cellspacing="0", width="100%", summary="", border="0", align="center") + td.emailContainer + table(width="100%", cellpadding="0", cellspacing="0") tr - td(class="column sectionArticleImage", valign="top") - table(cellpadding="0", cellspacing="0", summary="", border="0") - - if (event.news) - tr - td - p(class="q-chip row inline no-wrap items-center cltexth5 chipnews shadow-5 glossy text-right bg-red text-white") Novità + td.column.sectionArticleImage(width="150") + if event.news + p.q-chip.bg-red.text-white Novità + img(src=baseimg + (event.img_small || event.img), alt=event.title) + p.teacher= event.op1[0].name + ' ' + event.op1[0].surname + td.column + h2.sectionContentTitle= event.title + p.sectionContentSubTitle= prettyDate(event.dateTimeStart) + p.sectionContent!= event.details + if event.contrib.length + p.contrib= event.contrib[0].label + (event.contrib[0].showprice ? ' ' + event.price + ' €' : '') + table.buttonContainer tr - td - img(src=baseimg + imgev, alt="", width="150") - p(class="teacher") #{teacher1}
#{teacher2} #{teacher3} #{teacher4} - td(class="column", valign="top") + td.button + a(href=baseurl + '/event/' + event.typol + '?eventid=' + event._id, target="_blank") Apri l'Evento - table(cellpadding="0", cellspacing="0", summary="", border="0") - tr - td(class="sectionContentTitle boldhigh", valign="top") - p #{event.title} - tr - td(class="sectionContentSubTitle", valign="top") - p(class="q-chip row inline no-wrap items-center cltexth5 chipnews shadow-5 glossy text-right bg-blue text-white") #{mydate} - tr - td(class="sectionContent", valign="top") - p!= event.details - p.contrib= contrib - tr - td(class="buttonContainer") - table(width="50%", cellpadding="0", cellspacing="0", summary="", border="0") - tr - td(class="button hoverLink") - a(href=urlevent, title='Evento', target='_blank') Apri l'Evento + tr + td.center_img + a.button2(href=urlcal, target="_blank") Calendario Eventi + // Disciplines + if dataemail.templ.options.includes('SHOW_DISC') + tr + td.center + h2.cltitle_disc= dataemail.disc_title + each disc in dataemail.arrdiscipline tr - table(cellpadding="0", cellspacing="0", summary="", border="0", align="center", class="") - tr - td(class="whitespace", height="10") - p   - tr - td.center_img(class="button2 hoverLink") - a(href=urlcal, title='Calendario Eventi', target='_blank') Calendario Eventi - tr - td(class="whitespace", height="10") - p   - - - if (dataemail.templ.options.includes('SHOW_DISC')) - tr - td(class="whitespace bg-white", height="20") - p(class="bg-white")   - tr - td(class="center") - p(class="cltitle_disc") #{dataemail.disc_title} - tr - td(class="discContainer", valign="top") - - each disc in dataemail.arrdiscipline - - var urldisc = baseurl + disc.linkpage - - var imgdisc = disc.img_small - unless (imgdisc) - - imgdisc = disc.img - - table(cellpadding="0", cellspacing="0", width="100%", summary="", border="0", align="center") + td.discContainer + table(width="100%", cellpadding="0", cellspacing="0") tr - td(class="column sectionArticleImage", valign="top") - table(cellpadding="0", cellspacing="0", summary="", border="0") - tr - td - img(src=baseimg + imgdisc, alt="", width="150") - td(class="column", valign="top") + td.column.sectionArticleImage(width="150") + img(src=baseimg + (disc.img_small || disc.img), alt=disc.label) + td.column + p.LinkDisc + a(href=baseurl + disc.linkpage, target="_blank") + span.pDisc(style=`background-color:`+disc.color)= disc.label + p.sectionContent!= disc.description - table(cellpadding="0", cellspacing="0", summary="", border="0") - tr - td(class="sectionContentTitle boldhigh center LinkDisc", valign="top") - a(href=urldisc, title='Disciplina', target='_blank') - p(class="q-chip row inline no-wrap items-center cltexth4 chipnews shadow-5 glossy text-right text-white pDisc", style=`background-color: `+disc.color) #{disc.label} - tr - td(class="sectionContent", valign="top") - p!= disc.description - - tr - td(class="whitespace", height="20") - p   - - - if (dataemail.content_after_events) + // Additional Content + if dataemail.content_after_events tr - table(cellpadding="0", cellspacing="0", summary="", border="0") - tr - td.testomail - p!=dataemail.content_after_events - tr - td(class="whitespace", height="20") - p   + td.testomail + p!=dataemail.content_after_events // Social Media - table.socialMedia(cellpadding="0", cellspacing="0", width="100%", summary="", border="0", align="center") - tr - td(class="whitespace", height="5") - p   - tr - td - table(width="120", cellpadding="0", cellspacing="0", summary="", border="0", align="center") - tr - - if (dataemail.urlinstagram) - td(width="32", align="center") - a(href=dataemail.urlinstagram, title='Instagram') - img(src=imginstagram, alt="Instagram", width="29") - - if (dataemail.urltwitter) - td(width="32", align="center") - a(href=dataemail.urltwitter, title='Twitter') - img(src=imgtwitter, alt="Twitter", width="29") - - if (dataemail.urlfb) - td(width="32", align="center") - a(href=dataemail.urlfb, title='Facebook') - img(src=imgfb, alt="Facebook", width="29") - - if (dataemail.urlyoutube) - td(width="32", align="center") - a(href=dataemail.urlyoutube, title='YouTube') - img(src=imgyoutube, alt="YouTube", width="29") - - tr - td(class="whitespace", height="5") - p   + tr + td.socialMedia + table(width="120", align="center", cellpadding="0", cellspacing="0") + if dataemail.urlinstagram + td + a(href=dataemail.urlinstagram) + img(src=imginstagram, alt="Instagram") + if dataemail.urltwitter + td + a(href=dataemail.urltwitter) + img(src=imgtwitter, alt="Twitter") + if dataemail.urlfb + td + a(href=dataemail.urlfb) + img(src=imgfb, alt="Facebook") + if dataemail.urlyoutube + td + a(href=dataemail.urlyoutube) + img(src=imgyoutube, alt="YouTube") // Footer - table.footer(cellpadding="0", cellspacing="0", width="100%", summary="", border="0", align="center") - tr - td(class="whitespace", height="10") - p   - tr - td.firma - p!= dataemail.firma - - tr - td.disclaimer - p!= dataemail.disclaimer_out - - tr - td.bottom - p!= dataemail.disc_bottom_out - - tr - td(class="whitespace", height="10") - p   + tr + td.firma-container + p!= dataemail.firma + tr + td.disclaimer-container + p!= dataemail.disclaimer_out + tr + td.bottom-container + p!= dataemail.disc_bottom_out \ No newline at end of file diff --git a/src/server/controllers/articleController.js b/src/server/controllers/articleController.js index 113b8b0..73d6f05 100644 --- a/src/server/controllers/articleController.js +++ b/src/server/controllers/articleController.js @@ -345,7 +345,7 @@ exports.getTableContentBase = async (options) => { } if (!records || records.length === 0) { - console.log(`Nessun record trovato per la tabella ${options.nameTable}.`); + console.log(`Nessun record trovato per la tabella ${options.nameTable}.`); return []; } @@ -887,22 +887,34 @@ exports.mssqlmigrateTables = async (req) => { try { const options = req.body.mydata.options; let listaTabelle = []; - - - if (options?.tutte) { - // const listaTabelle = ['T_WEB_StatiProdotto']; - listaTabelle = ['T_WEB_TitoliOriginali', 'T_WEB_TestateOrdini', 'T_WEB_Ordini', 'T_WEB_Disponibile', 'T_WOO_TestateOrdini', 'T_WOO_Ordini', 'T_WEB_Articoli', - 'T_WEB_Argomenti', 'T_WEB_ClientiInternet', 'T_WOO_Clienti', 'T_WEB_Autori', 'T_WEB_Collane', 'T_WEB_MarchiEditoriali', 'T_WEB_StatiProdotto', 'T_WEB_TipiFormato', 'T_WEB_Tipologie', 'T_WEB_ArticoliFatturati', 'T_WEB_IdInternetFatturati', - 'T_WEB_Edizioni', 'T_WEB_Contratti']; - } else if (options?.parte1) { - listaTabelle = ['T_WEB_TitoliOriginali', 'T_WEB_TestateOrdini', 'T_WEB_Ordini', 'T_WOO_TestateOrdini', 'T_WOO_Ordini', 'T_WEB_Articoli']; - } else if (options?.parte2) { - listaTabelle = ['T_WEB_Disponibile', 'T_WEB_Argomenti', 'T_WEB_ClientiInternet', 'T_WOO_Clienti', 'T_WEB_Autori']; - } else if (options?.parte3) { - listaTabelle = ['T_WEB_Collane', 'T_WEB_MarchiEditoriali', 'T_WEB_StatiProdotto', 'T_WEB_TipiFormato', 'T_WEB_Tipologie', 'T_WEB_ArticoliFatturati', 'T_WEB_IdInternetFatturati', - 'T_WEB_Edizioni', 'T_WEB_Contratti']; - } else { - listaTabelle = ['T_WEB_Articoli']; + if (options?.parte1 || options?.tutte) { + listaTabelle.push({ table: 'T_WEB_TitoliOriginali', usaDataOra: true, fieldId: 'IdTitoloOriginale' }); + listaTabelle.push({ table: 'T_WEB_TestateOrdini', usaDataOra: false }); + listaTabelle.push({ table: 'T_WEB_Ordini', usaDataOra: false }); + listaTabelle.push({ table: 'T_WOO_TestateOrdini', usaDataOra: false }); + listaTabelle.push({ table: 'T_WOO_Ordini', usaDataOra: false }); + listaTabelle.push({ table: 'T_WEB_Articoli', usaDataOra: true, fieldId: 'IdArticolo' }); + } + if (options?.parte2 || options?.tutte) { + listaTabelle.push({ table: 'T_WEB_Disponibile', usaDataOra: true, fieldId: 'Codice' }); + listaTabelle.push({ table: 'T_WEB_Argomenti', usaDataOra: true, fieldId: 'IdArgomento' }); + listaTabelle.push({ table: 'T_WEB_ClientiInternet', usaDataOra: false }); + listaTabelle.push({ table: 'T_WOO_Clienti', usaDataOra: false }); + listaTabelle.push({ table: 'T_WEB_Autori', usaDataOra: true, fieldId: 'IdAutore' }); + } + if (options?.parte3 || options?.tutte) { + listaTabelle.push({ table: 'T_WEB_Collane', usaDataOra: true, fieldId: 'IdCollana' }); + listaTabelle.push({ table: 'T_WEB_MarchiEditoriali', usaDataOra: true, fieldId: 'IdMarchioEditoriale' }); + listaTabelle.push({ table: 'T_WEB_StatiProdotto', usaDataOra: true, fieldId: 'IdStatoProdotto' }); + listaTabelle.push({ table: 'T_WEB_TipiFormato', usaDataOra: true, fieldId: 'IdTipoFormato' }); + listaTabelle.push({ table: 'T_WEB_Tipologie', usaDataOra: true, fieldId: 'IdTipologia' }); + listaTabelle.push({ table: 'T_WEB_ArticoliFatturati', usaDataOra: false }); + listaTabelle.push({ table: 'T_WEB_IdInternetFatturati', usaDataOra: false }); + listaTabelle.push({ table: 'T_WEB_Edizioni', usaDataOra: true, fieldId: 'IdEdizione' }); + listaTabelle.push({ table: 'T_WEB_Contratti', usaDataOra: true, fieldId: 'IdArticolo' }); + } + if (options?.test) { + listaTabelle.push({ table: 'T_WEB_Articoli', usaDataOra: true, fieldId: 'IdArticolo' }); } const migrator = new MssqlMigrator(); diff --git a/src/server/models/JobsInProgress.js b/src/server/models/JobsInProgress.js new file mode 100755 index 0000000..01527b8 --- /dev/null +++ b/src/server/models/JobsInProgress.js @@ -0,0 +1,128 @@ +const mongoose = require('mongoose').set('debug', false) +const Schema = mongoose.Schema; + +const tools = require('../tools/general'); + +mongoose.Promise = global.Promise; +mongoose.level = "F"; + +const shared_consts = require('../tools/shared_nodejs'); + +// Resolving error Unknown modifier: $pushAll +mongoose.plugin(schema => { + schema.options.usePushEach = true +}); + + +const JobsInProgressSchema = new Schema({ + idapp: { + type: String, + }, + descr: { + type: String, + }, + nomeFunzioneDbOp: { + type: String, + }, + status: { + type: Number, + }, + terminatedWhy: { + type: Number, + }, + comment: { + type: String, + }, + date_created: { + type: Date, + default: Date.now + }, +}); + +JobsInProgressSchema.statics.chechifExistJobWorking = async function (jobData, terminateiftoolong) { + + // controlla se esiste un altro job, non ancora terminato !STATUS_JOB.FINISH + // se esiste già allora ritorna false + const existingJob = await this.findOne({ idapp: jobData.idapp, nomeFunzioneDbOp: jobData.nomeFunzioneDbOp, status: { $ne: shared_consts.STATUS_JOB.FINISH } }); + if (existingJob) { + // se il Job trovato è passato troppo tempo (oltre 3 ore date_created), allora fai finta che abbia già terminato + // (in questo caso, non ritorna false, ma ritorna il job trovato, per permettere di gestire il caso in cui si vuole forzare il job a terminare) + if (Math.abs(Date.now() - existingJob.date_created.getTime()) > 180 * 60 * 1000) { + if (terminateiftoolong) { + existingJob.status = shared_consts.STATUS_JOB.FINISH; + existingJob.terminatedWhy = shared_consts.TERMINATED_WHY.TOOLONGTIME; + await existingJob.save(); + return false; + } + } else { + return true; // E' in FUNZIONE il JOB + } + } + + return false; +}; + +JobsInProgressSchema.statics.addNewJob = async function (jobData) { + if (!jobData || typeof jobData !== 'object') { + console.error('ERRORE: ❌ jobData deve essere un oggetto valido'); + } + + const esistegia = await this.chechifExistJobWorking(jobData, true); + + if (esistegia) { + return null; + } + + try { + const newJob = await this.create(jobData); + return newJob; + } catch (err) { + console.error('Errore nell\'aggiungere un nuovo record: ', err); + throw err; + } +}; + +JobsInProgressSchema.methods.terminateJob = async function (witherror) { + try { + + this.status = shared_consts.STATUS_JOB.FINISH; + this.terminatedWhy = witherror ? shared_consts.TERMINATED_WHY.END_WITHERROR : shared_consts.TERMINATED_WHY.END_NORMALLY; + await this.save(); + return true; + } catch (err) { + console.error('Errore durante la terminazione del job: ', err); + return false; + } +}; + + +JobsInProgressSchema.statics.getFieldsForSearch = function () { + return [{ field: 'descr', type: tools.FieldType.string }] +}; + +JobsInProgressSchema.statics.executeQueryTable = function (idapp, params, user) { + params.fieldsearch = this.getFieldsForSearch(); + return tools.executeQueryTable(this, idapp, params, user); +}; + +JobsInProgressSchema.statics.findAllIdApp = async function (idapp) { + const JobsInProgress = this; + + try { + return await JobsInProgress.find({ idapp }).then((arrrec) => { + return arrrec; + }); + + } catch (err) { + console.error('Errore: ', err); + } +}; + +const JobsInProgress = mongoose.model('JobsInProgress', JobsInProgressSchema); + +JobsInProgress.createIndexes() + .then(() => { }) + .catch((err) => { throw err; }); + + +module.exports = { JobsInProgress }; diff --git a/src/server/models/catalog.js b/src/server/models/catalog.js index eebc331..2bf38ce 100755 --- a/src/server/models/catalog.js +++ b/src/server/models/catalog.js @@ -26,16 +26,17 @@ const CatalogSchema = new Schema({ }, title: { type: String, + index: true, }, foto_collana: IImg, - + idCollane: [{ type: String, }], idTipoFormato: [{ type: Number, }], - + argomenti: [{ type: String, }], @@ -43,9 +44,9 @@ const CatalogSchema = new Schema({ type: Number, default: 0, }, - + editore: [{ type: String }], - + descr_introduttiva: { type: String, }, @@ -93,6 +94,7 @@ const CatalogSchema = new Schema({ type: Schema.Types.ObjectId, ref: 'Product', }], + isCatalogoGenerale: Boolean, }); /* @@ -143,89 +145,102 @@ CatalogSchema.statics.executeQueryTable = function (idapp, params, user) { CatalogSchema.statics.findAllIdApp = async function (idapp) { const Catalog = this; - let arrrec = await Catalog.find({ idapp }) - .sort({ title: 1 }) // Ordina i risultati per titolo - /*.populate({ - path: "idCollane", // Popola il campo idCollane - model: "Collana" // Specifica il modello della collezione Collana - })*/ - .populate({ - path: "lista_prodotti", // Popola il campo lista_prodotti - populate: { - path: "idProductInfo", - model: "ProductInfo", - populate: [ - { - path: "idCatProds", - model: "CatProd" - }, - { - path: "idSubCatProds", - model: "SubCatProd" - }, - { - path: "idAuthors", - model: "Author" - } - ], - }, - }) - .populate({ - path: "lista_prodotti", - populate: { - path: "idProducer", - model: "Producer" - } - }) - .populate({ - path: "lista_prodotti", - populate: { - path: "idProvider", - model: "Provider" - } - }) - .populate({ - path: "lista_prodotti", - populate: { - path: "idStorehouses", - model: "Storehouse" - } - }) - .populate({ - path: "lista_prodotti", - populate: { - path: "idScontisticas", - model: "Scontistica" - } - }) - .populate({ - path: "lista_prodotti", - populate: { - path: "idGasordine", - model: "Gasordine" - } - }) - ; + try { + let arrrec = await Catalog.find({ idapp }) + .sort({ title: 1 }) // Ordina i risultati per titolo + /*.populate({ + path: "idCollane", // Popola il campo idCollane + model: "Collana" // Specifica il modello della collezione Collana + })*/ + .populate({ + path: "lista_prodotti", // Popola il campo lista_prodotti + populate: { + path: "idProductInfo", + model: "ProductInfo", + populate: [ + { + path: "idCatProds", + model: "CatProd" + }, + { + path: "idSubCatProds", + model: "SubCatProd" + }, + { + path: "idAuthors", + model: "Author" + } + ], + }, + }) + .populate({ + path: "lista_prodotti", + populate: { + path: "idProducer", + model: "Producer" + } + }) + .populate({ + path: "lista_prodotti", + populate: { + path: "idProvider", + model: "Provider" + } + }) + .populate({ + path: "lista_prodotti", + populate: { + path: "idStorehouses", + model: "Storehouse" + } + }) + .populate({ + path: "lista_prodotti", + populate: { + path: "idScontisticas", + model: "Scontistica" + } + }) + .populate({ + path: "lista_prodotti", + populate: { + path: "idGasordine", + model: "Gasordine" + } + }) + ; - const transformedArrRec = arrrec.map(catalog => ({ - ...catalog.toObject(), // Converte il documento Mongoose in un oggetto JavaScript puro - lista_prodotti: catalog.lista_prodotti.map(product => ({ - ...product.toObject(), - productInfo: { - ...product.idProductInfo.toObject(), // Copia tutti i campi di idProductInfo - catprods: product.idProductInfo.idCatProds, // Rinomina idCatProds in catprods - subcatprods: product.idProductInfo.idSubCatProds, - collana: product.idProductInfo.idCollana, - authors: product.idProductInfo.idAuthors, - }, - producer: product.idProducer, - storehouse: product.idStorehouses, - scontisticas: product.idScontisticas, - gasordine: product.idGasordine, - })), - })); + // controlla prima se nella lista ci sono dei product che non esistono piu allora li devi rimuovere ! + for (const catalog of arrrec) { + const originalLength = catalog.lista_prodotti.length; + catalog.lista_prodotti = catalog.lista_prodotti.filter(product => product.idProductInfo); + if (catalog.lista_prodotti.length !== originalLength) { + await catalog.save(); + } + } - return transformedArrRec; + const transformedArrRec = arrrec.map(catalog => ({ + ...catalog.toObject(), // Converte il documento Mongoose in un oggetto JavaScript puro + lista_prodotti: catalog.lista_prodotti.map(product => ({ + ...product.toObject(), + productInfo: { + ...product.idProductInfo.toObject(), // Copia tutti i campi di idProductInfo + catprods: product.idProductInfo.idCatProds, // Rinomina idCatProds in catprods + subcatprods: product.idProductInfo.idSubCatProds, + collana: product.idProductInfo.idCollana, + authors: product.idProductInfo.idAuthors, + }, + producer: product.idProducer, + storehouse: product.idStorehouses, + scontisticas: product.idScontisticas, + gasordine: product.idGasordine, + })), + })); + + return transformedArrRec; + } catch (err) { + console.error('Errore: ', err); + } }; const Catalog = mongoose.model('Catalog', CatalogSchema); diff --git a/src/server/models/catprod.js b/src/server/models/catprod.js index 9ed3e4e..ce5d06c 100755 --- a/src/server/models/catprod.js +++ b/src/server/models/catprod.js @@ -21,6 +21,7 @@ const CatProdSchema = new Schema({ }, name: { type: String, + index: 1, }, descr_estesa: { type: String, @@ -100,7 +101,7 @@ CatProdSchema.statics.updateCatDeleteEmpty = async function (idapp) { }; -CatProdSchema.statics.getCatProdWithTitleCount = async function (idapp) { +CatProdSchema.statics.getCatProdWithTitleCount = async function (idapp, updatedata) { try { const myquery = [ @@ -151,11 +152,13 @@ CatProdSchema.statics.getCatProdWithTitleCount = async function (idapp) { const result = await CatProd.aggregate(myquery); - for (const record of result) { - await CatProd.updateOne( - { _id: record._id }, - { $set: { quanti: record.quanti } } - ); + if (updatedata) { + for (const record of result) { + await CatProd.updateOne( + { _id: record._id }, + { $set: { quanti: record.quanti } } + ); + } } return result; diff --git a/src/server/models/collana.js b/src/server/models/collana.js index 79ee5bc..62a44b2 100755 --- a/src/server/models/collana.js +++ b/src/server/models/collana.js @@ -20,6 +20,7 @@ const CollanaSchema = new Schema({ }, title: { type: String, + index: true, }, dataOra: { type: Date, @@ -55,7 +56,7 @@ module.exports.findAllIdApp = async function (idapp) { return await Collana.find(myfind).sort({title: 1}).lean(); }; -module.exports.getCollaneWithTitleCount = async function (idapp) { +module.exports.getCollaneWithTitleCount = async function (idapp, updatedata) { try { const myquery = [ @@ -104,11 +105,13 @@ module.exports.getCollaneWithTitleCount = async function (idapp) { const result = await Collana.aggregate(myquery); - for (const record of result) { - await Collana.updateOne( - { _id: record._id }, - { $set: { quanti: record.quanti } } - ); + if (updatedata) { + for (const record of result) { + await Collana.updateOne( + { _id: record._id }, + { $set: { quanti: record.quanti } } + ); + } } return result; diff --git a/src/server/models/cron.js b/src/server/models/cron.js new file mode 100755 index 0000000..97b6eab --- /dev/null +++ b/src/server/models/cron.js @@ -0,0 +1,77 @@ +const mongoose = require('mongoose').set('debug', false) +const Schema = mongoose.Schema; + +const tools = require('../tools/general'); + +mongoose.Promise = global.Promise; +mongoose.level = "F"; + + +// Resolving error Unknown modifier: $pushAll +mongoose.plugin(schema => { + schema.options.usePushEach = true +}); + + +const CronSchema = new Schema({ + idapp: { + type: String, + }, + active: { + type: Boolean, + default: false, + }, + descr: { + type: String, + }, + nomeFunzioneDbOp: { + type: String, + }, + startTime: { // Orario iniziale (es. "08:30") + type: String, + default: "00:00" + }, + everyXHours: { // Esegui ogni X ore + type: Number, + default: 24 + }, + date_created: { + type: Date, + default: Date.now + }, + date_updated: { + type: Date, + }, +}); + + +CronSchema.statics.getFieldsForSearch = function () { + return [{ field: 'descr', type: tools.FieldType.string }] +}; + +CronSchema.statics.executeQueryTable = function (idapp, params, user) { + params.fieldsearch = this.getFieldsForSearch(); + return tools.executeQueryTable(this, idapp, params, user); +}; + +CronSchema.statics.findAllIdApp = async function (idapp) { + const Cron = this; + + try { + return await Cron.find({ idapp }).then((arrrec) => { + return arrrec; + }); + + } catch (err) { + console.error('Errore: ', err); + } +}; + +const Cron = mongoose.model('Cron', CronSchema); + +Cron.createIndexes() + .then(() => { }) + .catch((err) => { throw err; }); + + +module.exports = { Cron }; diff --git a/src/server/models/destnewsletter.js b/src/server/models/destnewsletter.js new file mode 100755 index 0000000..ca3ef26 --- /dev/null +++ b/src/server/models/destnewsletter.js @@ -0,0 +1,59 @@ +const mongoose = require('mongoose').set('debug', false) +const Schema = mongoose.Schema; + +const tools = require('../tools/general'); + +mongoose.Promise = global.Promise; +mongoose.level = "F"; + + +// Resolving error Unknown modifier: $pushAll +mongoose.plugin(schema => { + schema.options.usePushEach = true +}); + +const DestNewsletterSchema = new Schema({ + idapp: { + type: String, + }, + descr: { + type: String, + }, + tipodest_id: { + type: Number, + }, +}); + +DestNewsletterSchema.statics.getFieldsForSearch = function () { + return [] +}; + +DestNewsletterSchema.statics.executeQueryTable = function (idapp, params) { + params.fieldsearch = this.getFieldsForSearch(); + return tools.executeQueryTable(this, idapp, params); +}; + +DestNewsletterSchema.statics.DuplicateAllRecords = async function (idapporig, idappdest) { + + return await tools.DuplicateAllRecords(this, idapporig, idappdest); + +}; + + +DestNewsletterSchema.statics.findAllIdApp = async function (idapp) { + const DestNewsletter = this; + + const myfind = { idapp }; + + return await DestNewsletter.find(myfind).lean(); +}; + + +const DestNewsletter = mongoose.model('DestNewsletter', DestNewsletterSchema); + +DestNewsletter.createIndexes() + .then(() => { }) + .catch((err) => { throw err; }); + + +module.exports = { DestNewsletter }; diff --git a/src/server/models/mailinglist.js b/src/server/models/mailinglist.js index 92b5091..3ffa562 100755 --- a/src/server/models/mailinglist.js +++ b/src/server/models/mailinglist.js @@ -39,6 +39,41 @@ MailingListSchema.statics.findAllIdAppSubscribed = function (idapp) { return tools.findAllQueryIdApp(User, myfind); }; +MailingListSchema.statics.findAllIdAppDiarioSubscr = function (idapp) { + + const myfind = { + idapp, + diario_on: true, + $or: [ + { deleted: { $exists: false } }, + { deleted: { $exists: true, $eq: false } }], + $or: [ + { email_errata: { $exists: false } }, + { email_errata: { $exists: true, $ne: true } }], + }; + + // Extract only the Teacher where in the users table the field permissions is set 'Teacher' bit. + + return tools.findAllQueryIdApp(User, myfind); +}; +MailingListSchema.statics.findAllIdAppDiarioSubscr = function (idapp) { + + const myfind = { + idapp, + test: true, + $or: [ + { deleted: { $exists: false } }, + { deleted: { $exists: true, $eq: false } }], + $or: [ + { email_errata: { $exists: false } }, + { email_errata: { $exists: true, $ne: true } }], + }; + + // Extract only the Teacher where in the users table the field permissions is set 'Teacher' bit. + + return tools.findAllQueryIdApp(User, myfind); +}; + MailingListSchema.statics.getnumSent = async function (idapp, idUser) { const myfind = { idapp, news_on: true, lastid_newstosent: idUser }; diff --git a/src/server/models/newstosent.js b/src/server/models/newstosent.js index c4eda3e..3ab41ef 100755 --- a/src/server/models/newstosent.js +++ b/src/server/models/newstosent.js @@ -22,6 +22,9 @@ const NewstosentSchema = new Schema({ templemail_str: { type: String, }, + destnewsletter_str: { + type: String, + }, activate: { type: Boolean, default: false @@ -121,7 +124,10 @@ NewstosentSchema.statics.findNewsletterPending_To_Send = function (idapp) { starting_job: true, finish_job: false, processing_job: false, - lastemailsent_Job: { $gte: tools.IncDateNow(-1000 * 60 * 60 * 15) }, + $or: [ + { lastemailsent_Job: { $gte: tools.IncDateNow(-1000 * 60 * 60 * 15) } }, + { lastemailsent_Job: null } + ], idapp }).lean(); }; diff --git a/src/server/models/order.js b/src/server/models/order.js index a4baebb..e84bf3e 100755 --- a/src/server/models/order.js +++ b/src/server/models/order.js @@ -22,9 +22,9 @@ const orderSchema = new Schema({ }, userId: { type: Schema.Types.ObjectId, ref: 'User' }, status: { - type: Number, + type: Number, index: true }, - idProduct: { type: Schema.Types.ObjectId, ref: 'Product' }, + idProduct: { type: Schema.Types.ObjectId, ref: 'Product', index: true }, idProducer: { type: Schema.Types.ObjectId, ref: 'Producer' }, idStorehouse: { type: Schema.Types.ObjectId, ref: 'StoreHouse' }, idScontisticas: [{ type: Schema.Types.ObjectId, ref: 'Scontistica' }], @@ -125,7 +125,8 @@ const orderSchema = new Schema({ type: String }, modify_at: { - type: Date + type: Date, + index: true }, }); diff --git a/src/server/models/product.js b/src/server/models/product.js index 2f59cae..dc17775 100755 --- a/src/server/models/product.js +++ b/src/server/models/product.js @@ -36,16 +36,16 @@ const productSchema = new Schema({ isbn: { type: String, }, - idProductInfo: { type: Schema.Types.ObjectId, ref: 'ProductInfo' }, - idProducer: { type: Schema.Types.ObjectId, ref: 'Producer' }, + idProductInfo: { type: Schema.Types.ObjectId, ref: 'ProductInfo', index: true }, + idProducer: { type: Schema.Types.ObjectId, ref: 'Producer', index: true }, idStorehouses: [ - { type: Schema.Types.ObjectId, ref: 'Storehouse' } + { type: Schema.Types.ObjectId, ref: 'Storehouse', index: true } ], - idGasordine: { type: Schema.Types.ObjectId, ref: 'Gasordine' }, + idGasordine: { type: Schema.Types.ObjectId, ref: 'Gasordine', index: true }, idScontisticas: [ - { type: Schema.Types.ObjectId, ref: 'Scontistica' } + { type: Schema.Types.ObjectId, ref: 'Scontistica', index: true } ], - idProvider: { type: Schema.Types.ObjectId, ref: 'Provider' }, + idProvider: { type: Schema.Types.ObjectId, ref: 'Provider', index: true }, prezzo_ivato: { // Con IVA type: Number }, @@ -137,6 +137,7 @@ const productSchema = new Schema({ stockQty: { // in magazzino type: Number, default: 0, + index: true, }, stockBloccatiQty: { // Prenotati Bloccati type: Number, @@ -220,7 +221,7 @@ const productSchema = new Schema({ }, validaprod: { esito: { - type: Number, + type: Number, }, data: { type: Date, @@ -468,176 +469,33 @@ module.exports.findAllIdApp = async function (idapp, code, id, all) { // return await Product.find(myfind); query.push( + // PRIMO FILTRO: riduce subito il numero di documenti { $match: myfind }, + + // UNICO LOOKUP ORDERS CON FACET PER RIDURRE I DOPPIONI { $lookup: { - from: 'producers', - localField: 'idProducer', - foreignField: '_id', - as: 'producer' - } - }, - { - $unwind: { - path: '$producer', - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: 'productinfos', - localField: 'idProductInfo', - foreignField: '_id', - as: 'productInfo' - } - }, - { - $unwind: { - path: '$productInfo', - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: 'gasordines', - localField: 'idGasordine', - foreignField: '_id', - as: 'gasordine' - } - }, - { - $unwind: { - path: '$gasordine', - preserveNullAndEmptyArrays: true, - }, - }, - { - $match: { - $or: [ - { 'gasordine.active': true }, // Include documents where gasordines.active is true - { 'gasordine': { $exists: false } } // Include documents where gasordines array doesn't exist - ] - } - }, - { - $group: { - _id: '$_id', - gasordine: { $first: '$gasordine' }, - originalFields: { $first: '$$ROOT' } // Preserve all existing fields - } - }, - { - $replaceRoot: { - newRoot: { - $mergeObjects: ['$originalFields', { gasordine: '$gasordine' }] - } - } - }, - { - $lookup: { - from: 'providers', - localField: 'idProvider', - foreignField: '_id', - as: 'provider' - } - }, - { - $unwind: { - path: '$provider', - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: 'authors', - localField: 'productInfo.idAuthors', - foreignField: '_id', - as: 'productInfo.authors' - } - }, - { - $lookup: { - from: 'publishers', - localField: 'productInfo.idPublisher', - foreignField: '_id', - as: 'productInfo.publisher' - } - }, - { - $unwind: { - path: '$productInfo.publisher', - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: 'collanas', - localField: 'productInfo.idCollana', - foreignField: '_id', - as: 'productInfo.collana' - } - }, - { - $unwind: { - path: '$productInfo.collana', - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: 'catprods', - localField: 'productInfo.idCatProds', - foreignField: '_id', - as: 'productInfo.catprods' - } - }, - { - $lookup: { - from: 'subcatprods', - localField: 'productInfo.idSubCatProds', - foreignField: '_id', - as: 'productInfo.subcatprods' - } - }, - { - $lookup: { - from: 'scontisticas', - localField: 'idScontisticas', - foreignField: '_id', - as: 'scontisticas' - } - }, - { - $lookup: { - from: 'storehouses', - localField: 'idStorehouses', - foreignField: '_id', - as: 'storehouses' - } - }, - { - $lookup: { - from: 'orders', - let: { productId: '$_id' }, + from: "orders", + let: { productId: "$_id" }, pipeline: [ { $match: { $expr: { $and: [ - { $eq: ['$idProduct', '$$productId'] }, + { $eq: ["$idProduct", "$$productId"] }, { $or: [ + { $eq: ["$status", shared_consts.OrderStatus.CHECKOUT_SENT] }, { - $eq: ['$status', shared_consts.OrderStatus.CHECKOUT_SENT] - }, - { - $and: [{ $lt: ['$status', shared_consts.OrderStatus.CHECKOUT_SENT] }, - { - $gt: [ - '$modify_at', - { $subtract: [new Date(), 60 * 60 * 1000] } // 1 hour in milliseconds 60 * 60 - ] - }] + $and: [ + { $lt: ["$status", shared_consts.OrderStatus.CHECKOUT_SENT] }, + { + $gt: [ + "$modify_at", + { $subtract: [new Date(), 60 * 60 * 1000] } + ] + } + ] } ] } @@ -648,102 +506,162 @@ module.exports.findAllIdApp = async function (idapp, code, id, all) { { $group: { _id: null, - totalQty: { $sum: '$quantity' }, + totalQty: { $sum: "$quantity" }, + totalQtyPreordered: { $sum: "$quantitypreordered" } } } ], - as: 'productOrders' - } - }, - { - $lookup: { - from: 'orders', - let: { productId: '$_id' }, - pipeline: [ - { - $match: { - $expr: { - $and: [ - { $eq: ['$idProduct', '$$productId'] }, - { - $or: [ - { - $eq: ['$status', shared_consts.OrderStatus.CHECKOUT_SENT] - }, - { - $and: [{ $lt: ['$status', shared_consts.OrderStatus.CHECKOUT_SENT] }, - { - $gt: [ - '$modify_at', - { $subtract: [new Date(), 60 * 60 * 1000] } // 1 hour in milliseconds 60 * 60 - ] - }] - } - ] - } - ] - } - } - }, - { - $group: { - _id: null, - totalQtyPreordered: { $sum: '$quantitypreordered' } - } - } - ], - as: 'productPreOrders' + as: "orderSummary" } }, + + // ESTRAGGO LE QUANTITÀ IN CAMPI AGGIUNTIVI { $addFields: { QuantitaOrdinateInAttesa: { - $ifNull: [ - { - $cond: { - if: { $isArray: '$productOrders' }, - then: { $arrayElemAt: ['$productOrders.totalQty', 0] }, - else: 0 - } - }, - 0 - ] + $ifNull: [{ $arrayElemAt: ["$orderSummary.totalQty", 0] }, 0] }, QuantitaPrenotateInAttesa: { - $ifNull: [ - { - $cond: { - if: { $isArray: '$productPreOrders' }, - then: { $arrayElemAt: ['$productPreOrders.totalQtyPreordered', 0] }, - else: 0 - } - }, - 0 - ] - }, - }, - }, - { - $addFields: { - quantityAvailable: { - $subtract: ["$stockQty", "$QuantitaOrdinateInAttesa"], - }, - bookableAvailableQty: { - $subtract: ["$maxbookableGASQty", "$QuantitaPrenotateInAttesa"], + $ifNull: [{ $arrayElemAt: ["$orderSummary.totalQtyPreordered", 0] }, 0] } } }, + + // CALCOLO DELLE DISPONIBILITÀ { - $unset: 'productOrders' - }, - { - $unset: 'productPreOrders' - }, - { - $sort: { - 'productInfo.name': 1 // 1 for ascending order, -1 for descending order + $addFields: { + quantityAvailable: { + $subtract: ["$stockQty", "$QuantitaOrdinateInAttesa"] + }, + bookableAvailableQty: { + $subtract: ["$maxbookableGASQty", "$QuantitaPrenotateInAttesa"] + } } }, + + // ELIMINO IL RISULTATO TEMPORANEO + { $unset: "orderSummary" }, + + // LOOKUP MULTIPLI MA ORGANIZZATI + { + $lookup: { + from: "producers", + localField: "idProducer", + foreignField: "_id", + as: "producer" + } + }, + { $unwind: { path: "$producer", preserveNullAndEmptyArrays: true } }, + + { + $lookup: { + from: "productinfos", + localField: "idProductInfo", + foreignField: "_id", + as: "productInfo" + } + }, + { $unwind: { path: "$productInfo", preserveNullAndEmptyArrays: true } }, + + { + $lookup: { + from: "gasordines", + localField: "idGasordine", + foreignField: "_id", + as: "gasordine" + } + }, + { $unwind: { path: "$gasordine", preserveNullAndEmptyArrays: true } }, + + // FILTRO DOPO LOOKUP SU GASORDINE + { + $match: { + $or: [ + { "gasordine.active": true }, + { gasordine: { $exists: false } } + ] + } + }, + + // LOOKUP SU AUTHORS + { + $lookup: { + from: "authors", + localField: "productInfo.idAuthors", + foreignField: "_id", + as: "productInfo.authors" + } + }, + + // LOOKUP PUBBLICATORI, COLLANE, CATEGORIE, ECC. + { + $lookup: { + from: "publishers", + localField: "productInfo.idPublisher", + foreignField: "_id", + as: "productInfo.publisher" + } + }, + { $unwind: { path: "$productInfo.publisher", preserveNullAndEmptyArrays: true } }, + + { + $lookup: { + from: "collanas", + localField: "productInfo.idCollana", + foreignField: "_id", + as: "productInfo.collana" + } + }, + { $unwind: { path: "$productInfo.collana", preserveNullAndEmptyArrays: true } }, + + { + $lookup: { + from: "catprods", + localField: "productInfo.idCatProds", + foreignField: "_id", + as: "productInfo.catprods" + } + }, + { + $lookup: { + from: "subcatprods", + localField: "productInfo.idSubCatProds", + foreignField: "_id", + as: "productInfo.subcatprods" + } + }, + { + $lookup: { + from: "scontisticas", + localField: "idScontisticas", + foreignField: "_id", + as: "scontisticas" + } + }, + { + $lookup: { + from: "storehouses", + localField: "idStorehouses", + foreignField: "_id", + as: "storehouses" + } + }, + { + $lookup: { + from: "providers", + localField: "idProvider", + foreignField: "_id", + as: "provider" + } + }, + { $unwind: { path: "$provider", preserveNullAndEmptyArrays: true } }, + + // ORDINAMENTO FINALE + { + $sort: { + "productInfo.name": 1 + } + } ); // console.log('query=', query); diff --git a/src/server/models/productInfo.js b/src/server/models/productInfo.js index 6b35fb6..a5186f6 100755 --- a/src/server/models/productInfo.js +++ b/src/server/models/productInfo.js @@ -41,6 +41,7 @@ const productInfoSchema = new Schema({ }, name: { type: String, + index: true, }, description: { type: String, @@ -48,10 +49,10 @@ const productInfoSchema = new Schema({ short_descr: { type: String, }, - idCatProds: [{ type: Schema.Types.ObjectId, ref: 'CatProd' }], + idCatProds: [{ type: Schema.Types.ObjectId, ref: 'CatProd', index: true }], idSubCatProds: [{ type: Schema.Types.ObjectId, ref: 'SubCatProd' }], idStatoProdotto: { - type: Number + type: Number, index: true }, color: { type: String @@ -86,6 +87,9 @@ const productInfoSchema = new Schema({ imagefile: { type: String, }, + image_not_found: { + type: Boolean, + }, vers_img: { type: Number, }, @@ -122,9 +126,9 @@ const productInfoSchema = new Schema({ note: { type: String, }, - idAuthors: [{ type: Schema.Types.ObjectId, ref: 'Author' }], - idCollana: { type: Schema.Types.ObjectId, ref: 'Collana' }, - idPublisher: { type: Schema.Types.ObjectId, ref: 'Publisher' }, + idAuthors: [{ type: Schema.Types.ObjectId, ref: 'Author', index: true }], + idCollana: { type: Schema.Types.ObjectId, ref: 'Collana', index: true }, + idPublisher: { type: Schema.Types.ObjectId, ref: 'Publisher', index: true }, collezione: { type: String, }, @@ -133,6 +137,7 @@ const productInfoSchema = new Schema({ }, date_pub: { type: Date, + index: 1, }, date_pub_ts: { type: Number, @@ -144,7 +149,7 @@ const productInfoSchema = new Schema({ date_updated: { type: Date, }, - + date_updated_fromGM: { type: Date, }, @@ -156,8 +161,13 @@ const productInfoSchema = new Schema({ fatLast6M: Number, fatLast1Y: Number, fatLast2Y: Number, - vLast6M: Number, - vLast1Y: Number, + vLast6M: { + type: Number, + index: true, + }, + vLast1Y: { + type: Number, index: true + }, vLast2Y: Number, dataUltimoOrdine: Date, rank3M: Number, @@ -492,6 +502,60 @@ module.exports.updateProductInfoByStats = async function (idapp) { return mylogtot; } +// crea setImgNotFound + +module.exports.setImgNotFound = async function (id) { + // set sul record id il flag image_not_found + try { + const ProductInfo = this; + + await ProductInfo.updateOne( + { _id: id }, + { $set: { image_not_found: true } } + ); + console.log(`Flag image_not_found set for record with id: ${id}`); + } catch (error) { + console.error(`Error setting image_not_found flag for id ${id}:`, error); + } + +} + +// crea una funzione che mi rimuove il record "product" che utilizza productInfo, nel caso in cui date_updated_fromGM non esista +module.exports.removeProductInfoWithoutDateUpdatedFromGM = async function (idapp) { + const ProductInfo = this; + + const globalTables = require('../tools/globalTables'); + const Product = globalTables.getTableByTableName('Product'); + + let mylog; + + try { + const arrproductInfo = await ProductInfo.find({ idapp, date_updated_fromGM: { $exists: false } }); + + if (arrproductInfo.length > 0) { + mylog = `Rimuovo ${arrproductInfo.length} productInfo senza date_updated_fromGM !!` + console.log(mylog); + + for (const productinfo of arrproductInfo) { + // cerca nella tabella Product se esiste idProductInfo = _id e cancella tutti i record che hanno questa corrispondenza + if (Product) { + await Product.deleteMany({ idProductInfo: productinfo._id }); + } + + // Ora rimuovi anche questo productInfo + await ProductInfo.deleteOne({ _id: productinfo._id }); + } + } + + return mylog; + } catch (error) { + mylog += 'Error removing productInfo without date_updated_fromGM:' + error; + console.error(mylog); + } + + return mylog; +}; + module.exports.createIndexes() .then(() => { }) .catch((err) => { throw err; }); diff --git a/src/server/models/publisher.js b/src/server/models/publisher.js index 38b4ed2..f6fafef 100755 --- a/src/server/models/publisher.js +++ b/src/server/models/publisher.js @@ -48,7 +48,7 @@ module.exports.findAllIdApp = async function (idapp) { return await Publisher.find(myfind).sort({ name: 1 }).lean(); }; -module.exports.getEditoriWithTitleCount = async function (idapp) { +module.exports.getEditoriWithTitleCount = async function (idapp, updatedata) { try { const myquery = [ @@ -95,11 +95,13 @@ module.exports.getEditoriWithTitleCount = async function (idapp) { const result = await Publisher.aggregate(myquery); - for (const record of result) { - await Publisher.updateOne( - { _id: record._id }, - { $set: { quanti: record.quanti } } - ); + if (updatedata) { + for (const record of result) { + await Publisher.updateOne( + { _id: record._id }, + { $set: { quanti: record.quanti } } + ); + } } return result; diff --git a/src/server/models/templemail.js b/src/server/models/templemail.js index 74316f5..5f4b104 100755 --- a/src/server/models/templemail.js +++ b/src/server/models/templemail.js @@ -25,6 +25,15 @@ const TemplEmailSchema = new Schema({ content: { type: String, }, + disclaimer: { + type: String, + }, + piedipagina: { + type: String, + }, + firma: { + type: String, + }, img: { type: String, }, diff --git a/src/server/models/user.js b/src/server/models/user.js index 6a51d4b..14b2514 100755 --- a/src/server/models/user.js +++ b/src/server/models/user.js @@ -177,6 +177,12 @@ const UserSchema = new mongoose.Schema({ news_on: { type: Boolean, }, + diario_on: { + type: Boolean, + }, + test: { + type: Boolean, + }, email_errata: { type: Boolean, }, @@ -640,7 +646,7 @@ UserSchema.statics.canHavePower = function (perm) { try { let consentito = false; if (User.isAdmin(perm) || User.isManager(perm) || - User.isEditor(perm) || User.isFacilitatore(perm)) { + User.isEditor(perm) || User.isCommerciale(perm) || User.isFacilitatore(perm)) { consentito = true; } @@ -696,6 +702,14 @@ UserSchema.statics.isEditor = function (perm) { return false; } }; +UserSchema.statics.isCommerciale = function (perm) { + try { + return ((perm & shared_consts.Permissions.Commerciale) === + shared_consts.Permissions.Commerciale); + } catch (e) { + return false; + } +}; UserSchema.statics.isGrafico = function (perm) { try { return ((perm & shared_consts.Permissions.Grafico) === @@ -759,7 +773,7 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, wi if (!token) { console.warn('TOKEN VUOTO ! '); return { user, code }; - } + } try { @@ -822,7 +836,7 @@ UserSchema.statics.findByToken = async function (token, typeaccess, con_auth, wi const end_find = process.hrtime.bigint(); // console.log(` User.findOne LEAN impiega ${Math.round(Number(end_find - start_find) / 1e6) / 1000} secondi.`); } - + if (user) { const checkExpiry = tools.getEnableTokenExpiredByIdApp(user.idapp); @@ -1350,6 +1364,16 @@ UserSchema.statics.setaportador_solidario = async function ( return !!myrec; }; +UserSchema.statics.setNewsletterToAll = async function ( + idapp) { + const User = this; + + return await User.updateMany({ + idapp, + $or: [{ deleted: { $exists: false } }, { deleted: { $exists: true, $eq: false } }], + }, { $set: { 'news_on': true } }, + { new: false }); +}; UserSchema.statics.setNewsletter = async function ( idapp, username, newsletter_on) { const User = this; diff --git a/src/server/modules/Macro.js b/src/server/modules/Macro.js index 2ec8ca5..dbb4ea5 100644 --- a/src/server/modules/Macro.js +++ b/src/server/modules/Macro.js @@ -15,6 +15,7 @@ const { getTableContent } = require('../controllers/articleController'); const T_WEB_ArticoliFatturati = require('../models/t_web_articolifatturati'); const T_WEB_Ordini = require('../models/t_web_ordini'); +const { JobsInProgress } = require('../models/JobsInProgress'); class Macro { constructor(idapp, options) { @@ -51,6 +52,18 @@ class Macro { idapp: options.idapp, } + let myjob = null; + + const lavoromassivo = options.caricatutti; + if (lavoromassivo) { + myjob = await JobsInProgress.addNewJob({ idapp, descr: 'Riaggiorna Articoli', nomeFunzioneDbOp: 'updateAllBook', status: shared_consts.STATUS_JOB.START }); + if (!myjob) { + mylog = 'ATTENZIONE! ❌ STAVO GIA ESEGUENDO QUESTO JOB, quindi ESCO !'; + console.error(mylog); + return { updated: opt.updated, imported: opt.imported, errors: opt.errors, mylog, idRecUpdated: opt.idRecUpdated, table: opt.table }; + } + } + try { @@ -417,13 +430,21 @@ class Macro { console.log('numrec', numrec); } + let rimuoviTabellePerIniziare = false; + let count = 0; if (Array.isArray(recproducts)) { - for (const recproduct of recproducts) { - // if (!options.caricatutti) { - await this.elaboraProdotto(recproduct, opt); + if (recproducts.length > 10 && lavoromassivo) { + // rimuovi dalla tabella productInfo tutti i campi date_updated_fromGM + const result = await ProductInfo.updateMany({ idapp: options.idapp }, { $unset: { date_updated_fromGM: null } }); + let quanti_rimossi = result.modifiedCount; + console.log(`Sbianca date_updated_fromGM da ProductInfo: (${quanti_rimossi} su ${result.matchedCount})`); + rimuoviTabellePerIniziare = true; + } + for (const recproduct of recproducts) { + await this.elaboraProdotto(recproduct, opt); const sku = recproduct.IdArticolo; @@ -439,6 +460,12 @@ class Macro { } //} } + + if (rimuoviTabellePerIniziare) { + await ProductInfo.removeProductInfoWithoutDateUpdatedFromGM(options.idapp); + } + if (myjob) + await myjob.terminateJob(); } if (numrec > 1) { @@ -455,6 +482,8 @@ class Macro { } catch (e) { mylog += 'ERRORE ! *** IMPORTATI: ' + opt?.imported + ' AGGIORNATI = ' + opt?.updated + ' (su ' + numrec + ' RECORD)'; opt.logerror = e.message; + if (myjob) + await myjob.terminateJob(true); console.error(e.message); return { updated: opt.updated, imported: opt.imported, errors: opt.errors, mylog, logerror: opt.logerror }; } @@ -1075,6 +1104,15 @@ class Macro { return listaCampi.some((campo) => recordOld[campo] !== recordNew[campo]); } + async getStat() { + let mystr = ''; + + const ris = await ProductInfo.countDocuments({ $or: [{ date_updated_fromGM: { $exists: false } }, { date_updated_fromGM: null }] }); + mystr += `${ris} ProductInfo non aggiornati da GM, quindi da cancellare ! \n`; + + return mystr; + } + } module.exports = Macro; \ No newline at end of file diff --git a/src/server/modules/MssqlMigrator.js b/src/server/modules/MssqlMigrator.js index e874806..40bbb58 100644 --- a/src/server/modules/MssqlMigrator.js +++ b/src/server/modules/MssqlMigrator.js @@ -33,13 +33,27 @@ class MssqlMigrator { let indtab = 0; let indtabok = 0; const logs = []; - for (const tableName of tableNames) { + for (const rectable of tableNames) { try { + const tableName = rectable.table; + const usaDataOra = rectable.usaDataOra; + const fieldId = rectable.fieldId; const percentuale = ((indtab / numtables) * 100).toFixed(2); logs.push(`\n>> Recupero dati da MSSQL per la tabella: ${tableName} - (Completamento: ${percentuale}%)`); console.log(logs[logs.length - 1]); - const dataQuery = `SELECT * FROM [${tableName}]`; + let dataQuery = `SELECT * FROM ${tableName}`; + + if (usaDataOra) { + dataQuery = `SELECT T.* FROM ${tableName} T`; + dataQuery += ` JOIN ( + SELECT ${fieldId}, MAX(DataOra) AS data + FROM ${tableName} + GROUP BY ${fieldId} + ) b ON T.${fieldId} = b.${fieldId} AND T.DataOra = b.data; `; + } + + console.log('query', dataQuery); let dataResponse = null; @@ -51,11 +65,12 @@ class MssqlMigrator { null, { timeout: 900000 }); } catch (error) { - console.error('Error: ', error); + console.error('Error: ', error.response?.data?.error || error.message || error); if (error.message === 'socket hang up') { console.log('Error: hangup, waiting 5 seconds and retrying...'); await new Promise(resolve => setTimeout(resolve, 5000)); + dataResponse = await axios.post( `${this.serverUrl}/query`, { query: dataQuery }, @@ -69,6 +84,7 @@ class MssqlMigrator { } } + const records = dataResponse?.data; if (!records || records.length === 0) { diff --git a/src/server/populate/cities_sardegna.js b/src/server/populate/cities_sardegna.js new file mode 100644 index 0000000..674a423 --- /dev/null +++ b/src/server/populate/cities_sardegna.js @@ -0,0 +1,3381 @@ +module.exports = { + list: [ + { + "comune": "Abbasanta", + "prov": "OR", + "reg": "SAR", + "pref": "0785", + "cap": "315", + "abitanti": "2579", + "country": "IT" + }, + { + "comune": "Aggius", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "514", + "abitanti": "1409", + "country": "IT" + }, + { + "comune": "Aglientu", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "420", + "abitanti": "1154", + "country": "IT" + }, + { + "comune": "Aidomaggiore", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "250", + "abitanti": "398", + "country": "IT" + }, + { + "comune": "Alà dei Sardi", + "prov": "SS", + "reg": "SAR", + "pref": "0784", + "cap": "663", + "abitanti": "1764", + "country": "IT" + }, + { + "comune": "Albagiara", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "215", + "abitanti": "246", + "country": "IT" + }, + { + "comune": "Ales", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "194", + "abitanti": "1285", + "country": "IT" + }, + { + "comune": "Alghero", + "prov": "SS", + "reg": "SAR", + "pref": "079", + "cap": "7", + "abitanti": "42352", + "country": "IT" + }, + { + "comune": "Allai", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "60", + "abitanti": "355", + "country": "IT" + }, + { + "comune": "Anela", + "prov": "SS", + "reg": "SAR", + "pref": "0784", + "cap": "446", + "abitanti": "584", + "country": "IT" + }, + { + "comune": "Arborea", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "7", + "abitanti": "3758", + "country": "IT" + }, + { + "comune": "Arbus", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "311", + "abitanti": "5869", + "country": "IT" + }, + { + "comune": "Ardara", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "296", + "abitanti": "736", + "country": "IT" + }, + { + "comune": "Ardauli", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "421", + "abitanti": "782", + "country": "IT" + }, + { + "comune": "Aritzo", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "796", + "abitanti": "1223", + "country": "IT" + }, + { + "comune": "Armungia", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "366", + "abitanti": "424", + "country": "IT" + }, + { + "comune": "Arzachena", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "85", + "abitanti": "13331", + "country": "IT" + }, + { + "comune": "Arzana", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "672", + "abitanti": "2255", + "country": "IT" + }, + { + "comune": "Assemini", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "6", + "abitanti": "25944", + "country": "IT" + }, + { + "comune": "Assolo", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "255", + "abitanti": "348", + "country": "IT" + }, + { + "comune": "Asuni", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "233", + "abitanti": "311", + "country": "IT" + }, + { + "comune": "Atzara", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "553", + "abitanti": "1018", + "country": "IT" + }, + { + "comune": "Austis", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "737", + "abitanti": "767", + "country": "IT" + }, + { + "comune": "Badesi", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "102", + "abitanti": "1825", + "country": "IT" + }, + { + "comune": "Ballao", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "98", + "abitanti": "731", + "country": "IT" + }, + { + "comune": "Banari", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "419", + "abitanti": "533", + "country": "IT" + }, + { + "comune": "Baradili", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "165", + "abitanti": "75", + "country": "IT" + }, + { + "comune": "Baratili San Pietro", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "11", + "abitanti": "1206", + "country": "IT" + }, + { + "comune": "Baressa", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "165", + "abitanti": "566", + "country": "IT" + }, + { + "comune": "Bari Sardo", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "51", + "abitanti": "3830", + "country": "IT" + }, + { + "comune": "Barrali", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "140", + "abitanti": "1094", + "country": "IT" + }, + { + "comune": "Barumini", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "202", + "abitanti": "1178", + "country": "IT" + }, + { + "comune": "Bauladu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "38", + "abitanti": "649", + "country": "IT" + }, + { + "comune": "Baunei", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "480", + "abitanti": "3442", + "country": "IT" + }, + { + "comune": "Belvì", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "787", + "abitanti": "560", + "country": "IT" + }, + { + "comune": "Benetutti", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "406", + "abitanti": "1704", + "country": "IT" + }, + { + "comune": "Berchidda", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "290", + "abitanti": "2630", + "country": "IT" + }, + { + "comune": "Bessude", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "447", + "abitanti": "391", + "country": "IT" + }, + { + "comune": "Bidonì", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "250", + "abitanti": "127", + "country": "IT" + }, + { + "comune": "Birori", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "450", + "abitanti": "500", + "country": "IT" + }, + { + "comune": "Bitti", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "549", + "abitanti": "2597", + "country": "IT" + }, + { + "comune": "Bolotana", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "472", + "abitanti": "2403", + "country": "IT" + }, + { + "comune": "Bonarcado", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "284", + "abitanti": "1503", + "country": "IT" + }, + { + "comune": "Bonnanaro", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "405", + "abitanti": "930", + "country": "IT" + }, + { + "comune": "Bono", + "prov": "SS", + "reg": "SAR", + "pref": "0784", + "cap": "540", + "abitanti": "3331", + "country": "IT" + }, + { + "comune": "Bonorva", + "prov": "SS", + "reg": "SAR", + "pref": "0785", + "cap": "508", + "abitanti": "3211", + "country": "IT" + }, + { + "comune": "Boroneddu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "206", + "abitanti": "154", + "country": "IT" + }, + { + "comune": "Borore", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "394", + "abitanti": "1982", + "country": "IT" + }, + { + "comune": "Bortigali", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "510", + "abitanti": "1244", + "country": "IT" + }, + { + "comune": "Bortigiadas", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "476", + "abitanti": "729", + "country": "IT" + }, + { + "comune": "Borutta", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "471", + "abitanti": "260", + "country": "IT" + }, + { + "comune": "Bosa", + "prov": "OR", + "reg": "SAR", + "pref": "0785", + "cap": "2", + "abitanti": "7465", + "country": "IT" + }, + { + "comune": "Bottidda", + "prov": "SS", + "reg": "SAR", + "pref": "0785", + "cap": "396", + "abitanti": "649", + "country": "IT" + }, + { + "comune": "Buddusò", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "700", + "abitanti": "3622", + "country": "IT" + }, + { + "comune": "Budoni", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "16", + "abitanti": "5298", + "country": "IT" + }, + { + "comune": "Buggerru", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "51", + "abitanti": "1050", + "country": "IT" + }, + { + "comune": "Bultei", + "prov": "SS", + "reg": "SAR", + "pref": "0785", + "cap": "509", + "abitanti": "841", + "country": "IT" + }, + { + "comune": "Bulzi", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "250", + "abitanti": "469", + "country": "IT" + }, + { + "comune": "Burcei", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "648", + "abitanti": "2646", + "country": "IT" + }, + { + "comune": "Burgos", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "575", + "abitanti": "855", + "country": "IT" + }, + { + "comune": "Busachi", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "379", + "abitanti": "1164", + "country": "IT" + }, + { + "comune": "Cabras", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "6", + "abitanti": "8760", + "country": "IT" + }, + { + "comune": "Cagliari", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "6", + "abitanti": "148881", + "country": "IT" + }, + { + "comune": "Calangianus", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "518", + "abitanti": "3814", + "country": "IT" + }, + { + "comune": "Calasetta", + "prov": "SU", + "reg": "SAR", + "pref": "0781", + "cap": "9", + "abitanti": "2773", + "country": "IT" + }, + { + "comune": "Capoterra", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "54", + "abitanti": "23172", + "country": "IT" + }, + { + "comune": "Carbonia", + "prov": "SU", + "reg": "SAR", + "pref": "0781", + "cap": "111", + "abitanti": "26390", + "country": "IT" + }, + { + "comune": "Cardedu", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "19", + "abitanti": "1923", + "country": "IT" + }, + { + "comune": "Cargeghe", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "333", + "abitanti": "585", + "country": "IT" + }, + { + "comune": "Carloforte", + "prov": "SU", + "reg": "SAR", + "pref": "0781", + "cap": "10", + "abitanti": "5953", + "country": "IT" + }, + { + "comune": "Castelsardo", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "114", + "abitanti": "5651", + "country": "IT" + }, + { + "comune": "Castiadas", + "prov": "SU", + "reg": "SAR", + "pref": "0782", + "cap": "60", + "abitanti": "1651", + "country": "IT" + }, + { + "comune": "Cheremule", + "prov": "SS", + "reg": "SAR", + "pref": "0784", + "cap": "540", + "abitanti": "402", + "country": "IT" + }, + { + "comune": "Chiaramonti", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "430", + "abitanti": "1511", + "country": "IT" + }, + { + "comune": "Codrongianos", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "317", + "abitanti": "1279", + "country": "IT" + }, + { + "comune": "Collinas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "249", + "abitanti": "774", + "country": "IT" + }, + { + "comune": "Cossoine", + "prov": "SS", + "reg": "SAR", + "pref": "0785", + "cap": "529", + "abitanti": "762", + "country": "IT" + }, + { + "comune": "Cuglieri", + "prov": "OR", + "reg": "SAR", + "pref": "0785", + "cap": "479", + "abitanti": "2457", + "country": "IT" + }, + { + "comune": "Curcuris", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "130", + "abitanti": "311", + "country": "IT" + }, + { + "comune": "Decimomannu", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "10", + "abitanti": "8293", + "country": "IT" + }, + { + "comune": "Decimoputzu", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "17", + "abitanti": "4164", + "country": "IT" + }, + { + "comune": "Desulo", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "888", + "abitanti": "2137", + "country": "IT" + }, + { + "comune": "Dolianova", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "212", + "abitanti": "9411", + "country": "IT" + }, + { + "comune": "Domus de Maria", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "66", + "abitanti": "1621", + "country": "IT" + }, + { + "comune": "Domusnovas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "52", + "abitanti": "5886", + "country": "IT" + }, + { + "comune": "Donori", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "141", + "abitanti": "1964", + "country": "IT" + }, + { + "comune": "Dorgali", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "390", + "abitanti": "8299", + "country": "IT" + }, + { + "comune": "Dualchi", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "321", + "abitanti": "577", + "country": "IT" + }, + { + "comune": "Elini", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "472", + "abitanti": "559", + "country": "IT" + }, + { + "comune": "Elmas", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "7", + "abitanti": "9358", + "country": "IT" + }, + { + "comune": "Erula", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "457", + "abitanti": "690", + "country": "IT" + }, + { + "comune": "Escalaplano", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "338", + "abitanti": "2079", + "country": "IT" + }, + { + "comune": "Escolca", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "416", + "abitanti": "542", + "country": "IT" + }, + { + "comune": "Espotlatu", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "473", + "abitanti": "381", + "country": "IT" + }, + { + "comune": "Esterzili", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "731", + "abitanti": "565", + "country": "IT" + }, + { + "comune": "Florinas", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "417", + "abitanti": "1442", + "country": "IT" + }, + { + "comune": "Fluminimaggiore", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "63", + "abitanti": "2654", + "country": "IT" + }, + { + "comune": "Flussio", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "305", + "abitanti": "425", + "country": "IT" + }, + { + "comune": "Fonni", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "1000", + "abitanti": "3696", + "country": "IT" + }, + { + "comune": "Fordongianus", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "35", + "abitanti": "852", + "country": "IT" + }, + { + "comune": "Furtei", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "90", + "abitanti": "1527", + "country": "IT" + }, + { + "comune": "Gadoni", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "696", + "abitanti": "703", + "country": "IT" + }, + { + "comune": "Gairo", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "670", + "abitanti": "1293", + "country": "IT" + }, + { + "comune": "Galtellì", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "35", + "abitanti": "2363", + "country": "IT" + }, + { + "comune": "Gavoi", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "777", + "abitanti": "2492", + "country": "IT" + }, + { + "comune": "Genoni", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "447", + "abitanti": "768", + "country": "IT" + }, + { + "comune": "Genuri", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "230", + "abitanti": "314", + "country": "IT" + }, + { + "comune": "Gergei", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "374", + "abitanti": "1129", + "country": "IT" + }, + { + "comune": "Gesico", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "300", + "abitanti": "781", + "country": "IT" + }, + { + "comune": "Gesturi", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "315", + "abitanti": "1153", + "country": "IT" + }, + { + "comune": "Ghilarza", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "290", + "abitanti": "4207", + "country": "IT" + }, + { + "comune": "Giave", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "595", + "abitanti": "493", + "country": "IT" + }, + { + "comune": "Giba", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "59", + "abitanti": "1889", + "country": "IT" + }, + { + "comune": "Girasole", + "prov": "NU", + "reg": "SAR", + "pref": "0782", + "cap": "10", + "abitanti": "1326", + "country": "IT" + }, + { + "comune": "Golfo Aranci", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "19", + "abitanti": "2366", + "country": "IT" + }, + { + "comune": "Goni", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "383", + "abitanti": "455", + "country": "IT" + }, + { + "comune": "Gonnese", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "40", + "abitanti": "4674", + "country": "IT" + }, + { + "comune": "Gonnoscodina", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "112", + "abitanti": "435", + "country": "IT" + }, + { + "comune": "Gonnosfanadiga", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "180", + "abitanti": "6185", + "country": "IT" + }, + { + "comune": "Gonnosnò", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "220", + "abitanti": "712", + "country": "IT" + }, + { + "comune": "Gonnostramatza", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "104", + "abitanti": "809", + "country": "IT" + }, + { + "comune": "Guamaggiore", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "199", + "abitanti": "917", + "country": "IT" + }, + { + "comune": "Guasila", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "210", + "abitanti": "2482", + "country": "IT" + }, + { + "comune": "Guspini", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "130", + "abitanti": "11060", + "country": "IT" + }, + { + "comune": "Iglesias", + "prov": "SU", + "reg": "SAR", + "pref": "0781", + "cap": "200", + "abitanti": "25288", + "country": "IT" + }, + { + "comune": "Ilbono", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "400", + "abitanti": "1977", + "country": "IT" + }, + { + "comune": "Illorai", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "503", + "abitanti": "760", + "country": "IT" + }, + { + "comune": "Irgoli", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "26", + "abitanti": "2220", + "country": "IT" + }, + { + "comune": "Isili", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "523", + "abitanti": "2516", + "country": "IT" + }, + { + "comune": "Ittireddu", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "313", + "abitanti": "481", + "country": "IT" + }, + { + "comune": "Ittiri", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "400", + "abitanti": "8069", + "country": "IT" + }, + { + "comune": "Jerzu", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "427", + "abitanti": "3034", + "country": "IT" + }, + { + "comune": "La Maddalena", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "19", + "abitanti": "10617", + "country": "IT" + }, + { + "comune": "Laconi", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "555", + "abitanti": "1673", + "country": "IT" + }, + { + "comune": "Laerru", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "165", + "abitanti": "859", + "country": "IT" + }, + { + "comune": "Lanusei", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "595", + "abitanti": "5064", + "country": "IT" + }, + { + "comune": "Las Plassas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "148", + "abitanti": "215", + "country": "IT" + }, + { + "comune": "Lei", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "456", + "abitanti": "466", + "country": "IT" + }, + { + "comune": "Loceri", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "206", + "abitanti": "1272", + "country": "IT" + }, + { + "comune": "Loculi", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "26", + "abitanti": "507", + "country": "IT" + }, + { + "comune": "Lodè", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "345", + "abitanti": "1606", + "country": "IT" + }, + { + "comune": "Lodine", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "884", + "abitanti": "306", + "country": "IT" + }, + { + "comune": "Loiri Porto San Paolo", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "105", + "abitanti": "3641", + "country": "IT" + }, + { + "comune": "Lotzorai", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "11", + "abitanti": "2093", + "country": "IT" + }, + { + "comune": "Lula", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "521", + "abitanti": "1263", + "country": "IT" + }, + { + "comune": "Lunamatrona", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "168", + "abitanti": "1634", + "country": "IT" + }, + { + "comune": "Luogosanto", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "321", + "abitanti": "1807", + "country": "IT" + }, + { + "comune": "Luras", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "508", + "abitanti": "2429", + "country": "IT" + }, + { + "comune": "Macomer", + "prov": "NU", + "reg": "SAR", + "pref": "0785", + "cap": "563", + "abitanti": "9410", + "country": "IT" + }, + { + "comune": "Magomadas", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "263", + "abitanti": "582", + "country": "IT" + }, + { + "comune": "Mamoiada", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "544", + "abitanti": "2404", + "country": "IT" + }, + { + "comune": "Mandas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "457", + "abitanti": "2009", + "country": "IT" + }, + { + "comune": "Mara", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "257", + "abitanti": "525", + "country": "IT" + }, + { + "comune": "Maracalagonis", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "86", + "abitanti": "7873", + "country": "IT" + }, + { + "comune": "Marrubiu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "7", + "abitanti": "4609", + "country": "IT" + }, + { + "comune": "Martis", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "295", + "abitanti": "471", + "country": "IT" + }, + { + "comune": "Masainas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "57", + "abitanti": "1217", + "country": "IT" + }, + { + "comune": "Masullas", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "129", + "abitanti": "1011", + "country": "IT" + }, + { + "comune": "Meana Sardo", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "588", + "abitanti": "1602", + "country": "IT" + }, + { + "comune": "Milis", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "72", + "abitanti": "1423", + "country": "IT" + }, + { + "comune": "Modolo", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "134", + "abitanti": "156", + "country": "IT" + }, + { + "comune": "Mogorella", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "265", + "abitanti": "410", + "country": "IT" + }, + { + "comune": "Mogoro", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "136", + "abitanti": "3941", + "country": "IT" + }, + { + "comune": "Monastir", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "81", + "abitanti": "4432", + "country": "IT" + }, + { + "comune": "Monserrato", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "8", + "abitanti": "19037", + "country": "IT" + }, + { + "comune": "Monteleone Rocca Doria", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "368", + "abitanti": "109", + "country": "IT" + }, + { + "comune": "Monti", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "300", + "abitanti": "2331", + "country": "IT" + }, + { + "comune": "Montresta", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "410", + "abitanti": "438", + "country": "IT" + }, + { + "comune": "Mores", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "366", + "abitanti": "1747", + "country": "IT" + }, + { + "comune": "Morgongiori", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "351", + "abitanti": "660", + "country": "IT" + }, + { + "comune": "Muravera", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "9", + "abitanti": "5131", + "country": "IT" + }, + { + "comune": "Muros", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "305", + "abitanti": "829", + "country": "IT" + }, + { + "comune": "Musei", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "119", + "abitanti": "1491", + "country": "IT" + }, + { + "comune": "Narbolia", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "57", + "abitanti": "1690", + "country": "IT" + }, + { + "comune": "Narcao", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "125", + "abitanti": "3089", + "country": "IT" + }, + { + "comune": "Neoneli", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "554", + "abitanti": "622", + "country": "IT" + }, + { + "comune": "Noragugume", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "288", + "abitanti": "286", + "country": "IT" + }, + { + "comune": "Norbellu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "350", + "abitanti": "1115", + "country": "IT" + }, + { + "comune": "Nughedu San Nicolò", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "577", + "abitanti": "763", + "country": "IT" + }, + { + "comune": "Nughedu Santa Vittoria", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "496", + "abitanti": "441", + "country": "IT" + }, + { + "comune": "Nule", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "650", + "abitanti": "1276", + "country": "IT" + }, + { + "comune": "Nulvi", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "478", + "abitanti": "2634", + "country": "IT" + }, + { + "comune": "Nuoro", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "549", + "abitanti": "34105", + "country": "IT" + }, + { + "comune": "Nurachi", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "7", + "abitanti": "1677", + "country": "IT" + }, + { + "comune": "Nuragus", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "359", + "abitanti": "841", + "country": "IT" + }, + { + "comune": "Nurallao", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "390", + "abitanti": "1167", + "country": "IT" + }, + { + "comune": "Nuraminis", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "91", + "abitanti": "2317", + "country": "IT" + }, + { + "comune": "Nureci", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "335", + "abitanti": "319", + "country": "IT" + }, + { + "comune": "Nurri", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "612", + "abitanti": "2025", + "country": "IT" + }, + { + "comune": "Nuxis", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "196", + "abitanti": "1434", + "country": "IT" + }, + { + "comune": "Olbia", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "15", + "abitanti": "60385", + "country": "IT" + }, + { + "comune": "Oliena", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "379", + "abitanti": "6640", + "country": "IT" + }, + { + "comune": "Ollastra", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "23", + "abitanti": "1131", + "country": "IT" + }, + { + "comune": "Ollolai", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "960", + "abitanti": "1190", + "country": "IT" + }, + { + "comune": "Olmedo", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "68", + "abitanti": "4113", + "country": "IT" + }, + { + "comune": "Olzai", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "474", + "abitanti": "770", + "country": "IT" + }, + { + "comune": "Onanì", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "482", + "abitanti": "371", + "country": "IT" + }, + { + "comune": "Onifai", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "29", + "abitanti": "696", + "country": "IT" + }, + { + "comune": "Oniferi", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "478", + "abitanti": "873", + "country": "IT" + }, + { + "comune": "Orani", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "523", + "abitanti": "2706", + "country": "IT" + }, + { + "comune": "Orgosolo", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "620", + "abitanti": "3950", + "country": "IT" + }, + { + "comune": "Oristano", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "9", + "abitanti": "30541", + "country": "IT" + }, + { + "comune": "Orosei", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "19", + "abitanti": "6765", + "country": "IT" + }, + { + "comune": "Orotelli", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "406", + "abitanti": "1894", + "country": "IT" + }, + { + "comune": "Orroli", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "550", + "abitanti": "2012", + "country": "IT" + }, + { + "comune": "Ortacesus", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "161", + "abitanti": "881", + "country": "IT" + }, + { + "comune": "Ortueri", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "584", + "abitanti": "1044", + "country": "IT" + }, + { + "comune": "Orune", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "745", + "abitanti": "2150", + "country": "IT" + }, + { + "comune": "Oschiri", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "202", + "abitanti": "3037", + "country": "IT" + }, + { + "comune": "Osidda", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "650", + "abitanti": "219", + "country": "IT" + }, + { + "comune": "Osilo", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "672", + "abitanti": "2826", + "country": "IT" + }, + { + "comune": "Osini", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "645", + "abitanti": "725", + "country": "IT" + }, + { + "comune": "Ossi", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "322", + "abitanti": "5462", + "country": "IT" + }, + { + "comune": "Ottana", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "185", + "abitanti": "2195", + "country": "IT" + }, + { + "comune": "Ovodda", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "751", + "abitanti": "1521", + "country": "IT" + }, + { + "comune": "Ozieri", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "390", + "abitanti": "9857", + "country": "IT" + }, + { + "comune": "Pabillonis", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "42", + "abitanti": "2510", + "country": "IT" + }, + { + "comune": "Padria", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "304", + "abitanti": "603", + "country": "IT" + }, + { + "comune": "Padru", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "160", + "abitanti": "2040", + "country": "IT" + }, + { + "comune": "Palau", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "5", + "abitanti": "4034", + "country": "IT" + }, + { + "comune": "Palmas Arborea", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "4", + "abitanti": "1475", + "country": "IT" + }, + { + "comune": "Pattada", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "794", + "abitanti": "2880", + "country": "IT" + }, + { + "comune": "Pau", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "315", + "abitanti": "276", + "country": "IT" + }, + { + "comune": "Pauli Arbarei", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "140", + "abitanti": "565", + "country": "IT" + }, + { + "comune": "Paulilatino", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "280", + "abitanti": "2087", + "country": "IT" + }, + { + "comune": "Perdasdefogu", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "600", + "abitanti": "1751", + "country": "IT" + }, + { + "comune": "Perdaxius", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "98", + "abitanti": "1310", + "country": "IT" + }, + { + "comune": "Perfugas", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "90", + "abitanti": "2234", + "country": "IT" + }, + { + "comune": "Pimentel", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "154", + "abitanti": "1126", + "country": "IT" + }, + { + "comune": "Piscinas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "66", + "abitanti": "816", + "country": "IT" + }, + { + "comune": "Ploaghe", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "427", + "abitanti": "4348", + "country": "IT" + }, + { + "comune": "Pompu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "147", + "abitanti": "215", + "country": "IT" + }, + { + "comune": "Porto Torres", + "prov": "SS", + "reg": "SAR", + "pref": "0784", + "cap": "5", + "abitanti": "21224", + "country": "IT" + }, + { + "comune": "Portoscuso", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "6", + "abitanti": "4859", + "country": "IT" + }, + { + "comune": "Posada", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "37", + "abitanti": "2974", + "country": "IT" + }, + { + "comune": "Pozzomaggiore", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "438", + "abitanti": "2399", + "country": "IT" + }, + { + "comune": "Pula", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "15", + "abitanti": "7054", + "country": "IT" + }, + { + "comune": "Putifigari", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "267", + "abitanti": "694", + "country": "IT" + }, + { + "comune": "Quartu Sant'Elena", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "6", + "abitanti": "68430", + "country": "IT" + }, + { + "comune": "Quartucciu", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "16", + "abitanti": "12756", + "country": "IT" + }, + { + "comune": "Riola Sardo", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "9", + "abitanti": "2015", + "country": "IT" + }, + { + "comune": "Romana", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "267", + "abitanti": "496", + "country": "IT" + }, + { + "comune": "Ruinas", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "359", + "abitanti": "619", + "country": "IT" + }, + { + "comune": "Sadali", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "765", + "abitanti": "867", + "country": "IT" + }, + { + "comune": "Sagama", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "333", + "abitanti": "191", + "country": "IT" + }, + { + "comune": "Samassi", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "56", + "abitanti": "4817", + "country": "IT" + }, + { + "comune": "Samatzai", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "174", + "abitanti": "1555", + "country": "IT" + }, + { + "comune": "Samugheo", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "370", + "abitanti": "2760", + "country": "IT" + }, + { + "comune": "San Basilio", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "415", + "abitanti": "1132", + "country": "IT" + }, + { + "comune": "San Gavino Monreale", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "54", + "abitanti": "8119", + "country": "IT" + }, + { + "comune": "San Giovanni Suergiu", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "16", + "abitanti": "5673", + "country": "IT" + }, + { + "comune": "San Nicolò d'Arcidano", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "13", + "abitanti": "2516", + "country": "IT" + }, + { + "comune": "San Nicolò Gerrei", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "365", + "abitanti": "717", + "country": "IT" + }, + { + "comune": "San Sperate", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "41", + "abitanti": "8352", + "country": "IT" + }, + { + "comune": "San Teodoro", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "15", + "abitanti": "4941", + "country": "IT" + }, + { + "comune": "San Vero Milis", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "10", + "abitanti": "2416", + "country": "IT" + }, + { + "comune": "San Vito", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "10", + "abitanti": "3436", + "country": "IT" + }, + { + "comune": "Sanluri", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "135", + "abitanti": "8112", + "country": "IT" + }, + { + "comune": "Santa Giusta", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "10", + "abitanti": "4649", + "country": "IT" + }, + { + "comune": "Santa Maria Coghinas", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "21", + "abitanti": "1292", + "country": "IT" + }, + { + "comune": "Santa Teresa Gallura", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "40", + "abitanti": "4969", + "country": "IT" + }, + { + "comune": "Santadi", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "135", + "abitanti": "3156", + "country": "IT" + }, + { + "comune": "Sant'Andrea Frius", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "280", + "abitanti": "1709", + "country": "IT" + }, + { + "comune": "Sant'Anna Arresi", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "77", + "abitanti": "2628", + "country": "IT" + }, + { + "comune": "Sant'Antioco", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "7", + "abitanti": "10670", + "country": "IT" + }, + { + "comune": "Sant'Antonio di Gallura", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "354", + "abitanti": "1438", + "country": "IT" + }, + { + "comune": "Santu Lussurgiu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "503", + "abitanti": "2215", + "country": "IT" + }, + { + "comune": "Sardara", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "163", + "abitanti": "3824", + "country": "IT" + }, + { + "comune": "Sarroch", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "47", + "abitanti": "5033", + "country": "IT" + }, + { + "comune": "Sarule", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "630", + "abitanti": "1568", + "country": "IT" + }, + { + "comune": "Sassari", + "prov": "SS", + "reg": "SAR", + "pref": "079", + "cap": "100", + "abitanti": "121657", + "country": "IT" + }, + { + "comune": "Scano di Montiferro", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "385", + "abitanti": "1407", + "country": "IT" + }, + { + "comune": "Sedilo", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "283", + "abitanti": "1975", + "country": "IT" + }, + { + "comune": "Sedini", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "350", + "abitanti": "1245", + "country": "IT" + }, + { + "comune": "Segariu", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "129", + "abitanti": "1099", + "country": "IT" + }, + { + "comune": "Selargius", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "11", + "abitanti": "28501", + "country": "IT" + }, + { + "comune": "Selegas", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "234", + "abitanti": "1292", + "country": "IT" + }, + { + "comune": "Semestene", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "405", + "abitanti": "142", + "country": "IT" + }, + { + "comune": "Seneghe", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "305", + "abitanti": "1676", + "country": "IT" + }, + { + "comune": "Senis", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "256", + "abitanti": "421", + "country": "IT" + }, + { + "comune": "Sennariolo", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "274", + "abitanti": "154", + "country": "IT" + }, + { + "comune": "Sennori", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "277", + "abitanti": "6910", + "country": "IT" + }, + { + "comune": "Senorbì", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "199", + "abitanti": "4729", + "country": "IT" + }, + { + "comune": "Serdiana", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "171", + "abitanti": "2647", + "country": "IT" + }, + { + "comune": "Serramanna", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "30", + "abitanti": "8614", + "country": "IT" + }, + { + "comune": "Serrenti", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "114", + "abitanti": "4557", + "country": "IT" + }, + { + "comune": "Serri", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "640", + "abitanti": "630", + "country": "IT" + }, + { + "comune": "Sestu", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "44", + "abitanti": "20676", + "country": "IT" + }, + { + "comune": "Settimo San Pietro", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "70", + "abitanti": "6839", + "country": "IT" + }, + { + "comune": "Setzu", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "216", + "abitanti": "133", + "country": "IT" + }, + { + "comune": "Seui", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "820", + "abitanti": "1178", + "country": "IT" + }, + { + "comune": "Seulo", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "797", + "abitanti": "795", + "country": "IT" + }, + { + "comune": "Siamaggiore", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "8", + "abitanti": "884", + "country": "IT" + }, + { + "comune": "Siamanna", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "100", + "abitanti": "772", + "country": "IT" + }, + { + "comune": "Siapiccia", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "64", + "abitanti": "344", + "country": "IT" + }, + { + "comune": "Siddi", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "184", + "abitanti": "596", + "country": "IT" + }, + { + "comune": "Silanus", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "432", + "abitanti": "2005", + "country": "IT" + }, + { + "comune": "Siligo", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "400", + "abitanti": "811", + "country": "IT" + }, + { + "comune": "Siliqua", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "66", + "abitanti": "3586", + "country": "IT" + }, + { + "comune": "Silius", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "585", + "abitanti": "1060", + "country": "IT" + }, + { + "comune": "Simala", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "155", + "abitanti": "283", + "country": "IT" + }, + { + "comune": "Simaxis", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "7", + "abitanti": "2124", + "country": "IT" + }, + { + "comune": "Sindia", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "509", + "abitanti": "1605", + "country": "IT" + }, + { + "comune": "Sini", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "255", + "abitanti": "481", + "country": "IT" + }, + { + "comune": "Siniscola", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "39", + "abitanti": "11159", + "country": "IT" + }, + { + "comune": "Sinnai", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "134", + "abitanti": "17172", + "country": "IT" + }, + { + "comune": "Soddì", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "250", + "abitanti": "120", + "country": "IT" + }, + { + "comune": "Solarussa", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "12", + "abitanti": "2288", + "country": "IT" + }, + { + "comune": "Soleminis", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "200", + "abitanti": "1840", + "country": "IT" + }, + { + "comune": "Sorgono", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "700", + "abitanti": "1515", + "country": "IT" + }, + { + "comune": "Sorradile", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "337", + "abitanti": "353", + "country": "IT" + }, + { + "comune": "Sorso", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "136", + "abitanti": "14383", + "country": "IT" + }, + { + "comune": "Stintino", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "9", + "abitanti": "1535", + "country": "IT" + }, + { + "comune": "Suelli", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "256", + "abitanti": "1071", + "country": "IT" + }, + { + "comune": "Suni", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "340", + "abitanti": "975", + "country": "IT" + }, + { + "comune": "Tadasuni", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "180", + "abitanti": "141", + "country": "IT" + }, + { + "comune": "Talana", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "682", + "abitanti": "963", + "country": "IT" + }, + { + "comune": "Telti", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "326", + "abitanti": "2222", + "country": "IT" + }, + { + "comune": "Tempio Pausania", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "566", + "abitanti": "13278", + "country": "IT" + }, + { + "comune": "Tergu", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "284", + "abitanti": "605", + "country": "IT" + }, + { + "comune": "Terralba", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "12", + "abitanti": "9689", + "country": "IT" + }, + { + "comune": "Tertenia", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "129", + "abitanti": "3801", + "country": "IT" + }, + { + "comune": "Teti", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "714", + "abitanti": "612", + "country": "IT" + }, + { + "comune": "Teulada", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "50", + "abitanti": "3293", + "country": "IT" + }, + { + "comune": "Thiesi", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "461", + "abitanti": "2796", + "country": "IT" + }, + { + "comune": "Tiana", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "564", + "abitanti": "444", + "country": "IT" + }, + { + "comune": "Tinnura", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "328", + "abitanti": "238", + "country": "IT" + }, + { + "comune": "Tissi", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "250", + "abitanti": "2359", + "country": "IT" + }, + { + "comune": "Tonara", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "900", + "abitanti": "1806", + "country": "IT" + }, + { + "comune": "Torpè", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "24", + "abitanti": "2698", + "country": "IT" + }, + { + "comune": "Torralba", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "435", + "abitanti": "902", + "country": "IT" + }, + { + "comune": "Tortolì", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "13", + "abitanti": "10986", + "country": "IT" + }, + { + "comune": "Tramatza", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "19", + "abitanti": "936", + "country": "IT" + }, + { + "comune": "Tratalias", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "17", + "abitanti": "993", + "country": "IT" + }, + { + "comune": "Tresnuraghes", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "257", + "abitanti": "1111", + "country": "IT" + }, + { + "comune": "Triei", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "140", + "abitanti": "1047", + "country": "IT" + }, + { + "comune": "Trinità d'Agultu e Vignola", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "365", + "abitanti": "2210", + "country": "IT" + }, + { + "comune": "Tuili", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "208", + "abitanti": "946", + "country": "IT" + }, + { + "comune": "Tula", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "275", + "abitanti": "1462", + "country": "IT" + }, + { + "comune": "Turri", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "164", + "abitanti": "391", + "country": "IT" + }, + { + "comune": "Ula Tirso", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "348", + "abitanti": "469", + "country": "IT" + }, + { + "comune": "Ulassai", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "775", + "abitanti": "1369", + "country": "IT" + }, + { + "comune": "Uras", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "23", + "abitanti": "2682", + "country": "IT" + }, + { + "comune": "Uri", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "150", + "abitanti": "2837", + "country": "IT" + }, + { + "comune": "Urzulei", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "511", + "abitanti": "1114", + "country": "IT" + }, + { + "comune": "Usellus", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "289", + "abitanti": "727", + "country": "IT" + }, + { + "comune": "Usini", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "200", + "abitanti": "4222", + "country": "IT" + }, + { + "comune": "Ussana", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "97", + "abitanti": "4001", + "country": "IT" + }, + { + "comune": "Ussaramanna", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "157", + "abitanti": "493", + "country": "IT" + }, + { + "comune": "Ussassai", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "710", + "abitanti": "472", + "country": "IT" + }, + { + "comune": "Uta", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "6", + "abitanti": "8596", + "country": "IT" + }, + { + "comune": "Valledoria", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "16", + "abitanti": "4218", + "country": "IT" + }, + { + "comune": "Vallermosa", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "70", + "abitanti": "1799", + "country": "IT" + }, + { + "comune": "Viddalba", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "22", + "abitanti": "1611", + "country": "IT" + }, + { + "comune": "Villa San Pietro", + "prov": "CA", + "reg": "SAR", + "pref": "070", + "cap": "37", + "abitanti": "2155", + "country": "IT" + }, + { + "comune": "Villa Sant'Antonio", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "249", + "abitanti": "331", + "country": "IT" + }, + { + "comune": "Villa Verde", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "204", + "abitanti": "288", + "country": "IT" + }, + { + "comune": "Villacidro", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "267", + "abitanti": "13216", + "country": "IT" + }, + { + "comune": "Villagrande Strisaili", + "prov": "NU", + "reg": "SAR", + "pref": "0784", + "cap": "700", + "abitanti": "2947", + "country": "IT" + }, + { + "comune": "Villamar", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "108", + "abitanti": "2457", + "country": "IT" + }, + { + "comune": "Villamassargia", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "121", + "abitanti": "3411", + "country": "IT" + }, + { + "comune": "Villanova Monteleone", + "prov": "SS", + "reg": "SAR", + "pref": "0789", + "cap": "567", + "abitanti": "2133", + "country": "IT" + }, + { + "comune": "Villanova Truschedu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "56", + "abitanti": "293", + "country": "IT" + }, + { + "comune": "Villanova Tulo", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "600", + "abitanti": "1015", + "country": "IT" + }, + { + "comune": "Villanovaforru", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "324", + "abitanti": "611", + "country": "IT" + }, + { + "comune": "Villanovafranca", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "292", + "abitanti": "1194", + "country": "IT" + }, + { + "comune": "Villaperuccio", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "68", + "abitanti": "1007", + "country": "IT" + }, + { + "comune": "Villaputzu", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "11", + "abitanti": "4473", + "country": "IT" + }, + [ + { + "comune": "Villasalto", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "502", + "abitanti": "987", + "country": "IT" + }, + { + "comune": "Villasimius", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "49", + "abitanti": "3685", + "country": "IT" + }, + { + "comune": "Villasor", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "25", + "abitanti": "6554", + "country": "IT" + }, + { + "comune": "Villaspeciosa", + "prov": "SU", + "reg": "SAR", + "pref": "07032", + "cap": "7", + "abitanti": "2519", + "country": "IT" + }, + { + "comune": "Villaurbana", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "110", + "abitanti": "1491", + "country": "IT" + }, + { + "comune": "Zeddiani", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "10", + "abitanti": "1125", + "country": "IT" + }, + { + "comune": "Zerfaliu", + "prov": "OR", + "reg": "SAR", + "pref": "0783", + "cap": "15", + "abitanti": "1000", + "country": "IT" + } + ], + ] +} \ No newline at end of file diff --git a/src/server/populate/provinces.js b/src/server/populate/provinces.js index f8b2a52..0c70853 100644 --- a/src/server/populate/provinces.js +++ b/src/server/populate/provinces.js @@ -18,10 +18,9 @@ module.exports = { { _id: 16, reg: 'TAA', prov: 'BZ', descr: 'Bolzano', link_grp: 'https://t.me/c/1614195634/596?thread=594', link_telegram: '' }, { _id: 17, reg: 'LOM', prov: 'BS', descr: 'Brescia', link_grp: 'https://t.me/c/1614195634/490?thread=478', link_telegram: '' }, { _id: 18, reg: 'PUG', prov: 'BR', descr: 'Brindisi', link_grp: 'https://t.me/c/1614195634/536?thread=530', link_telegram: '' }, - { _id: 19, reg: 'SAR', prov: 'CA', descr: 'Cagliari', link_grp: 'https://t.me/c/1614195634/546?thread=541', link_telegram: '' }, + { _id: 19, reg: 'SAR', prov: 'CA', descr: 'Cagliari', link_grp: 'https://t.me/+9A0xl58CEI8zMWE0', link_telegram: '' }, { _id: 20, reg: 'SIC', prov: 'CL', descr: 'Caltanissetta', link_grp: 'https://t.me/c/1614195634/563?thread=554', link_telegram: '' }, { _id: 21, reg: 'MOL', prov: 'CB', descr: 'Campobasso', link_grp: 'https://t.me/c/1614195634/398?thread=396', link_telegram: '' }, - { _id: 22, reg: 'SAR', prov: 'CI', descr: 'Carbonia-Iglesias', link_grp: '', link_telegram: '' }, { _id: 23, reg: 'CAM', prov: 'CE', descr: 'Caserta', link_grp: 'https://t.me/c/1614195634/414?thread=410', link_telegram: '' }, { _id: 24, reg: 'SIC', prov: 'CT', descr: 'Catania', link_grp: 'https://t.me/c/1614195634/564?thread=555', link_telegram: '' }, { _id: 25, reg: 'CAL', prov: 'CZ', descr: 'Catanzaro', link_grp: 'https://t.me/c/1614195634/378?thread=377', link_telegram: '' }, @@ -62,7 +61,6 @@ module.exports = { { _id: 60, reg: 'CAM', prov: 'NA', descr: 'Napoli', link_grp: 'https://t.me/c/1614195634/407?thread=406', link_telegram: '' }, { _id: 61, reg: 'PIE', prov: 'NO', descr: 'Novara', link_grp: 'https://t.me/c/1614195634/517?thread=508', link_telegram: '' }, { _id: 62, reg: 'SAR', prov: 'NU', descr: 'Nuoro', link_grp: 'https://t.me/c/1614195634/548?thread=542', link_telegram: '' }, - { _id: 63, reg: 'SAR', prov: 'OT', descr: 'Olbia-Tempio', link_grp: '', link_telegram: '' }, { _id: 64, reg: 'SAR', prov: 'OR', descr: 'Oristano', link_grp: 'https://t.me/c/1614195634/550?thread=543', link_telegram: '' }, { _id: 65, reg: 'VEN', prov: 'PD', descr: 'Padova', link_grp: 'https://t.me/c/1614195634/610?thread=600', link_telegram: '' }, { _id: 66, reg: 'SIC', prov: 'PA', descr: 'Palermo', link_grp: 'https://t.me/c/1614195634/568?thread=558', link_telegram: '' }, @@ -86,7 +84,6 @@ module.exports = { { _id: 84, reg: 'LAZ', prov: 'RM', descr: 'Roma', link_grp: '', link_telegram: '' }, { _id: 85, reg: 'VEN', prov: 'RO', descr: 'Rovigo', link_grp: 'https://t.me/c/1614195634/611?thread=601', link_telegram: '' }, { _id: 86, reg: 'CAM', prov: 'SA', descr: 'Salerno', link_grp: 'https://t.me/c/1614195634/416?thread=412', link_telegram: '' }, - { _id: 87, reg: 'SAR', prov: 'VS', descr: 'Medio Campidano', link_grp: '', link_telegram: '' }, { _id: 88, reg: 'SAR', prov: 'SS', descr: 'Sassari', link_grp: 'https://t.me/c/1614195634/551?thread=544', link_telegram: '' }, { _id: 89, reg: 'LIG', prov: 'SV', descr: 'Savona', link_grp: 'https://t.me/c/1614195634/395?thread=391', link_telegram: '' }, { _id: 90, reg: 'TOS', prov: 'SI', descr: 'Siena', link_grp: 'https://t.me/c/1614195634/592?thread=581', link_telegram: '' }, @@ -96,7 +93,6 @@ module.exports = { { _id: 94, reg: 'ABR', prov: 'TE', descr: 'Teramo', link_grp: 'https://t.me/c/1614195634/370?thread=369', link_telegram: '' }, { _id: 95, reg: 'UMB', prov: 'TR', descr: 'Terni', link_grp: 'https://t.me/c/1614195634/404?thread=402', link_telegram: '' }, { _id: 96, reg: 'PIE', prov: 'TO', descr: 'Torino', link_grp: 'https://t.me/c/1614195634/518?thread=509', link_telegram: '' }, - { _id: 97, reg: 'SAR', prov: 'OG', descr: 'Ogliastra', link_grp: '', link_telegram: '' }, { _id: 98, reg: 'SIC', prov: 'TP', descr: 'Trapani', link_grp: 'https://t.me/c/1614195634/571?thread=561', link_telegram: '' }, { _id: 99, reg: 'TAA', prov: 'TN', descr: 'Trento', link_grp: 'https://t.me/c/1614195634/597?thread=595', link_telegram: '' }, { _id: 100, reg: 'VEN', prov: 'TV', descr: 'Treviso', link_grp: 'https://t.me/c/1614195634/612?thread=602', link_telegram: '' }, @@ -121,5 +117,6 @@ module.exports = { { _id: 119, reg: 'PUG', prov: 'VAL', descr: 'Valle D\'Itria', link_grp: 'https://t.me/progettoriso/7016?thread=7015', link_telegram: '' }, { _id: 121, reg: 'ITA', prov: 'ITA', descr: 'Italia', link_grp: '', link_telegram: '' }, { _id: 122, reg: 'LOM', prov: 'MI', descr: 'Milano Est', card: 'EST', link_grp: '', link_telegram: '' }, + { _id: 123, reg: 'SAR', prov: 'SU', descr: 'Sud Sardegna', link_grp: '', link_telegram: '' }, ], }; diff --git a/src/server/router/index_router.js b/src/server/router/index_router.js index 29aeefc..d17bebb 100755 --- a/src/server/router/index_router.js +++ b/src/server/router/index_router.js @@ -354,13 +354,14 @@ router.post('/settable', authenticate, async (req, res) => { try { if (User.isAdmin(req.user.perm) || User.isManager(req.user.perm) || - User.isEditor(req.user.perm) || User.isFacilitatore(req.user.perm)) { + User.isEditor(req.user.perm) || User.isCommerciale(req.user.perm) || User.isFacilitatore(req.user.perm)) { consentito = true; } if ((!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isEditor(req.user.perm) + && !User.isCommerciale(req.user.perm) && !User.isGrafico(req.user.perm) && !User.isFacilitatore(req.user.perm)) && @@ -1065,7 +1066,7 @@ async function importPage(req, idapp, jsonString) { const table = globalTables.getTableByTableName(tableName); if (tableName === 'mypages') { - if (User.isEditor(req.user.perm)) { + if (User.isEditor(req.user.perm) || User.isCommerciale(req.user.perm)) { for (const page of myexp.mypages) { const { ImportedRecords, newId } = await upsertRecord(table, page, idapp); if (!newIdPage && newId) { @@ -1095,7 +1096,7 @@ async function importPage(req, idapp, jsonString) { const table = globalTables.getTableByTableName(tableName); if (tableName === 'myelems') { - if (User.isEditor(req.user.perm)) { + if (User.isEditor(req.user.perm) || User.isCommerciale(req.user.perm)) { for (const elem of myexp.myelems) { const { ImportedRecords, newId } = await upsertRecord(table, elem, idapp, newIdPage); ImportedRecordstemp += ImportedRecords ? 1 : 0; @@ -1258,6 +1259,7 @@ router.patch('/chval', authenticate, async (req, res) => { (!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isEditor(req.user.perm) + && !User.isCommerciale(req.user.perm) && !User.isFacilitatore(req.user.perm)) && (await !tools.ModificheConsentite(req, mydata.table, fieldsvalue, id))) && !((mydata.table === 'accounts') @@ -1620,7 +1622,7 @@ router.get('/copyfromapptoapp/:idapporig/:idappdest', async (req, res) => { if (!idapporig || !idappdest || (idcode !== 'ASD3429Kjgà#@cvX')) res.status(400).send(); - const mytablesstr = ['settings', 'users', 'templemail']; + const mytablesstr = ['settings', 'users', 'templemail', 'destnewsletter']; try { let numrectot = 0; @@ -1908,7 +1910,7 @@ async function measurePromises(promises) { // Ordina le chiamate per tempo decrescente e prende le 10 più lente const slowCalls = Object.entries(timings) .sort(([, timeA], [, timeB]) => timeB - timeA) - .slice(0, 5) + .slice(0, 10) .map(([key, time]) => ({ key, time })); return { data, totalTime, slowCalls }; @@ -2007,10 +2009,12 @@ async function load(req, res, version = '0') { providers: version >= 91 ? Provider.findAllIdApp(idapp) : Promise.resolve([]), scontisticas: version >= 91 ? Scontistica.findAllIdApp(idapp) : Promise.resolve([]), gasordines: version >= 91 ? Gasordine.findAllIdApp(idapp) : Promise.resolve([]), - products: version >= 91 + /*products: version >= 91 ? Product.findAllIdApp(idapp, undefined, undefined, req.user ? User.isManager(req.user.perm) : false) - : Promise.resolve([]), - productInfos: version >= 91 ? ProductInfo.findAllIdApp(idapp) : Promise.resolve([]), + : Promise.resolve([]),*/ + products: Promise.resolve([]), + // productInfos: version >= 91 ? ProductInfo.findAllIdApp(idapp) : Promise.resolve([]), + productInfos: Promise.resolve([]), catprods: version >= 91 ? Product.getArrCatProds(idapp, shared_consts.PROD.BOTTEGA) : Promise.resolve([]), subcatprods: version >= 91 ? SubCatProd.findAllIdApp(idapp) : Promise.resolve([]), catprods_gas: version >= 91 ? Product.getArrCatProds(idapp, shared_consts.PROD.GAS) : Promise.resolve([]), @@ -2040,8 +2044,8 @@ async function load(req, res, version = '0') { const { data, totalTime, slowCalls } = await measurePromises(promises); // console.log('Risultati delle promise:', data); - // console.log('Tempo totale di esecuzione:', totalTime, 'secondi'); - // console.log('Le 5 chiamate più lente:', slowCalls); + console.log('Tempo di esecuzione:', totalTime, 'secondi'); + //console.log('Le 10 chiamate più lente:', slowCalls); // Aggiornamento delle informazioni dell'utente, se presente let myuser = req.user; diff --git a/src/server/router/newsletter_router.js b/src/server/router/newsletter_router.js index f2c5a42..cf40070 100755 --- a/src/server/router/newsletter_router.js +++ b/src/server/router/newsletter_router.js @@ -10,6 +10,7 @@ const { User } = require('../models/user'); const { MailingList } = require('../models/mailinglist'); const { Newstosent } = require('../models/newstosent'); const { TemplEmail } = require('../models/templemail'); +const { DestNewsletter } = require('../models/destnewsletter'); const { OpzEmail } = require('../models/opzemail'); const { Settings } = require('../models/settings'); @@ -254,12 +255,21 @@ router.post('/load', authenticate, async (req, res) => { idapp = req.body.idapp; locale = req.body.locale; - const ris = { - newsstate: await getDataNewsletter(locale, idapp), - serv_settings: await Settings.findAllIdApp(idapp, true, false), - templemail: await TemplEmail.findAllIdApp(idapp, true), - opzemail: await OpzEmail.findAllIdApp(idapp) - }; + let ris; + + try { + ris = { + newsstate: await getDataNewsletter(locale, idapp), + serv_settings: await Settings.findAllIdApp(idapp, true, false), + templemail: await TemplEmail.findAllIdApp(idapp, true), + destnewsletter: await DestNewsletter.findAllIdApp(idapp, true), + opzemail: await OpzEmail.findAllIdApp(idapp) + }; + + } catch (e) { + console.error('Errore load newsletter: ', e); + ris = { code: server_constants.RIS_CODE_ERR, msg: e.message }; + } return res.send(ris); }); @@ -275,12 +285,19 @@ router.post('/setactivate', authenticate, async (req, res) => { activate, }; - return await Newstosent.findOneAndUpdate({ _id: id }, { $set: rec }, { new: false }).then((item) => { - const ris = getDataNewsletter(locale, idapp); + try { + const item = await Newstosent.findOneAndUpdate({ _id: id }, { $set: rec }, { new: false }); - return res.send(ris); + if (item) { + const ris = await getDataNewsletter(locale, idapp); + return res.send(ris); + } else { + return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: 'Record not found' }); + } - }); + } catch (e) { + return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e.message }); + } }); @@ -296,7 +313,7 @@ router.post('/unsubscribe_user', async (req, res) => { if (myuser) { const hashcalc = tools.getHash(myuser.email + myuser.username); - if (hashcalc === hashemail) { + if (hashcalc === hashemail) { await User.setNewsletter(idapp, myuser.username, false); return res.send({ @@ -329,28 +346,37 @@ router.post('/unsubscribe', (req, res) => { console.log('Remove_from_MailingList -> ', ris); if (!!ris.myperson && mailchimpactive) { - const subscriber_md5_email = tools.getmd5(ris.myperson.email); - request - .put('https://' + newsletter[idapp].mailchimpInstance + '.api.mailchimp.com/3.0/lists/' + newsletter[idapp].listUniqueId + '/members/' + subscriber_md5_email) - .set('Content-Type', 'application/json;charset=utf-8') - .set('Authorization', 'Basic ' + new Buffer('any:' + newsletter[idapp].mailchimpApiKey).toString('base64')) - .send({ - 'email_address': ris.myperson.email, - 'status': server_constants.RIS_UNSUBSCRIBED_STR - }) - .end(function (err, response) { - console.log("STAT", response.status); + try { + const subscriber_md5_email = tools.getmd5(ris.myperson.email); + request + .put('https://' + newsletter[idapp].mailchimpInstance + '.api.mailchimp.com/3.0/lists/' + newsletter[idapp].listUniqueId + '/members/' + subscriber_md5_email) + .set('Content-Type', 'application/json;charset=utf-8') + .set('Authorization', 'Basic ' + new Buffer('any:' + newsletter[idapp].mailchimpApiKey).toString('base64')) + .send({ + 'email_address': ris.myperson.email, + 'status': server_constants.RIS_UNSUBSCRIBED_STR + }) + .end(function (err, response) { + console.log("STAT", response.status); - if (response.status < 300 || (response.status === 400 && response.body.title === "Member Exists")) { - res.send({ - code: server_constants.RIS_UNSUBSCRIBED_OK - }); - } else { - res.send({ - code: server_constants.RIS_SUBSCRIBED_ERR - }); - } + if (response.status < 300 || (response.status === 400 && response.body.title === "Member Exists")) { + res.send({ + code: server_constants.RIS_UNSUBSCRIBED_OK + }); + } else { + res.send({ + code: server_constants.RIS_SUBSCRIBED_ERR + }); + } + }); + + } catch (e) { + console.error('Errore unsubscribe -> ', e); + res.send({ + code: server_constants.RIS_SUBSCRIBED_ERR, + msg: e.message }); + } } else { res.send({ code: ris.code, msg: ris.msg }); diff --git a/src/server/router/users_router.js b/src/server/router/users_router.js index 36094a5..31b5f94 100755 --- a/src/server/router/users_router.js +++ b/src/server/router/users_router.js @@ -54,6 +54,7 @@ const { Account } = require('../models/account'); const mongoose = require('mongoose').set('debug', false); const Subscription = require('../models/subscribers'); +const Macro = require('../modules/Macro'); async function existSubScribe(userId, access, browser) { try { @@ -1074,6 +1075,17 @@ async function eseguiDbOp(idapp, mydata, locale, req, res) { await CatProd.deleteMany({ idapp }); await SubCatProd.deleteMany({ idapp }); + } else if (mydata.dbop === 'removeProductInfoWithoutDateUpdatedFromGM') { + + mystr = await ProductInfo.removeProductInfoWithoutDateUpdatedFromGM(idapp); + ris = { mystr }; + + } else if (mydata.dbop === 'StatMacro') { + + const macro = new Macro(idapp, {}); + mystr = await macro.getStat(); + ris = { mystr }; + } else if (mydata.dbop === 'updateAllBook') { // chiama updateAllBook const { updateAllBook } = require("../controllers/articleController"); @@ -1203,6 +1215,7 @@ async function eseguiDbOp(idapp, mydata, locale, req, res) { 'settings', 'users', 'templemail', + 'destnewsletter', 'contribtypes', 'bots', 'cfgservers']; @@ -1255,6 +1268,9 @@ async function eseguiDbOp(idapp, mydata, locale, req, res) { } else if (mydata.dbop === 'listCollectionsBySize') { mystr = await tools.listCollectionsBySize(); ris = { mystr }; + } else if (mydata.dbop === 'EnableNewsOn_ToAll') { + mystr = await User.setNewsletterToAll(idapp); + } else if (mydata.dbop === 'MyElemSetIdPageInsteadThePah') { mystr = await MyElem.SetIdPageInsteadThePah(idapp); ris = { mystr }; diff --git a/src/server/sendemail.js b/src/server/sendemail.js index f71d8eb..b3af33c 100755 --- a/src/server/sendemail.js +++ b/src/server/sendemail.js @@ -12,6 +12,7 @@ const i18n = require('i18n'); const { ObjectId } = require('mongodb'); const { Settings } = require('./models/settings'); const { TemplEmail } = require('./models/templemail'); +const { DestNewsletter } = require('./models/destnewsletter'); const { Discipline } = require('./models/discipline'); const previewEmail = require('preview-email'); @@ -57,7 +58,7 @@ module.exports = { if (to === '') return false; - + // console.log('mylocalsconf', mylocalsconf); // console.log("check EMAIL :" + checkifSendEmail()); @@ -166,7 +167,7 @@ module.exports = { else transport_preview.sendMail(mailOptions).then(console.log).catch(console.error); } - + } catch (e) { console.error('Errore Sendmail', e); } @@ -679,12 +680,20 @@ module.exports = { replacefields: function (mylocalsconf) { try { - mylocalsconf.dataemail.disclaimer_out = !!mylocalsconf.dataemail.disclaimer ? this.fieldsloop(mylocalsconf, - mylocalsconf.dataemail.disclaimer) : ''; - mylocalsconf.dataemail.disc_bottom_out = !!mylocalsconf.dataemail.disc_bottom ? this.fieldsloop(mylocalsconf, - mylocalsconf.dataemail.disc_bottom) : ''; + mylocalsconf.dataemail.disclaimer_out = !!mylocalsconf.dataemail.disclaimer ? this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.disclaimer) : ''; + mylocalsconf.dataemail.disc_bottom_out = !!mylocalsconf.dataemail.disc_bottom ? this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.disc_bottom) : ''; + mylocalsconf.dataemail.firma = !!mylocalsconf.dataemail.firma ? this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.firma) : ''; if (mylocalsconf.dataemail.templ) { + if (mylocalsconf.dataemail.templ.disclaimer) + mylocalsconf.dataemail.disclaimer_out = this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.templ.disclaimer); + + if (mylocalsconf.dataemail.templ.piedipagina) + mylocalsconf.dataemail.disc_bottom_out = this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.templ.piedipagina); + + if (mylocalsconf.dataemail.templ.firma) + mylocalsconf.dataemail.firma = this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.templ.firma); + mylocalsconf.dataemail.templ.testoheadermail_out = !!mylocalsconf.dataemail.templ.testoheadermail ? this.fieldsloop(mylocalsconf, mylocalsconf.dataemail.templ.testoheadermail) : ''; @@ -701,37 +710,47 @@ module.exports = { getdataemail: async (idapp, templemail_id) => { - const pwd_from = await Settings.getValDbSettings(idapp, 'PWD_FROM'); + try { - // console.log('getdataemail'); - const mydata = { - content_after_events: await Settings.getValDbSettings(idapp, 'TEXT_AFTER_EV'), - mailchimpactive: tools.BoolToInt(await Settings.getValDbSettings(idapp, 'MAILCHIMP_ON')), - urltwitter: await Settings.getValDbSettings(idapp, 'URL_TWITTER'), - urlfb: await Settings.getValDbSettings(idapp, 'URL_FACEBOOK'), - urlyoutube: await Settings.getValDbSettings(idapp, 'URL_YOUTUBE'), - urlinstagram: await Settings.getValDbSettings(idapp, 'URL_INSTAGRAM'), - textpromo: await Settings.getValDbSettings(idapp, 'TEXT_PROMO'), - disclaimer: await Settings.getValDbSettings(idapp, 'TEXT_DISCLAIMER'), - disc_bottom: await Settings.getValDbSettings(idapp, 'TEXT_DISC_BOTTOM'), - firma: await Settings.getValDbSettings(idapp, 'TEXT_SIGN'), + const pwd_from = await Settings.getValDbSettings(idapp, 'PWD_FROM'); - arrdiscipline: await Discipline.getDisciplineforNewsletter(idapp), - disc_title: await Settings.getValDbSettings(idapp, 'DISC_TITLE'), - height_logo: await Settings.getValDbSettings(idapp, 'HEIGHT_LOGO'), - from: await Settings.getValDbSettings(idapp, 'EMAIL_FROM'), - email_reply: await Settings.getValDbSettings(idapp, 'EMAIL_REPLY', ''), - pwd_from: pwd_from, - email_service: await Settings.getValDbSettings(idapp, 'EMAIL_SERVICE_SEND'), - email_port: await Settings.getValDbSettings(idapp, 'EMAIL_PORT'), - templemail_id: templemail_id ? templemail_id : await Settings.getValDbSettings(idapp, 'TEMPLEMAIL_ID'), - }; + let mydata = { + templemail_id: templemail_id ? templemail_id : await Settings.getValDbSettings(idapp, 'TEMPLEMAIL_ID'), + destnewsletter_id: await Settings.getValDbSettings(idapp, 'TEMPLEMAIL_DEST'), + }; - // console.log(mydata.templemail_id); - mydata.templ = await TemplEmail.findOne({ _id: mydata.templemail_id }); - // console.log(mydata.templ); + mydata.templ = await TemplEmail.findOne({ _id: mydata.templemail_id }).lean(); + mydata.destnewsl = await DestNewsletter.findOne({ _id: mydata.destnewsletter_id }).lean(); + + // console.log('getdataemail'); + mydata = { + ...mydata, + content_after_events: await Settings.getValDbSettings(idapp, 'TEXT_AFTER_EV'), + mailchimpactive: tools.BoolToInt(await Settings.getValDbSettings(idapp, 'MAILCHIMP_ON')), + urltwitter: await Settings.getValDbSettings(idapp, 'URL_TWITTER'), + urlfb: await Settings.getValDbSettings(idapp, 'URL_FACEBOOK'), + urlyoutube: await Settings.getValDbSettings(idapp, 'URL_YOUTUBE'), + urlinstagram: await Settings.getValDbSettings(idapp, 'URL_INSTAGRAM'), + textpromo: await Settings.getValDbSettings(idapp, 'TEXT_PROMO'), + disc_bottom: await Settings.getValDbSettings(idapp, 'TEXT_DISC_BOTTOM'), + + arrdiscipline: await Discipline.getDisciplineforNewsletter(idapp), + disc_title: await Settings.getValDbSettings(idapp, 'DISC_TITLE'), + height_logo: await Settings.getValDbSettings(idapp, 'HEIGHT_LOGO'), + from: await Settings.getValDbSettings(idapp, 'EMAIL_FROM'), + email_reply: await Settings.getValDbSettings(idapp, 'EMAIL_REPLY', ''), + pwd_from: pwd_from, + email_service: await Settings.getValDbSettings(idapp, 'EMAIL_SERVICE_SEND'), + email_port: await Settings.getValDbSettings(idapp, 'EMAIL_PORT'), + }; + + return mydata; + + } catch (e) { + console.error('Error getdataemail: ' + e); + return null; + } - return mydata; }, getTransport: (mylocalsconf) => { @@ -888,9 +907,6 @@ module.exports = { await telegrambot.sendMsgTelegramToTheManagers(idapp, msginizio); - //++Todo Extract List Email to send - const userstosend = await MailingList.findAllIdAppSubscribed(idapp); - const myarrevents = await MyEvent.getLastEvents(idapp); let mylocalsconf = { @@ -908,8 +924,24 @@ module.exports = { const mynewsrec = await Newstosent.findOne({ _id: id_newstosent }); try { - mynewsrec.numemail_tot = userstosend.length; mynewsrec.templemail_str = mylocalsconf.dataemail.templ.subject; + mynewsrec.destnewsletter_str = mylocalsconf.dataemail.destnewsl?.descr; + + let userstosend = []; + + if (mylocalsconf.dataemail.destnewsl?.tipodest_id === shared_consts.DESTNEWSLETTER.UTENTI) { + userstosend = await MailingList.findAllIdAppSubscribed(idapp); + } else if (mylocalsconf.dataemail.destnewsl?.tipodest_id === shared_consts.DESTNEWSLETTER.DIARIO) { + userstosend = await MailingList.findAllIdAppDiarioSubscr(idapp); + } else if (mylocalsconf.dataemail.destnewsl?.tipodest_id === shared_consts.DESTNEWSLETTER.TEST) { + userstosend = await MailingList.findAllIdAppTestSubscr(idapp); + } else { + userstosend = await MailingList.findAllIdAppSubscribed(idapp); + } + + + mynewsrec.numemail_tot = userstosend.length; + mynewsrec.numemail_sent = await MailingList.getnumSent(idapp, id_newstosent); const smtpTransport = this.getTransport(mylocalsconf); @@ -990,6 +1022,8 @@ module.exports = { } catch (e) { + console.error('*** Errore su sendEmail_Newsletter: ! ', e.message); + const activate = await Newstosent.isActivated(id_newstosent); if (!activate) { diff --git a/src/server/tools/general.js b/src/server/tools/general.js index 9f8c2f1..fb58990 100755 --- a/src/server/tools/general.js +++ b/src/server/tools/general.js @@ -537,6 +537,11 @@ class ImageDownloader { fs.unlinkSync(filepath); } + // se in error.message c'è '404' allora esci e ritorna code: 404 + if (error.message.includes('404')) { + return { ris: false, code: 404 }; + } + if (attempt === maxRetries) { console.error(`Download fallito dopo ${maxRetries} tentativi: ${error.message}`); return { ris: false }; @@ -6109,28 +6114,30 @@ module.exports = { async downloadImgIfMissing(productInfo) { + const ProductInfo = require('../models/productInfo'); + try { if (this.sulServer()) { dirmain = ''; } else { dirmain = server_constants.DIR_PUBLIC_LOCALE; } - + const vecchiomodo = false; - + if (productInfo.image_link && vecchiomodo) { - + const relativeimg = productInfo.image_link.split('/').pop(); const img = this.getdirByIdApp(productInfo.idapp) + dirmain + server_constants.DIR_UPLOAD + '/products/' + productInfo.image_link.split('/').pop(); const savePath = path.resolve(__dirname, img); // Sostituisci con il percorso dove salvare l'immagine - + let scaricaimg = !productInfo.imagefile || !fs.existsSync(savePath); - + if (!productInfo.imagefile && fs.existsSync(savePath)) { // esiste il file, ma sul DB non è corretto const stats = fs.statSync(savePath); // Ottieni informazioni sul file - + if (stats.size > 0) { // Controlla se la dimensione del file è maggiore di zero // Esiste il file ed è non vuoto, ma sul DB non è corretto productInfo.imagefile = relativeimg; @@ -6139,23 +6146,24 @@ module.exports = { scaricaimg = true; } } - + + if (productInfo.imagefile && fs.existsSync(savePath)) { // esiste il file, ma sul DB non è corretto const stats = fs.statSync(savePath); // Ottieni informazioni sul file - + if (stats.size <= 0) { // Controlla se la dimensione del file è maggiore di zero scaricaimg = true; } } - - + + if (scaricaimg && vecchiomodo) { // Download image from the URL productInfo.image_link productInfo.imagefile = relativeimg; - + const downloader = new ImageDownloader(); - + const aggiornatoimg = await downloader.downloadImage(productInfo.image_link, savePath, { maxRetries: 1, @@ -6167,80 +6175,82 @@ module.exports = { } else { console.log('Download non riuscito.'); } - + return result; - + }); return { prodInfo: productInfo, aggiornatoimg: aggiornatoimg.ris }; } } - + let fileesistente = false; if (productInfo.imagefile) { // controlla se esiste il file const img = this.getdirByIdApp(productInfo.idapp) + dirmain + server_constants.DIR_UPLOAD + '/products/' + productInfo.imagefile.split('/').pop(); const filecompleto = path.resolve(__dirname, img); // Sostituisci con il percorso dove salvare l'immagine - + // Se non esiste lo scarico ! fileesistente = fs.existsSync(filecompleto); } - + if (!vecchiomodo && (!productInfo.image_link || !fileesistente)) { - + let scarica_da_sito = !productInfo.imagefile; - + if (!scarica_da_sito && productInfo.imagefile) { scarica_da_sito = !fileesistente; // Se non esiste lo scarico ! } - - if (scarica_da_sito) { + + if (scarica_da_sito && !productInfo.image_not_found) { // date and time productInfo.imagefile = 'img_' + new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); const img = this.getdirByIdApp(productInfo.idapp) + dirmain + server_constants.DIR_UPLOAD + '/products/' + productInfo.imagefile.split('/').pop(); let savePath = path.resolve(__dirname, img); // Sostituisci con il percorso dove salvare l'immagine - - + + let link = 'https://www.gruppomacro.com/copertine.php?id_gm=' + productInfo.sku - + const downloader = new ImageDownloader(); - - - const aggiornatoimg = await downloader.downloadImage(link, savePath, - { - maxRetries: 1, - initialDelay: 300, - timeout: 15000, - nomefileoriginale: true, - }).then(result => { - if (result) { - // console.log('Download completato con successo!'); - } else { - console.log('Download non riuscito.'); - } - - return result; - - }); - if (aggiornatoimg.filepath) { + + + let aggiornatoimg; + try { + aggiornatoimg = await downloader.downloadImage(link, savePath, + { + maxRetries: 1, + initialDelay: 300, + timeout: 15000, + nomefileoriginale: true, + }); + } catch (e) { + aggiornatoimg = { ris: false }; + } + if (aggiornatoimg?.code === 404) { + // non trovato quindi la prossima volta non richiederlo + await ProductInfo.setImgNotFound(productInfo._id); + } + + + if (aggiornatoimg?.filepath) { const filenamebase = path.basename(aggiornatoimg.filepath); // const img = '/upload/products/' + filenamebase; productInfo.imagefile = filenamebase; } - + return { prodInfo: productInfo, aggiornatoimg: aggiornatoimg.ris }; } else { return { prodInfo: null, aggiornatoimg: false }; } } - + } catch (e) { console.error('downloadImgIfMissing', e.message); } - + return { prodInfo: null, aggiornatoimg: false }; - + }, removeAccents(mystr) { @@ -6257,7 +6267,7 @@ module.exports = { return Array.from(mystr).map(char => accentsMap.get(char) || char).join(''); }, - + diff --git a/src/server/tools/globalTables.js b/src/server/tools/globalTables.js index 9686806..56814e8 100755 --- a/src/server/tools/globalTables.js +++ b/src/server/tools/globalTables.js @@ -53,6 +53,7 @@ const { CfgServer } = require('../models/cfgserver'); const { CalZoom } = require('../models/calzoom'); const { Gallery } = require('../models/gallery'); const { TemplEmail } = require('../models/templemail'); +const { DestNewsletter } = require('../models/destnewsletter'); const { OpzEmail } = require('../models/opzemail'); const { MailingList } = require('../models/mailinglist'); const { Settings } = require('../models/settings'); @@ -207,6 +208,8 @@ module.exports = { mytable = CalZoom; else if (tablename === 'templemail') mytable = TemplEmail; + else if (tablename === 'destnewsletter') + mytable = DestNewsletter; else if (tablename === 'opzemail') mytable = OpzEmail; else if (tablename === 'settings') diff --git a/src/server/tools/shared_nodejs.js b/src/server/tools/shared_nodejs.js index e80f9aa..61f0a4a 100755 --- a/src/server/tools/shared_nodejs.js +++ b/src/server/tools/shared_nodejs.js @@ -359,6 +359,7 @@ module.exports = { Zoomeri: 32, Department: 64, Grafico: 128, + Commerciale: 256, }, MessageOptions: { @@ -730,6 +731,8 @@ module.exports = { 'profile', 'calcstat', 'news_on', + 'diario_on', + 'test', 'aportador_solidario', 'made_gift', 'ind_order', @@ -1195,6 +1198,26 @@ module.exports = { EXCEED_QTAMAX: 20, }, + DESTNEWSLETTER: { + LISTA_NEWSLETTER: 0, + UTENTI: 1, + DIARIO: 2, + TEST: 10, + }, + + STATUS_JOB: { + NONE: 0, + START: 1, + FINISH: 10, + PAUSE: 2, + }, + + TERMINATED_WHY: { + END_NORMALLY: 1, + END_WITHERROR: -50, + TOOLONGTIME: -10, + } + // Download, DVD, Epub, Mobi, Nuovo, PDF, Streaming, Usato }; diff --git a/src/server/version.txt b/src/server/version.txt index 2aa6561..f480e79 100644 --- a/src/server/version.txt +++ b/src/server/version.txt @@ -1 +1 @@ -1.2.35 \ No newline at end of file +1.2.37 \ No newline at end of file