'} window.exportView=function(){openModal('Export dati',`
L’export usa i record selezionati. Se non selezioni nulla esporta i record filtrati; se non ci sono filtri esporta la vista corrente.
`,``)}; window.doExportView=function(){const fmt=document.getElementById('exp_fmt').value; const rows=selectedOrFilteredData(); const base='datasphere_'+currentKey()+'_'+new Date().toISOString().slice(0,10); if(fmt==='json')download(base+'.json',JSON.stringify(rows,null,2),'application/json'); else if(fmt==='xls')download(base+'.xls',toXls(rows),'application/vnd.ms-excel'); else download(base+'.csv',toCsv(rows),'text/csv;charset=utf-8'); closeModal(); audit('export.'+fmt,currentKey()+' '+rows.length+' record');}; function templateForCurrent(){const {data}=currentCollection(); if(data&&data[0])return [Object.fromEntries(Object.keys(data[0]).map(k=>[k,'']))]; return [{id:'',company:state.company,name:'',active:true}]} window.importView=function(){openModal('Import dati',`
Import massivo per la vista corrente. Scarica prima il template, compila CSV/JSON/XLS compatibile e poi importa. In produzione: validazione, anteprima, simulazione, conferma e rollback.
`,``)}; window.downloadImportTemplate=function(fmt){const rows=templateForCurrent(); const base='template_'+currentKey(); if(fmt==='json')download(base+'.json',JSON.stringify(rows,null,2),'application/json'); else if(fmt==='xls')download(base+'.xls',toXls(rows),'application/vnd.ms-excel'); else download(base+'.csv',toCsv(rows),'text/csv;charset=utf-8')}; function parseCsv(text){const lines=text.trim().split(/\r?\n/).filter(Boolean); if(!lines.length)return []; const sep=lines[0].includes(';')?';':','; const clean=s=>String(s||'').replace(/^"|"$/g,'').replaceAll('""','"'); const keys=lines[0].split(sep).map(clean); return lines.slice(1).map(l=>Object.fromEntries(l.split(sep).map((v,i)=>[keys[i],clean(v)])))} window.doImportView=async function(){const {coll}=currentCollection(); if(!coll)return dsNotify('Questa vista non è collegata a una tabella importabile.'); let text=document.getElementById('imp_text').value.trim(); const file=document.getElementById('imp_file').files[0]; if(file)text=await file.text(); if(!text)return dsNotify('Nessun dato da importare.'); let rows; try{rows=text.trim().startsWith('[')||text.trim().startsWith('{')?JSON.parse(text):parseCsv(text)}catch(e){return dsNotify('Formato import non valido: '+e.message)} if(!Array.isArray(rows))rows=[rows]; rows.forEach(r=>{if(!r.id)r.id=(coll.toUpperCase().slice(0,3)+'-'+uid()); if(!r.company)r.company=state.company; state[coll].push(r)}); audit('import',coll+' '+rows.length+' record'); save(); closeModal(); renderApp();}; window.markNotif=function(id){const n=(state.notifications||[]).find(x=>x.id===id); if(n){n.read=true;n.readAt=new Date().toLocaleString('it-IT');audit('notification.read',id)} save(); renderApp();}; window.openNotifications=function(){const unread=(state.notifications||[]).filter(n=>!n.read); openModal('Notifiche e azioni',`${unread.map(n=>`
${esc(n.text)}
${esc(n.toRole||'')}
${String(n.text).includes('RDA')?``:''}
`).join('')||'
Nessuna notifica non letta
'}`,``)}; window.manualJournalPage=function(){return pageHead('Prime note manuali','Testate e righe contabili manuali con quadratura.',``)+`

Testate

${table(['ID','Data','Causale','Descrizione','Stato','Totale','Azioni'],(state.journalHeaders||[]).map(h=>[h.id,h.date,h.cause,h.description,h.status,money(h.total||0),``]),'manualJournal')}

Righe

${table(['Testata','Riga','Conto','Dare','Avere','Dimensione'],(state.journalLines||[]).map(l=>[l.header||'',l.lineNo||'',l.account||l.debit||l.credit,money(l.debitAmount||0),money(l.creditAmount||0),l.dimension||'']),'manualJournalLines')}
`}; window.openManualJournal=window.openManualJournalV47=function(id){let h=id?(state.journalHeaders||[]).find(x=>x.id===id):{id:'PN-'+new Date().getFullYear()+'-'+uid(),date:today(),cause:'MAN',description:'Prima nota manuale',status:'bozza',rows:[{account:'110100',debitAmount:1000,creditAmount:0,dimension:''},{account:'700100',debitAmount:0,creditAmount:1000,dimension:'CDR-NORD'}]}; if(!h.rows)h.rows=(state.journalLines||[]).filter(l=>l.header===h.id).map(l=>({account:l.account||l.debit||l.credit,debitAmount:l.debitAmount||0,creditAmount:l.creditAmount||0,dimension:l.dimension||''})); const rowHtml=()=> (h.rows||[]).map((r,i)=>`${i+1}`).join(''); openModal(id?'Prima nota '+id:'Nuova prima nota',`
${rowHtml()}
#ContoDareAvereDimensione
Il salvataggio verifica la quadratura Dare/Avere.
`,``); window.__pnDraft=h;}; window.addPnDraftRow=function(){window.__pnDraft.rows.push({account:'110100',debitAmount:0,creditAmount:0,dimension:''}); closeModal(); openManualJournalV47(window.__pnDraft.id)}; window.saveManualJournalV47=function(id){const h=window.__pnDraft; h.date=document.getElementById('pn_date').value; h.cause=document.getElementById('pn_cause').value; h.description=document.getElementById('pn_desc').value; h.rows=(h.rows||[]).map((r,i)=>({account:document.getElementById('pn_acc_'+i).value,debitAmount:+document.getElementById('pn_deb_'+i).value||0,creditAmount:+document.getElementById('pn_cred_'+i).value||0,dimension:document.getElementById('pn_dim_'+i).value})); const dare=h.rows.reduce((a,b)=>a+b.debitAmount,0), avere=h.rows.reduce((a,b)=>a+b.creditAmount,0); if(Math.abs(dare-avere)>0.001)return dsNotify('Prima nota non quadrata: Dare '+dare+' / Avere '+avere); h.total=dare; h.status='registrata'; state.journalHeaders=state.journalHeaders||[]; const idx=state.journalHeaders.findIndex(x=>x.id===id); if(idx>=0)state.journalHeaders[idx]=h; else state.journalHeaders.push(h); state.journalLines=(state.journalLines||[]).filter(l=>l.header!==id); h.rows.forEach((r,i)=>state.journalLines.push({id:'PNL-'+uid(),header:id,lineNo:i+1,date:h.date,type:'manuale',account:r.account,debitAmount:r.debitAmount,creditAmount:r.creditAmount,amount:Math.max(r.debitAmount,r.creditAmount),dimension:r.dimension,guid:'GUID-'+uid()})); audit('journal.manual.save',id); save(); closeModal(); renderApp();}; const oldFinancePageV47=financePage; financePage=function(){const sub=state.sub||'journal'; if(sub==='integrationEntries')return pageHead('Integrazioni contabili','Eventi ERP → movimenti gestionali/statutari con GUID per analisi e audit.',``)+`
${table(['GUID','Company','Origine','Evento','Libro','Dare','Avere','Importo','Stato','Data','Dim.','PN'],state.integrationEntries.map(e=>[e.guid,e.company,e.sourceType+' '+e.sourceId,e.event,e.book,e.debit,e.credit,money(e.amount),e.status,e.date,e.dimension,e.journalRef||'']),'integrationEntries')}
`; if(sub==='manualJournal')return manualJournalPage(); return oldFinancePageV47();}; window.rebuildIntegrationEntries=function(){audit('integration.rebuild','simulazione da contratti/ordini/consegne/fatture'); dsNotify('Ricostruzione simulata: in backend produzione ricalcola GUID e quadrature da eventi ERP.');}; window.inventoryPage=function(){const sub=state.sub||'stockOverview'; const rows={stockOverview:[['KPI','Valore','Note'],[['Articoli',state.items.length,'anagrafica'],['Giacenza totale',state.items.reduce((a,b)=>a+(+b.stock||0),0),'pezzi'],['Movimenti',(state.stockMoves||[]).length,'stock ledger']]],warehouses:[['ID','Company','Nome','Tipo','Default'],(state.warehouses||[]).map(w=>[w.id,w.company,w.name,w.type,w.default?'Sì':'No'])],locations:[['ID','Magazzino','Nome','Zona'],(state.locations||[]).map(l=>[l.id,l.warehouse,l.name,l.zone])],stockByItem:[['Articolo','Gruppo','Disponibile','Impegnato','Valore'],(state.items||[]).map(it=>[it.code||it.id,it.group,it.stock||0,(state.stockMoves||[]).filter(m=>m.item===it.id&&m.status==='prenotato').reduce((a,b)=>a+Math.abs(+b.qty||0),0),money((it.stock||0)*(it.cost||0))])],stockMoves:[['ID','Data','Articolo','Q.tà','Tipo','Origine','Stato'],(state.stockMoves||[]).map(m=>[m.id,m.date,m.item,m.qty,m.type,m.source,m.status])],lotsSerials:[['Lotto/Seriale','Articolo','Magazzino','Ubicazione','Q.tà','Stato'],(state.stockLots||[]).map(l=>[l.id,l.item,l.warehouse,l.location,l.qty,l.status])]}; const r=rows[sub]||rows.stockOverview; return pageHead(currentTitle(),'Magazzino collegato a ordini, articoli, lotti, ubicazioni e contabilità di stock.',``)+`
${table(r[0],r[1],'inventory_'+sub)}
`}; const oldDashboardV47=dashboard; dashboard=function(){const html=oldDashboardV47(); const unread=(state.notifications||[]).filter(n=>!n.read); return html.replace(/

Notifiche e approvazioni<\/h3>[\s\S]*?<\/div><\/div>$/,`

Notifiche e approvazioni

${unread.map(n=>`
${esc(n.text)}
`).join('')||'
Nessuna notifica
'}

`)}; window.download=function(name,content,type='application/octet-stream'){const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([content],{type}));a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000)}; window.__ds_v47_smoke=function(){return {ok:typeof table==='function'&&typeof exportView==='function'&&typeof importView==='function'&&typeof manualJournalPage==='function', integrationEntries:state.integrationEntries.length, periods:state.paymentPeriods.length}}; console.log('DataSphere v4.7 patch loaded', window.__ds_v47_smoke()); save(); }catch(e){console.error('Patch v4.7 failed',e); if(root) root.innerHTML='
Errore patch v4.7
'+esc(e.message)+'
'} })(); // ===== end DataSphere v4.7 patch ===== // ===== DataSphere v4.8 patch: company/session governance and company propagation ===== (function(){ try{ state.version='4.8'; state.__build='4.8.0'; const v48Val=id=>document.getElementById(id)?.value || ''; const v48Bool=id=>v48Val(id)==='true'; const v48Split=v=>String(v||'').split(',').map(x=>x.trim()).filter(Boolean); state.companies = Array.isArray(state.companies) ? state.companies : []; if(!state.companies.length){ state.companies.push({id:'CO-1000',code:'1000',name:'Company iniziale',legalName:'Company iniziale S.r.l.',vat:'',fiscalCode:'',country:'IT',currency:'EUR',pec:'',sdiCode:'',active:true,default:true}); } state.users=(state.users||[]).map(u=>Object.assign({active:true,maintenance:false,phone:'',mobile:'',companyPhone:''},u)); if(state.user){ const fresh=(state.users||[]).find(u=>u.email===state.user.email); if(fresh) state.user=deepClone(fresh); } const activeUserCompanies=()=>((state.user?.companies&&state.user.companies.length)?state.user.companies:(state.companies||[]).map(c=>c.code||c.id)).filter(Boolean); if(!activeUserCompanies().includes(state.company)) state.company=activeUserCompanies()[0] ||''; // Aggiunge sottovoce Sessioni utente e mantiene Company in Amministrazione. const adminMenu=(menus||[]).find(m=>m.id==='admin'); if(adminMenu){ const add=(id,label)=>{if(!adminMenu.children.some(c=>c[0]===id)) adminMenu.children.push([id,label]);}; add('companies','Company'); add('userSessions','Sessioni utente'); } function ensureDocumentCompanies(){ ['customers','suppliers','addresses','items','itemGroups','vatCodes','paymentTerms','paymentMethods','paymentPeriods','accountingCauses','accounts','dimensions','offers','orders','invoices','rdas','pos','contracts','jobs','compensations','equalizationWeights','splitSubjects','journalHeaders','journalLines','managerialLines','openItems','integrationEntries'].forEach(coll=>{ (state[coll]||[]).forEach(r=>{if(r && typeof r==='object' && !r.company) r.company=state.company;}); }); } ensureDocumentCompanies(); function companyOptionsV48(selected){ return (state.companies||[]).filter(c=>c.active!==false).map(c=>``).join(''); } function companyEditorV48(rec){ return `
Chiave obbligatoria usata da documenti, contabilità, job e audit.
Tutti i documenti devono avere una company. Il cambio company in alto filtra viste e default dei nuovi documenti.
`; } window.openCompanyV48=function(id){ const rec=id==='NEW'?{id:'CO-'+uid(),code:'',name:'',active:true,country:'IT',currency:'EUR'}:(state.companies||[]).find(c=>(c.id||c.code)===id || c.code===id); if(!rec)return dsNotify('Company non trovata'); openModal(id==='NEW'?'Nuova company':'Modifica company '+(rec.code||rec.id),companyEditorV48(rec),``); window.__companyDraft=deepClone(rec); }; window.saveCompanyV48=function(oldId){ const rec=window.__companyDraft||{}; Object.assign(rec,{id:rec.id||'CO-'+v48Val('co_code'),code:v48Val('co_code'),name:v48Val('co_name'),legalName:v48Val('co_legal'),vat:v48Val('co_vat'),fiscalCode:v48Val('co_fiscal'),country:v48Val('co_country'),currency:v48Val('co_currency'),pec:v48Val('co_pec'),sdiCode:v48Val('co_sdi'),active:v48Bool('co_active')}); if(!rec.code)return dsNotify('Codice company obbligatorio'); const ix=state.companies.findIndex(c=>(c.id||c.code)===oldId || c.code===oldId || c.id===rec.id || c.code===rec.code); if(ix>=0) state.companies[ix]=rec; else state.companies.push(rec); // Aggiunge la company agli amministratori se nuova, senza bloccare gli altri utenti. state.users.forEach(u=>{if((u.roles||[]).includes('Administrator') && !(u.companies||[]).includes(rec.code)){u.companies=u.companies||[];u.companies.push(rec.code)}}); if(state.user && (state.user.roles||[]).includes('Administrator') && !(state.user.companies||[]).includes(rec.code)) state.user.companies.push(rec.code); audit('company.save',rec.code); save(); closeModal(); renderApp(); }; window.deleteCompanyV48=function(id){ const co=(state.companies||[]).find(c=>(c.id||c.code)===id || c.code===id); if(!co)return; const code=co.code||co.id; const used=['offers','orders','invoices','rdas','pos','contracts','journalHeaders','journalLines'].some(coll=>(state[coll]||[]).some(r=>r.company===code)); if(used)return dsNotify('Company già usata da documenti: disattivala, non eliminarla.'); if(!confirm('Eliminare company '+code+'?'))return; state.companies=state.companies.filter(c=>c!==co); audit('company.delete',code); save(); renderApp(); }; function companyPageV48(){ return pageHead('Company','Gestione company e controllo multi-company su ogni documento.',``)+`
${table(['Codice','Nome','P.IVA','Paese','Valuta','Attiva','Azioni'],(state.companies||[]).map(c=>[c.code||c.id,c.name||'',c.vat||'',c.country||'',c.currency||'',c.active!==false?'Sì':'No',` `]),'companies')}

Verifica company sui documenti

${companyCoverageV48()}
`; } function companyCoverageV48(){ const rows=['offers','orders','invoices','rdas','pos','contracts','journalHeaders','journalLines','jobs'].map(coll=>{const list=state[coll]||[];const missing=list.filter(x=>!x.company).length;return [coll,list.length,missing,missing?'KO':'OK']}); return table(['Oggetto','Record','Senza company','Esito'],rows,'companyCoverage',false); } function sessionsPageV48(){ state.sessions=state.sessions||[]; const u=state.users||[]; return pageHead('Sessioni utente','Gestione sessioni e manutenzione per singolo utente: gli altri continuano a lavorare.',``)+`

Utenti

${table(['Email','Nome','Attivo','Manutenzione','Ruoli','Azioni'],u.map(x=>[x.email,x.name,x.active!==false?'Sì':'No',x.maintenance?'Sì':'No',(x.roles||[]).join(', '),` `]),'userMaintenance')}

Sessioni registrate

${table(['ID','Utente','Company','Ruolo','Ultimo accesso','Stato'],state.sessions.map(s=>[s.id,s.email,s.company,s.role,s.lastSeen,s.active?'attiva':'terminata']),'sessions')}
`; } window.createCurrentSessionV48=function(){state.sessions=state.sessions||[];state.sessions.unshift({id:'SES-'+uid(),email:state.user?.email||'',company:state.company,role:state.role,lastSeen:new Date().toLocaleString('it-IT'),active:true});audit('session.create','current');save();renderApp();}; window.toggleUserMaintenanceV48=function(email){const u=state.users.find(x=>x.email===email);if(u){u.maintenance=!u.maintenance;audit('user.maintenance',email+' '+u.maintenance);save();renderApp();}}; window.terminateUserSessionsV48=function(email){(state.sessions||[]).filter(s=>s.email===email).forEach(s=>s.active=false);audit('session.terminate',email);save();renderApp();}; window.login=function(){ const e=document.getElementById('email')?.value.trim(); const p=document.getElementById('password')?.value; const u=(state.users||[]).find(x=>x.email===e&&x.password===p); if(!u){document.getElementById('loginErr').innerHTML='
Credenziali non valide.
';return;} if(u.active===false || u.maintenance){document.getElementById('loginErr').innerHTML='
Utente non attivo o in manutenzione. Gli altri utenti possono continuare a lavorare.
';return;} state.user=deepClone(u);state.role=(u.roles||[])[0]||'User';state.company=(u.companies||[])[0]||'';state.sessions=state.sessions||[];state.sessions.unshift({id:'SES-'+uid(),email:u.email,company:state.company,role:state.role,lastSeen:new Date().toLocaleString('it-IT'),active:true});audit('login','Accesso utente');save();render(); }; // Sostituisce la pagina admin per company/sessioni senza rompere le altre sottopagine. const previousAdminPageV48=adminPage; adminPage=function(){ const sub=state.sub||'users'; if(sub==='companies')return companyPageV48(); if(sub==='userSessions')return sessionsPageV48(); return previousAdminPageV48(); }; // Company selector robusto: solo company abilitate all’utente, etichetta presa dalla tabella company. const prevRenderAppV48=renderApp; renderApp=function(){ ensureDocumentCompanies(); if(!activeUserCompanies().includes(state.company)) state.company=activeUserCompanies()[0] ||''; prevRenderAppV48(); const sel=document.getElementById('companySel'); if(sel){ sel.innerHTML=activeUserCompanies().map(code=>{const co=(state.companies||[]).find(c=>(c.code||c.id)===code);return ``}).join(''); sel.onchange=e=>{state.company=e.target.value;save();renderApp();}; } }; // Fattura manuale: company sempre visibile, obbligatoria e propagata alle righe/documento. window.createManualInvoice=function(){ const inv={id:'FV-MAN-'+uid(),company:state.company,customer:'',address:'',date:today(),status:'bozza manuale',rows:[{item:'',qty:1,price:0,vat:'',dimension:'',competenceStart:today(),competenceEnd:today()}]}; openModal('Nuova fattura manuale',`
`,``); window.__manualInvoiceDraft=inv; }; window.saveManualInvoiceV48=function(){ const inv=window.__manualInvoiceDraft||{}; inv.company=v48Val('mi_company')||state.company; inv.customer=v48Val('mi_customer'); inv.date=v48Val('mi_date'); inv.rows=[{item:v48Val('mi_item'),qty:Number(v48Val('mi_qty')||0),price:Number(v48Val('mi_price')||0),vat:v48Val('mi_vat'),dimension:v48Val('mi_dim'),competenceStart:v48Val('mi_start'),competenceEnd:v48Val('mi_end')}]; if(!inv.company)return dsNotify('Company obbligatoria'); state.invoices=state.invoices||[]; state.invoices.push(inv); audit('invoice.manual.company',inv.company+' '+inv.id); save(); closeModal(); renderApp(); }; // Check setup company su ogni nuovo documento e diagnostica rapida. window.checkCompanyPropagationV48=function(){ensureDocumentCompanies();const issues=[];['offers','orders','invoices','rdas','pos','contracts','jobs'].forEach(coll=>{const n=(state[coll]||[]).filter(x=>!x.company).length;if(n)issues.push(coll+': '+n+' senza company')});dsNotify(issues.length?issues.join('\n'):'OK: tutti i documenti principali hanno company. Company attiva: '+state.company);}; const oldSetupChecklistPageV48=window.setupChecklistPage; if(typeof setupChecklistPage==='function'){ const prev=setupChecklistPage; setupChecklistPage=function(){return prev()+`

Controllo multi-company

Nuovi documenti, incluse fatture manuali, ereditano la company attiva.
`;}; } window.__ds_v48_smoke=function(){return {ok:true,version:state.version,company:state.company,companies:(state.companies||[]).length,adminCompanies:typeof openCompanyV48==='function',sessions:typeof createCurrentSessionV48==='function',manualInvoice:typeof saveManualInvoiceV48==='function'};}; console.log('DataSphere v4.8 patch loaded', window.__ds_v48_smoke()); save(); }catch(e){console.error('Patch v4.8 failed',e); if(root) root.innerHTML='
Errore patch v4.8
'+esc(e.message)+'
';} })(); // ===== end DataSphere v4.8 patch ===== // ===== DataSphere v4.8.6 CLEAN CORE HARD RESET / no demo data ===== (function(){ try{ const CLEAN='4.8.6-clean-core'; function emptyArrays(){ const keys=[ 'companies','companyDefaults','masterSeries','numberSeries','documentTypes','documentSequences', 'customers','suppliers','addresses','items','itemGroups','vatCodes','paymentTerms','paymentMethods','paymentPeriods','accountingCauses', 'accounts','managerialAccounts','dimensions','priceLists','priceListRows','offers','orders','invoices','manualInvoices','rdas','pos','purchaseOrders','vendorBills','contracts','contractRows','contractBillingSchemas', 'journalHeaders','journalLines','managerialLines','integrationEntries','openItems','compensations','equalizationWeights','splitSubjects','paEntities','sdiIntermediaries', 'jobs','nightPlans','notifications','files','assets','assetCategories','depreciations','budgetRows','budgetVersions','actualRows','allocationRules', 'warehouses','locations','stockLots','stockMoves','stockBalances','inventoryCounts','reorderRules','bankAccounts','bankStatements','bankStatementLines','treasuryRows','cashForecastRows', 'dbConnections','webServices','governanceRules','workflowRules','viewRules','auditTableRules','importTemplates','sessionLocks','sessions' ]; keys.forEach(k=>{state[k]=[]}); state.studio={features:[],etlFlows:[],biCubes:[]}; state.audit=[]; state.selectedRows={}; state.filters={}; } // Always clean only when this build is first seen, so user-created data in this build is not wiped on each refresh. if(state.__cleanCoreBuild!==CLEAN){ emptyArrays(); state.__cleanCoreBuild=CLEAN; state.version='4.8.6'; state.company=''; state.role='Administrator'; state.page='dashboard'; state.sub=null; state.users=[{email:'',password:'',name:'Admin DS',roles:['Administrator','System Administrator'],companies:[],costDims:['*'],revDims:['*'],langEnabled:true,aiEnabled:true,aiAutonomous:false,active:true,phone:'',mobile:'',businessPhone:'',twofaEmail:false,twofaSms:false}]; state.user=null; save(); } window.resetProductionCleanCore=function(){ if(!confirm('Ripulire tutti i dati operativi/configurativi locali e mantenere solo admin tecnico?')) return; emptyArrays(); state.__cleanCoreBuild=CLEAN; state.version='4.8.6'; state.company=''; state.page='dashboard'; state.sub=null; state.users=[{email:'',password:'',name:'Admin DS',roles:['Administrator','System Administrator'],companies:[],costDims:['*'],revDims:['*'],langEnabled:true,aiEnabled:true,aiAutonomous:false,active:true,phone:'',mobile:'',businessPhone:'',twofaEmail:false,twofaSms:false}]; state.user=null; save(); render(); }; function activeCompaniesClean(){return (state.companies||[]).map(c=>c.code||c.id).filter(Boolean)} const baseLogin486=window.login; window.login=function(){ const e=document.getElementById('email')?.value.trim(); const p=document.getElementById('password')?.value; const u=(state.users||[]).find(x=>x.email===e&&x.password===p); if(!u){document.getElementById('loginErr').innerHTML='
Credenziali non valide.
';return;} state.user=deepClone(u); state.role=(u.roles||[])[0]||'Administrator'; state.company=activeCompaniesClean()[0]||''; audit('login','Accesso utente'); save(); render(); }; const baseRenderApp486=renderApp; renderApp=function(){ baseRenderApp486(); const sel=document.getElementById('companySel'); if(sel){ const comps=activeCompaniesClean(); sel.innerHTML=comps.length?comps.map(code=>{const co=(state.companies||[]).find(c=>(c.code||c.id)===code);return ``}).join(''):``; sel.value=state.company||''; sel.onchange=e=>{state.company=e.target.value;save();renderApp();}; } }; function emptyPage(title,sub,extra=''){ return pageHead(title,sub,extra)+`
Nessun dato configurato.
ERP pulito: crea o importa dati da Amministrazione di Sistema → Import/Export configurazioni, oppure carica il dataset demo separato.
`; } controllingPage=function(){ const sub=state.sub||'budgets'; if(sub==='budgets') return pageHead('Budget pluriennale','Budget per conto, dimensione, anno e periodo.',``)+`
${table(['Anno','Periodo','Conto','Dimensione','Budget','Actual'],(state.budgetRows||[]).map(b=>[b.year,b.period,b.account,b.dimension,money(b.budget),money(b.actual)]),'budgets')}
`; if(sub==='equalizationRun') return pageHead('Perequazione esecuzione','Ripartizioni su pesi e scritture gestionali.')+`
${table(['Regola','Sorgente','Target','Peso','Stato'],(state.equalizationWeights||[]).map(e=>[e.name,e.source,e.target,e.weight+'%',e.status||'bozza']),'eqrun')}
`; if(sub==='variance') return pageHead('Scostamenti','Budget pluriennale vs actual.')+`
${table(['Anno','Dimensione','Budget','Actual','Scostamento'],(state.budgetRows||[]).map(b=>[b.year,b.dimension,money(b.budget),money(b.actual),money((b.actual||0)-(b.budget||0))]),'var')}
`; return emptyPage(currentTitle(),'Sezione controllo di gestione vuota.'); }; assetsPage=function(){ const sub=state.sub||'assetRegister'; if(sub==='assetRegister') return pageHead('Registro cespiti','Cespiti collegati a RDA/PO/fatture, conti e dimensioni.',``)+`
${table(['Codice','Descrizione','Valore','Fondo','Dimensione','Stato'],(state.assets||[]).map(a=>[a.id,a.name,money(a.value),money(a.fund),a.dimension,a.status]),'assets')}
`; if(sub==='depreciation') return pageHead('Ammortamenti','Calcolo quote cespiti con blocco doppio periodo.')+`
${table(['Cespite','Periodo','Quota','Stato'],(state.depreciations||[]).map(d=>[d.asset,d.period,money(d.amount),d.status]),'dep')}
`; return emptyPage(currentTitle(),'Sezione cespiti vuota.'); }; treasuryPage=function(){ const sub=state.sub||'bankStatements'; const rows=(state.treasuryRows||[]).filter(r=>r.area===sub); return pageHead(currentTitle(),'Tesoreria, banche, incassi, pagamenti e PFN.')+`
${table(['ID','Descrizione/Data','Importo','Stato'],rows.map(r=>[r.id,r.description||r.date,money(r.amount),r.status]),'treasury')}
`; }; const baseAdminPage486=adminPage; adminPage=function(){ const sub=state.sub||'users'; if(sub==='setupChecklist') return setupChecklistPage(); if(sub==='companies' && typeof companiesCrudPage==='function') return companiesCrudPage(); if(sub==='users') return pageHead('Utenti e ruoli','Solo utente tecnico iniziale presente; crea ruoli, utenti e company da qui.',``)+`
${table(['Email','Nome','Ruoli','Company','2FA email','2FA SMS','Azioni'],(state.users||[]).map(u=>[u.email,u.name,(u.roles||[]).join(', '),(u.companies||[]).join(', '),u.twofaEmail?'Sì':'No',u.twofaSms?'Sì':'No',typeof actionBtns==='function'?actionBtns('users',u.email):'']),'users')}
`; if(sub==='governance') return pageHead('Governance CORE','Regole CORE da creare manualmente o importare.',``)+`
${table(['Componente','Azione','Controlli','Stato'],(state.governanceRules||[]).map(g=>[g.component,g.action,g.controls,g.status]),'gov')}
`; if(sub==='workflowRules') return pageHead('Workflow approvativi','Nessun workflow precaricato.',``)+`
${table(['Area','Soglia','Step','Stato'],(state.workflowRules||[]).map(w=>[w.area,w.threshold,w.steps,w.status]),'wf')}
`; if(sub==='viewsButtons') return pageHead('Viste e pulsanti','Configurazione vuota: crea regole per ruolo/company/campo.',``)+`
${table(['Ruolo','Vista','Pulsanti','Campi nascosti'],(state.viewRules||[]).map(v=>[v.role,v.view,v.buttons,v.hiddenFields]),'views')}
`; if(sub==='auditConfig') return pageHead('Audit viste/tabelle','Audit totale attivo; audit di dettaglio configurabile qui.',``)+`
${table(['Tabella/Vista','Before/After','Allegati','Stato'],(state.auditTableRules||[]).map(a=>[a.table,a.beforeAfter?'Sì':'No',a.attachments?'Sì':'No',a.status]),'auditCfg')}
`; if(sub==='dbHub') return pageHead('Database Hub','Connessioni esterne vuote.',``)+`
${table(['Nome','Tipo','Host','Stato'],(state.dbConnections||[]).map(d=>[d.name,d.type,d.host,d.status]),'dbhub')}
`; if(sub==='integrations') return pageHead('API & Web Services','Nessuna integrazione precaricata.',``)+`
${table(['Nome','Tipo','Stato'],(state.webServices||[]).map(i=>[i.name,i.type,i.status]),'int')}
`; return baseAdminPage486(); }; setupChecklistPage=function(){ const checks=[ ['Company configurata',(state.companies||[]).length>0,'Creare almeno una company prima di operare'], ['Serie anagrafiche',(state.masterSeries||[]).length>0,'Obbligatorie per ID clienti/fornitori/articoli'], ['Serie documentali',(state.numberSeries||[]).length>0,'Obbligatorie per ordini/fatture/giustificativi'], ['Piano dei conti',(state.accounts||[]).length>0,'Conti statutari/gestionali in Piano dei conti'], ['Codici IVA',(state.vatCodes||[]).length>0,'Configurare IVA, split payment, reverse charge'], ['Dimensioni',(state.dimensions||[]).length>0,'CDC/CDR/commesse/BU'], ['Workflow approvativi',(state.workflowRules||[]).length>0,'RDA, pagamenti, contratti, note credito'], ['Nessun documento senza company',hasCompanyEverywhere(),'Tutti i documenti operativi devono avere company'] ]; return pageHead('Checklist setup','ERP pulito: evidenzia ciò che manca prima dell’operatività.')+`
${table(['Controllo','Esito','Nota'],checks.map(c=>[c[0],c[1]?'OK':'KO',c[2]]),'setup')}
`; }; window.__ds_v486_smoke=function(){return {version:state.version,clean:state.__cleanCoreBuild,companies:(state.companies||[]).length,customers:(state.customers||[]).length,suppliers:(state.suppliers||[]).length,budgetRows:(state.budgetRows||[]).length,treasuryRows:(state.treasuryRows||[]).length,users:(state.users||[]).length}}; save(); console.log('DataSphere v4.8.6 clean core patch loaded', window.__ds_v486_smoke()); }catch(e){console.error('DataSphere v4.8.6 clean patch failed',e);} })(); // ===== end DataSphere v4.8.6 clean core ===== // ===== DataSphere v4.9 CORE precision patch: clean data, finance links, anagrafiche groups/types ===== (function(){ try{ const CLEAN='4.9-clean-core-finance-linked'; const dataArrays=['companies','companyDefaults','masterSeries','numberSeries','documentTypes','documentSequences','customerTypes','supplierTypes','masterGroups','customerGroups','supplierGroups','customers','suppliers','addresses','items','itemGroups','vatCodes','paymentTerms','paymentMethods','paymentPeriods','accountingCauses','accounts','dimensions','dimensionCombinations','priceLists','priceListRows','offers','orders','invoices','manualInvoices','rdas','pos','purchaseOrders','vendorBills','contracts','contractRows','contractBillingSchemas','journalHeaders','journalLines','managerialLines','integrationEntries','openItems','compensations','equalizationWeights','splitSubjects','paEntities','sdiIntermediaries','jobs','nightPlans','notifications','files','assets','assetCategories','depreciations','budgetRows','budgetVersions','actualRows','allocationRules','warehouses','locations','stockLots','stockMoves','stockBalances','inventoryCounts','reorderRules','bankAccounts','bankStatements','bankStatementLines','treasuryRows','cashForecastRows','dbConnections','webServices','governanceRules','workflowRules','viewRules','auditTableRules','importTemplates','sessionLocks','sessions']; function ensureArrays(){dataArrays.forEach(k=>{if(!Array.isArray(state[k])) state[k]=[]}); if(!state.studio) state.studio={features:[],etlFlows:[],biCubes:[]}; if(!Array.isArray(state.audit)) state.audit=[]; if(!state.selectedRows) state.selectedRows={}; if(!state.filters) state.filters={};} function hardResetClean(){ensureArrays();dataArrays.forEach(k=>state[k]=[]);state.studio={features:[],etlFlows:[],biCubes:[]};state.audit=[];state.selectedRows={};state.filters={};state.__cleanCoreBuild=CLEAN;state.version='4.9';state.company='';state.role='Administrator';state.page='dashboard';state.sub=null;state.menuFilter='';state.user=null;state.users=[{email:'',password:'',name:'Admin DS',roles:['Administrator','System Administrator'],companies:[],costDims:['*'],revDims:['*'],langEnabled:true,aiEnabled:true,aiAutonomous:false,active:true,phone:'',mobile:'',businessPhone:'',twofaEmail:false,twofaSms:false}];} if(state.__cleanCoreBuild!==CLEAN){hardResetClean();save();} ensureArrays(); // remove reset from login UI; production clean CORE must not expose a reset button on login. renderLogin=function(){mount(`

DataSphere

ERP + ETL + BI + Studio
Primo accesso tecnico: / admin. Nessun dato operativo o demo è precaricato.
`);document.getElementById('loginBtn').onclick=login;document.getElementById('password').onkeydown=e=>{if(e.key==='Enter')login()};}; window.guid=function(){return (crypto&&crypto.randomUUID)?crypto.randomUUID():'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,c=>{const r=Math.random()*16|0,v=c==='x'?r:(r&3|8);return v.toString(16)})}; function companyFilter(rows){return (rows||[]).filter(r=>!state.company || !r.company || r.company===state.company)} function statusBadge(s){const v=esc(s||'bozza');let cls=v==='ok'||v==='contabilizzata'||v==='emessa'||v==='attivo'?'ok':(v==='bloccata'||v==='errore'?'bad':'warn');return `${v}`} // menu consolidation without losing existing sections function addChild(mid, child){const m=menus.find(x=>x.id===mid); if(m && !m.children.some(c=>c[0]===child[0])) m.children.push(child)} addChild('master',['customerTypes','Tipi cliente']); addChild('master',['supplierTypes','Tipi fornitore']); addChild('master',['customerGroups','Gruppi clienti']); addChild('master',['supplierGroups','Gruppi fornitori']); addChild('master',['masterGroups','Gruppi anagrafiche']); addChild('master',['dimensionCombinations','Combinazioni dimensioni']); const mMaster=menus.find(x=>x.id==='master'); if(mMaster){mMaster.children=mMaster.children.map(c=>c[0]==='accounts'?['accounts','Piano dei conti']:c).filter(c=>c[0]!=='managerialAccounts');} addChild('finance',['integrationEntries','Integrazioni contabili']); // keep search focus while typing const previousRenderApp=renderApp; renderApp=function(){const active=document.activeElement;const wasSearch=active&&active.id==='menuSearch';const pos=wasSearch?active.selectionStart:null;previousRenderApp();const ms=document.getElementById('menuSearch');if(ms){ms.oninput=e=>{state.menuFilter=e.target.value;save();const main=document.querySelector('aside.nav');const oldScroll=main?main.scrollTop:0;renderApp();const n=document.getElementById('menuSearch');if(n){n.focus();n.setSelectionRange(n.value.length,n.value.length)}const navEl=document.querySelector('aside.nav'); if(navEl) navEl.scrollTop=oldScroll;};if(wasSearch){ms.focus();try{ms.setSelectionRange(pos,pos)}catch(e){}}}const sel=document.getElementById('companySel');if(sel&&!sel.options.length){sel.innerHTML=''}}; // compact menu typography const style=document.createElement('style');style.textContent='.nav{font-size:13px}.nav-item{padding:9px 10px}.child{padding:8px 9px;font-size:13px}.nav-small{font-size:10px}.group-title{font-size:11px}.top select{max-width:260px}';document.head.appendChild(style); function emptyNotice(msg='Nessun record presente. Crea manualmente o importa dati da Amministrazione di Sistema → Import/Export configurazioni.'){return `
Nessun dato.
${esc(msg)}
`} function crudActions(entity,id){return ` `} const masterConfig={ customers:['Clienti',state.customers,['ID','Nome','Tipo','Gruppo','P.IVA','Company','Stato','Azioni'],r=>[r.id,r.name,r.type||'',r.group||'',r.vat||'',r.company||'',statusBadge(r.status||'attivo'),` ${crudActions('customers',r.id)}`]], customerTypes:['Tipi cliente',state.customerTypes,['Codice','Nome','Stato','Azioni'],r=>[r.code,r.name,statusBadge(r.status||'attivo'),crudActions('customerTypes',r.code)]], customerGroups:['Gruppi clienti',state.customerGroups,['Codice','Nome','Tipo','Stato','Azioni'],r=>[r.code,r.name,r.type||'',statusBadge(r.status||'attivo'),crudActions('customerGroups',r.code)]], suppliers:['Fornitori',state.suppliers,['ID','Nome','Tipo','Gruppo','P.IVA','Company','Stato','Azioni'],r=>[r.id,r.name,r.type||'',r.group||'',r.vat||'',r.company||'',statusBadge(r.status||'attivo'),crudActions('suppliers',r.id)]], supplierTypes:['Tipi fornitore',state.supplierTypes,['Codice','Nome','Stato','Azioni'],r=>[r.code,r.name,statusBadge(r.status||'attivo'),crudActions('supplierTypes',r.code)]], supplierGroups:['Gruppi fornitori',state.supplierGroups,['Codice','Nome','Tipo','Stato','Azioni'],r=>[r.code,r.name,r.type||'',statusBadge(r.status||'attivo'),crudActions('supplierGroups',r.code)]], masterGroups:['Gruppi anagrafiche',state.masterGroups,['Codice','Nome','Applicazione','Stato','Azioni'],r=>[r.code,r.name,r.appliesTo||'clienti/fornitori',statusBadge(r.status||'attivo'),crudActions('masterGroups',r.code)]], addresses:['Indirizzi fatturazione',state.addresses,['ID','Soggetto','Tipo','Etichetta','Città','Company','Azioni'],r=>[r.id,r.party||r.customer||'',r.type||'',r.label||'',r.city||'',r.company||'',crudActions('addresses',r.id)]], items:['Articoli',state.items,['Codice','Nome','Gruppo','IVA','Prezzo','Company','Azioni'],r=>[r.code,r.name,r.group||'',r.vat||'',money(r.price||0),r.company||'',crudActions('items',r.code)]], itemGroups:['Gruppi articolo',state.itemGroups,['Codice','Nome','Ricavo','Costo','Company','Azioni'],r=>[r.id||r.code,r.name,r.revAccount||'',r.costAccount||'',r.company||'',crudActions('itemGroups',r.id||r.code)]], vatCodes:['Codici IVA',state.vatCodes,['Codice','Nome','Aliquota','Split','Azioni'],r=>[r.code,r.name,(r.rate??0)+'%',r.splitEligible?'Sì':'No',crudActions('vatCodes',r.code)]], paymentTerms:['Condizioni pagamento',state.paymentTerms,['Codice','Nome','Giorni','Tipo','Azioni'],r=>[r.code,r.name,r.days||0,r.type||'',crudActions('paymentTerms',r.code)]], paymentMethods:['Metodi pagamento',state.paymentMethods,['Codice','Nome','IBAN','Tipo','Azioni'],r=>[r.code,r.name,r.requiresIban?'Sì':'No',r.type||'',crudActions('paymentMethods',r.code)]], paymentPeriods:['Periodicità pagamenti',state.paymentPeriods,['Codice','Nome','Mesi','Azioni'],r=>[r.code,r.name,r.months||'',crudActions('paymentPeriods',r.code)]], accountingCauses:['Causali contabili',state.accountingCauses,['Codice','Nome','Libro','Azioni'],r=>[r.code,r.name,r.book||'',crudActions('accountingCauses',r.code)]], accounts:['Piano dei conti',state.accounts,['Conto','Nome','Libro','Tipo','Sezione','Dim. obbl.','Azioni'],r=>[r.id||r.code,r.name,r.book||'statutario',r.type||'',r.section||'',r.dimRequired?'Sì':'No',crudActions('accounts',r.id||r.code)]], dimensions:['Dimensioni CDC/CDR',state.dimensions,['Codice','Tipo','Nome','Company','Azioni'],r=>[r.id||r.code,r.type,r.name,r.company||'',crudActions('dimensions',r.id||r.code)]], dimensionCombinations:['Combinazioni dimensioni',state.dimensionCombinations,['CDC','CDR','CONTO','VDB','TIPO','Company','Stato','Azioni'],r=>[r.cdc||'',r.cdr||'',r.account||'',r.vdb||'',r.type||'',r.company||'',statusBadge(r.status||'attiva'),crudActions('dimensionCombinations',r.id)]], priceLists:['Listini',state.priceLists,['Codice','Nome','Tipo','Company','Azioni'],r=>[r.code,r.name,r.type||'',r.company||'',crudActions('priceLists',r.code)]], documentTypes:['Tipi documento',state.documentTypes,['Codice','Nome','Area','Company','Azioni'],r=>[r.code,r.name,r.area||'',r.company||'',crudActions('documentTypes',r.code)]], numberSeries:['Serie documenti',state.numberSeries,['Serie','Tipo','Prossimo numero','Company','Azioni'],r=>[r.code||r.series,r.type,r.next||r.nextNumber||'',r.company||'',crudActions('numberSeries',r.code||r.series)]], split:['Split payment validità',state.splitSubjects,['P.IVA/C.F.','Nome','Da','A','Attivo','Azioni'],r=>[r.vat||r.fiscalCode,r.name,r.validFrom,r.validTo,r.active?'Sì':'No',crudActions('splitSubjects',r.id)]], paEntities:['Anagrafiche PA / IPA',state.paEntities,['Codice IPA','Nome','P.IVA','SDI','Split','Azioni'],r=>[r.ipaCode||r.ipa,r.name,r.vat,r.sdi,r.split?'Sì':'No',crudActions('paEntities',r.id||r.ipaCode)]], equalization:['Perequazione / Pesi',state.equalizationWeights,['Regola','Sorgente','Target','Peso','Company','Azioni'],r=>[r.name,r.source,r.target,(r.weight||0)+'%',r.company||'',crudActions('equalizationWeights',r.id)]] }; masterPage=function(){const sub=state.sub||'customers';const cfg=masterConfig[sub]||masterConfig.customers;const rows=companyFilter(cfg[1]).map(cfg[3]);return pageHead('Anagrafiche e Configurazioni',cfg[0],``)+`
${rows.length?table(cfg[2],rows,sub):emptyNotice()}
`}; purchasePage=function(){const sub=state.sub||'rdas';if(sub==='rdas')return pageHead('Richieste di acquisto','Richieste di acquisto in bozza/in approvazione, budget, allegati e workflow.',``)+`
${table(['ID','Fornitore','Data','Stato','Company','Salta budget','Azioni'],companyFilter(state.rdas).map(r=>[r.id,partyName(r.supplier),r.date,statusBadge(r.status),r.company||'',r.skipBudget?'Sì':'No',``]),'rda')}
`;if(sub==='purchaseOrders')return pageHead('Ordini acquisto','ODA/PO generati da RDA o manuali.')+`
${table(['ID','Fornitore','Stato','Company','Richiesta origine'],companyFilter(state.pos).map(p=>[p.id,partyName(p.supplier),statusBadge(p.status),p.company||'',p.sourceRda||'manuale']),'po')}
`;if(sub==='vendorBills')return pageHead('Fatture passive','Fatture fornitori, 3-way match, IVA e scadenzario.')+`
${table(['ID','Fornitore','Data','Stato','Totale','Company'],companyFilter(state.vendorBills).map(v=>[v.id,partyName(v.supplier),v.date,statusBadge(v.status),money(v.total||total(v.rows)),v.company||'']),'vendorBills')}
`;if(sub==='supplierAging')return pageHead('Scadenzario fornitori','Partite aperte fornitori calcolate da fatture passive e pagamenti.')+`
${table(['Fornitore','Documento','Scadenza','Importo','Residuo','Stato'],companyFilter(state.openItems).filter(o=>o.partyType==='supplier').map(o=>[partyName(o.party),o.document,o.dueDate,money(o.amount),money(o.openAmount),statusBadge(o.status)]),'supplierAging')}
`;return pageHead('Budget acquisti','Budget pluriennale per dimensione, conto e periodo.')+`
${table(['Anno','Periodo','Conto','Dimensione','Budget','Actual','Impegnato','Disponibile'],companyFilter(state.budgetRows).map(b=>[b.year,b.period,b.account,b.dimension,money(b.budget),money(b.actual),money(b.committed),money((b.budget||0)-(b.actual||0)-(b.committed||0))]),'purchaseBudget')}
`}; financePage=function(){const sub=state.sub||'journal';if(sub==='integrationEntries')return pageHead('Integrazioni contabili','Tabella ponte con GUID per ogni evento gestionale/statutario collegato a vendite, acquisti, magazzino e finance.')+`
${table(['GUID','Data','Origine','Evento','Libro','Dare','Avere','Importo','Company','Documento'],companyFilter(state.integrationEntries).map(e=>[e.guid,e.date,e.source,e.event,e.book,e.debit,e.credit,money(e.amount),e.company,e.documentId]),'integrationEntries')}
`;if(sub==='manualJournal')return manualJournalPage();if(sub==='compensations')return compensationsPage();if(sub==='vatSettlement')return vatSettlementPage();if(sub==='masters')return pageHead('Mastri contabili','Schede per conto derivate da righe prima nota.')+`
${table(['Conto','Data','Documento','Dare','Avere','Saldo'],companyFilter(state.journalLines).map(l=>[l.account||l.debit||l.credit,l.date,l.documentId||'',money(l.debitAmount||0),money(l.creditAmount||0),money((l.debitAmount||0)-(l.creditAmount||0))]),'masters')}
`;return pageHead(currentTitle(),'Finance integrata: libro giornale, IVA, prime note, integrazioni e quadrature.')+`
${table(['Data','Tipo','Documento','Dare','Avere','Importo','Company','GUID'],companyFilter(state.journalLines).map(l=>[l.date,l.type,l.documentId||'',l.debit,l.credit,money(l.amount),l.company||'',l.guid||'']),'jrnl')}
`}; controllingPage=function(){const sub=state.sub||'budgets';if(sub==='budgets')return pageHead('Budget pluriennale','Budget per conto, dimensione, anno e periodo.',``)+`
${table(['Anno','Periodo','Conto','Dimensione','Budget','Actual','Impegnato','Disponibile'],companyFilter(state.budgetRows).map(b=>[b.year,b.period,b.account,b.dimension,money(b.budget),money(b.actual),money(b.committed),money((b.budget||0)-(b.actual||0)-(b.committed||0))]),'budgets')}
`;if(sub==='actuals')return pageHead('Actual da contabilità','Consuntivi derivati da integrazioni contabili e prime note.')+`
${table(['Data','Conto','Dimensione','Importo','Origine'],companyFilter(state.integrationEntries).filter(e=>e.book==='gestionale'||e.book==='statutario').map(e=>[e.date,e.debit||e.credit,e.dimension,money(e.amount),e.source]),'actuals')}
`;if(sub==='equalizationRun')return pageHead('Perequazione esecuzione','Ripartizioni su pesi e scritture gestionali.')+`
${table(['Regola','Sorgente','Target','Peso','Stato'],companyFilter(state.equalizationWeights).map(e=>[e.name,e.source,e.target,(e.weight||0)+'%',statusBadge(e.status)]),'eqrun')}
`;return pageHead(currentTitle(),'Sezione controllo di gestione.')+`
${emptyNotice()}
`}; treasuryPage=function(){const sub=state.sub||'bankStatements';return pageHead(currentTitle(),'Tesoreria e banche: dati reali da import estratti, incassi, pagamenti e forecast.')+`
${table(['ID','Data','Descrizione','Importo','Stato','Company'],companyFilter(state.treasuryRows).filter(r=>!r.area||r.area===sub).map(r=>[r.id,r.date,r.description,money(r.amount),statusBadge(r.status),r.company||'']),'treasury')}
`}; assetsPage=function(){const sub=state.sub||'assetRegister';if(sub==='depreciation')return pageHead('Ammortamenti','Quote cespiti con blocco doppio periodo.')+`
${table(['Cespite','Periodo','Quota','Stato','GUID'],companyFilter(state.depreciations).map(d=>[d.asset,d.period,money(d.amount),statusBadge(d.status),d.guid||'']),'dep')}
`;return pageHead('Registro cespiti','Cespiti collegati a RDA/PO/fatture, conti e dimensioni.')+`
${table(['Codice','Descrizione','Valore','Fondo','Dimensione','Stato','Company'],companyFilter(state.assets).map(a=>[a.id,a.name,money(a.value),money(a.fund),a.dimension,statusBadge(a.status),a.company||'']),'assets')}
`}; dashboard=function(){return pageHead('Dashboard Operativa','ERP pulito: riepiloghi calcolati solo dai dati presenti.')+`

Company

${state.companies.length}

Clienti

${state.customers.length}

Documenti

${state.offers.length+state.orders.length+state.invoices.length+state.rdas.length+state.pos.length}

Integrazioni contabili

${state.integrationEntries.length}

Checklist collegamenti CORE

${setupChecklistPage?setupChecklistPage().replace(/^[\s\S]*?
/,'').replace(/<\/div>$/,''):emptyNotice()}
`}; adminPage=(function(old){return function(){const sub=state.sub||'users';if(sub==='setupChecklist')return setupChecklistPage();if(sub==='companies')return pageHead('Company','Creazione e manutenzione multi-company. Nessun documento può essere registrato senza company.')+`
${table(['Codice','Nome','P.IVA','Attiva','Azioni'],state.companies.map(c=>[c.code||c.id,c.name,c.vat||'',c.active!==false?'Sì':'No',crudActions('companies',c.code||c.id)]),'companies')}
`;return old();}})(adminPage); setupChecklistPage=function(){const docs=[...state.offers,...state.orders,...state.invoices,...state.rdas,...state.pos,...state.contracts,...state.journalHeaders,...state.integrationEntries];const checks=[['Company configurata',state.companies.length>0,'Creare almeno una company'],['Serie anagrafiche',state.masterSeries.length>0,'Obbligatorie per ID anagrafiche'],['Serie documentali',state.numberSeries.length>0,'Obbligatorie per documenti'],['Piano dei conti',state.accounts.length>0,'Statutario e gestionale nella stessa vista Piano dei conti'],['Codici IVA',state.vatCodes.length>0,'IVA, split, reverse charge'],['Combinazioni dimensioni',state.dimensionCombinations.length>0,'CDC-CDR-CONTO-VDB-TIPO autorizzati'],['Documenti senza company',docs.every(d=>!!d.company),'Tutti i documenti e movimenti devono avere company'],['Integrazioni con GUID',state.integrationEntries.every(e=>!!e.guid),'Ogni evento contabile/gestionale deve avere GUID univoco']];return pageHead('Checklist setup','Controlli di coerenza CORE prima di operare.')+`
${table(['Controllo','Esito','Nota'],checks.map(c=>[c[0],c[1]?'OK':'KO',c[2]]),'setup')}
`}; window.validateDimensionCombination=function({cdc,cdr,account,vdb,type,company}){return (state.dimensionCombinations||[]).some(x=>(!company||!x.company||x.company===company)&&(!x.cdc||x.cdc===cdc)&&(!x.cdr||x.cdr===cdr)&&(!x.account||x.account===account)&&(!x.vdb||x.vdb===vdb)&&(!x.type||x.type===type)&&x.status!=='bloccata')}; window.recordIntegration=function({source,event,book='gestionale',debit='',credit='',amount=0,dimension='',documentId='',company=state.company}){const entry={guid:window.guid(),date:today(),source,event,book,debit,credit,amount:+amount||0,dimension,documentId,company,status:'registrata'};state.integrationEntries.push(entry);audit('integration.entry',`${source}/${event}/${documentId}`);save();return entry}; window.newGenericRecord=function(entity){const id='NEW-'+uid();const labels={customerTypes:['code','name','status'],customerGroups:['code','name','type','status'],supplierTypes:['code','name','status'],supplierGroups:['code','name','type','status'],masterGroups:['code','name','appliesTo','status'],dimensionCombinations:['cdc','cdr','account','vdb','type','company','status'],companies:['code','name','vat','active'],accounts:['id','name','book','type','section','dimRequired'],vatCodes:['code','name','rate','splitEligible'],paymentTerms:['code','name','days','type'],paymentMethods:['code','name','type','requiresIban'],accountingCauses:['code','name','book'],items:['code','name','group','vat','price','company'],itemGroups:['code','name','revAccount','costAccount','company'],customers:['id','name','type','group','vat','company','status'],suppliers:['id','name','type','group','vat','company','status']};const fields=labels[entity]||['id','name','company','status'];openModal(`Nuovo record - ${entity}`,`
${fields.map(f=>`
`).join('')}
`,``)}; window.saveNewGenericRecord=function(entity,fieldsJoined){const fields=fieldsJoined.split('|');const obj={};fields.forEach(f=>{let v=document.getElementById('nr_'+f)?.value||''; if(v==='true')v=true;if(v==='false')v=false;obj[f]=v}); if(!obj.id&&obj.code)obj.id=obj.code;if(!obj.code&&obj.id)obj.code=obj.id;if(!obj.company&&['customers','suppliers','items','itemGroups','accounts','dimensionCombinations','companies'].includes(entity))obj.company=state.company;state[entity]=state[entity]||[];state[entity].push(obj);audit('create.'+entity,obj.id||obj.code||obj.name);save();closeModal();renderApp()}; window.editGenericRecord=function(entity,id){const arr=state[entity]||[];const rec=arr.find(x=>(x.id||x.code||x.series)===id);if(!rec)return dsNotify('Record non trovato');const fields=Object.keys(rec);openModal(`Modifica ${entity} ${id}`,`
${fields.map(f=>`
`).join('')}
`,``)}; window.saveEditGenericRecord=function(entity,id,fieldsJoined){const arr=state[entity]||[];const rec=arr.find(x=>(x.id||x.code||x.series)===id);if(!rec)return;fieldsJoined.split('|').forEach(f=>rec[f]=document.getElementById('er_'+f)?.value||'');audit('update.'+entity,id);save();closeModal();renderApp()}; window.deleteGenericRecord=function(entity,id){if(!confirm('Eliminare il record selezionato?'))return;state[entity]=(state[entity]||[]).filter(x=>(x.id||x.code||x.series)!==id);audit('delete.'+entity,id);save();renderApp()}; window.exportCurrent=function(entity,fmt){dsNotify(`Export ${fmt.toUpperCase()} della vista ${entity}: in produzione usa filtri/selezione e genera file server-side.`)}; window.importTemplate=function(entity){dsNotify(`Template import per ${entity}: scaricare CSV/JSON/XLS con colonne richieste e validazioni.`)}; window.__ds_v49_smoke=function(){return {build:CLEAN,customers:state.customers.length,suppliers:state.suppliers.length,budgetRows:state.budgetRows.length,treasuryRows:state.treasuryRows.length,hasFakeSupplierAging:purchasePage().includes('Budget acquisti')&&state.sub==='supplierAging'}}; save();console.log('DataSphere v4.9 CORE precision patch loaded',window.__ds_v49_smoke()); }catch(e){console.error('DataSphere v4.9 patch failed',e);root.innerHTML='
Errore patch v4.9
'+esc(e.message)+'
'} })(); // ===== end DataSphere v4.9 CORE precision patch ===== // ===== DataSphere v4.9.10 visible ERP/localization/security patch ===== (function(){ try{ const BUILD='4.9.10-visible-erp-italia'; function ensure4910Arrays(){ ['billingPeriodicities','securityPolicies','securityEvents','translationNamespaces','translationKeys','translationValues','apiKeys'].forEach(k=>{ if(!Array.isArray(state[k])) state[k]=[]; }); if(!state.locales) state.locales=[ {code:'it',name:'Italiano',active:true,default:true}, {code:'en',name:'English',active:true,default:false}, {code:'fr',name:'Français',active:true,default:false}, {code:'de',name:'Deutsch',active:true,default:false}, {code:'es',name:'Español',active:true,default:false} ]; if(!state.billingPeriodicities.length){ state.billingPeriodicities=[ {id:'BP-ONCE',code:'once',name:'Una tantum',unit:'once',interval:1,months:0,active:true,default:false}, {id:'BP-MONTHLY',code:'monthly',name:'Mensile',unit:'month',interval:1,months:1,active:true,default:true}, {id:'BP-BIMONTHLY',code:'bimonthly',name:'Bimestrale',unit:'month',interval:2,months:2,active:true,default:false}, {id:'BP-QUARTERLY',code:'quarterly',name:'Trimestrale',unit:'month',interval:3,months:3,active:true,default:false}, {id:'BP-FOURMONTHLY',code:'four_monthly',name:'Quadrimestrale',unit:'month',interval:4,months:4,active:true,default:false}, {id:'BP-SEMIANNUAL',code:'semiannual',name:'Semestrale',unit:'month',interval:6,months:6,active:true,default:false}, {id:'BP-ANNUAL',code:'annual',name:'Annuale',unit:'year',interval:1,months:12,active:true,default:false}, {id:'BP-MILESTONE',code:'milestone',name:'A milestone',unit:'milestone',interval:1,months:0,active:true,default:false}, {id:'BP-CUSTOM',code:'custom',name:'Personalizzata',unit:'custom',interval:1,months:0,active:true,default:false} ]; } if(!state.securityPolicies.length){ state.securityPolicies=[ {id:'SEC-RATE',name:'Rate limiting API',area:'API',enabled:true,value:'300 req/min'}, {id:'SEC-BRUTE',name:'Protezione brute force login',area:'Auth',enabled:true,value:'5 tentativi / 15 min'}, {id:'SEC-CSRF',name:'CSRF middleware',area:'Web',enabled:true,value:'attivo'}, {id:'SEC-HEADERS',name:'Security headers',area:'HTTP',enabled:true,value:'CSP,HSTS,XFO,nosniff'}, {id:'SEC-COMPANY',name:'Company isolation',area:'Tenant',enabled:true,value:'X-Company-Id'} ]; } if(!state.translationNamespaces.length){ state.translationNamespaces=[ {id:'TRN-CORE',code:'core',name:'Core UI',layer:'core',active:true}, {id:'TRN-FIN',code:'finance',name:'Finance',layer:'core',active:true}, {id:'TRN-SALES',code:'sales',name:'Ciclo attivo',layer:'core',active:true}, {id:'TRN-PUR',code:'purchase',name:'Ciclo passivo',layer:'core',active:true}, {id:'TRN-SEC',code:'security',name:'Security',layer:'core',active:true} ]; } state.lang = state.lang || 'it'; } ensure4910Arrays(); function addChild4910(menuId, child, pos){ const m=menus.find(x=>x.id===menuId); if(!m) return; if(!m.children.some(c=>c[0]===child[0])){ if(Number.isInteger(pos)) m.children.splice(pos,0,child); else m.children.push(child); } } function addMenu4910(menu){ if(!menus.some(m=>m.id===menu.id)) menus.push(menu); } const purchaseMenu=menus.find(m=>m.id==='purchase'); if(purchaseMenu){ purchaseMenu.sub='Richieste di acquisto, ordini, fatture'; purchaseMenu.children=purchaseMenu.children.map(c=>c[0]==='rdas'?['rdas','Richieste di acquisto']:c); } addChild4910('master',['billingPeriodicities','Periodicità fatturazione'],5); addChild4910('finance',['billingPeriodicities','Periodicità fatturazione'],3); addChild4910('finance',['vatRegisters','Registri IVA'],4); addChild4910('finance',['openItems','Partitario'],5); addChild4910('admin',['security','Security'],8); addChild4910('admin',['i18n','Lingue e traduzioni'],9); addMenu4910({grp:'AMMINISTRAZIONE',id:'security',label:'Security',sub:'Rate limit, brute force, CSRF, API key',children:[['securityDashboard','Dashboard security'],['securityPolicies','Policy sicurezza'],['apiKeys','API key'],['securityEvents','Eventi sicurezza']]}); function renderBillingPeriodicityTable(){ return table(['Codice','Nome','Unità','Intervallo','Mesi eq.','Default','Attiva','Azioni'], (state.billingPeriodicities||[]).map(p=>[ p.code,p.name,p.unit,p.interval,p.months,p.default?'Sì':'No',p.active?'Sì':'No', `` ]),'billingPeriodicities'); } window.newBillingPeriodicity4910=function(){ const id='BP-'+uid(); state.billingPeriodicities.push({id,code:'custom_'+uid().toLowerCase(),name:'Nuova periodicità',unit:'month',interval:1,months:1,active:true,default:false}); save(); editBillingPeriodicity4910(id); }; window.editBillingPeriodicity4910=function(id){ const p=(state.billingPeriodicities||[]).find(x=>x.id===id); if(!p) return dsNotify('Periodicità non trovata'); openModal('Periodicità fatturazione '+p.code, `
La stessa periodicità può essere usata da contratti, righe contratto, schemi consegna, ordini e fatture.
`, ``); }; window.saveBillingPeriodicity4910=function(id){ const p=(state.billingPeriodicities||[]).find(x=>x.id===id); if(!p) return; p.code=document.getElementById('bp_code').value; p.name=document.getElementById('bp_name').value; p.unit=document.getElementById('bp_unit').value; p.interval=+document.getElementById('bp_interval').value||1; p.months=+document.getElementById('bp_months').value||0; p.default=document.getElementById('bp_default').value==='true'; p.active=document.getElementById('bp_active').value==='true'; if(p.default) state.billingPeriodicities.forEach(x=>{if(x.id!==id)x.default=false}); audit('billing.periodicity.save',p.code); save(); closeModal(); renderApp(); }; const oldMasterPage4910=masterPage; masterPage=function(){ if(state.sub==='billingPeriodicities'){ return pageHead('Periodicità fatturazione','Tabella master aggiornabile per contratti, ordini, fatture e job massivi.', ``) + `
${renderBillingPeriodicityTable()}
`; } return oldMasterPage4910(); }; const oldFinancePage4910=financePage; financePage=function(){ const sub=state.sub||'journal'; if(sub==='billingPeriodicities'){ return pageHead('Periodicità fatturazione','Vista finance delle periodicità usate da fatture, ODV e contratti.', ``) + `
${renderBillingPeriodicityTable()}
`; } if(sub==='vatRegisters'){ const rows=companyFilter(state.vatRegisters||[]).concat(companyFilter(state.integrationEntries||[]).filter(e=>String(e.event||'').toLowerCase().includes('iva'))); return pageHead('Registri IVA','Registro IVA vendite/acquisti/reverse/intracom collegato alle fatture contabilizzate.')+ `
${table(['Data','Registro','Documento','Imponibile','IVA','Totale','Company'],rows.map(r=>[r.date||r.registrationDate,r.register||r.registerType||'vendite/acquisti',r.documentId||r.invoice||'',money(r.taxable||r.taxable_amount||0),money(r.vat||r.tax_amount||0),money(r.total||r.total_amount||0),r.company||state.company]),'vatRegisters')}
`; } if(sub==='openItems'){ return pageHead('Partitario','Partite aperte clienti/fornitori collegate a fatture, incassi e pagamenti.')+ `
${table(['Partner','Documento','Scadenza','Importo','Residuo','Stato','Company'],companyFilter(state.openItems||[]).map(o=>[partyName(o.party||o.partner),o.document||o.reference,o.dueDate||o.due_date,money(o.amount||o.original_amount),money(o.open||o.openAmount||o.open_amount),statusBadge(o.status||((o.open||o.openAmount)>0?'aperta':'chiusa')),o.company||'']),'openItems')}
`; } return oldFinancePage4910(); }; function securityDashboard4910(){ return pageHead('Security','Configurazione visibile: CSRF, rate limiting, brute force, header, API key e isolamento company.', ``) + `

Rate limiting

Attivo
300 req/min default

Brute force

Attivo
5 tentativi / 15 min

CSRF

Attivo
Django CsrfViewMiddleware

API key

/api/ext
X-DS-Api-Key
${table(['Policy','Area','Valore','Abilitata'],state.securityPolicies.map(p=>[p.name,p.area,p.value,p.enabled?'Sì':'No']),'secDash')}
`; } const oldAdminPage4910=adminPage; adminPage=function(){ const sub=state.sub||'users'; if(sub==='security' || sub==='securityDashboard') return securityDashboard4910(); if(sub==='securityPolicies') return pageHead('Policy sicurezza','Parametri visibili per security runtime.')+ `
${table(['ID','Nome','Area','Valore','Abilitata'],state.securityPolicies.map(p=>[p.id,p.name,p.area,p.value,p.enabled?'Sì':'No']),'securityPolicies')}
`; if(sub==='apiKeys') return pageHead('API key','Credenziali per sistemi esterni su /api/ext/.')+ `
${table(['Applicazione','Scope','Stato'],(state.apiKeys||[]).map(k=>[k.name,k.scopes,k.active?'attiva':'disattiva']),'apiKeys')}
`; if(sub==='securityEvents') return pageHead('Eventi sicurezza','Log visibile di blocchi rate limit, brute force e violazioni company.')+ `
${table(['Data','Tipo','Utente/IP','Esito'],(state.securityEvents||[]).map(e=>[e.date,e.type,e.actor,e.result]),'securityEvents')}
`; if(sub==='i18n'){ return pageHead('Lingue e traduzioni','Lingue locali configurabili e traduzioni per namespace CORE/CODE.', ``) + `

Lingue

${table(['Codice','Nome','Attiva','Default','Azioni'],state.locales.map(l=>[l.code,l.name,l.active?'Sì':'No',l.default?'Sì':'No',``]),'locales')}

Namespace

${table(['Codice','Nome','Layer','Attivo'],state.translationNamespaces.map(n=>[n.code,n.name,n.layer,n.active?'Sì':'No']),'trns')}
`; } return oldAdminPage4910(); }; const oldPageHtml4910=pageHtml; pageHtml=function(){ if(state.page==='security') return securityDashboard4910(); return oldPageHtml4910(); }; window.setLang4910=function(lang){ state.lang=lang; if(state.user) state.user.langEnabled=true; save(); renderApp(); }; window.seedTranslations4910=function(){ ensure4910Arrays(); fetch('/api/workspace/seed/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':(document.cookie.match(/csrftoken=([^;]+)/)||[])[1]||''},body:'{}'}).then(r=>r.json()).then(j=>{dsNotify('Lingue/traduzioni caricate: '+JSON.stringify(j.details||j)); location.reload();}).catch(e=>dsNotify('Errore seed lingue: '+e.message)); }; const oldRenderApp4910=renderApp; renderApp=function(){ ensure4910Arrays(); oldRenderApp4910(); const lang=document.getElementById('langSel'); if(lang){ const current=state.lang||'it'; lang.innerHTML=['it','en','fr','de','es'].map(l=>``).join(''); lang.onchange=e=>setLang4910(e.target.value); } }; window.__ds_v4910_smoke=function(){ return {build:BUILD, billingPeriodicities:state.billingPeriodicities.length, locales:state.locales.map(l=>l.code), hasSecurityMenu:!!menus.find(m=>m.id==='security')}; }; save(); console.log('DataSphere v4.9.10 visible patch loaded', window.__ds_v4910_smoke()); }catch(e){ console.error('DataSphere v4.9.10 visible patch failed', e); } })(); // ===== end DataSphere v4.9.10 visible ERP/localization/security patch ===== // ===== DataSphere v4.9.10 AI-only stable patch ===== (function(){ try{ function aiContext4910Only(){ return [ 'DataSphere ERP', 'Pagina: '+(state.page||'dashboard'), 'Sotto-sezione: '+(state.sub||''), 'Company: '+(state.company||''), 'Utente: '+((state.user&&state.user.email)||''), 'AI-only patch from DataSphere v4.9.10' ].join('\n'); } window.openAiAssistant4910Only=function(){ openModal('Assistente AI DataSphere', `
Configura facoltativamente provider/proxy/API key. Se lasci vuoto, usa la configurazione backend o la modalità offline.
`, ``); setTimeout(()=>{const el=document.getElementById('aiPrompt4910Only'); if(el) el.focus();},50); }; function aiPayload4910Only(run){ const provider=(document.getElementById('aiProvider4910Only')||{}).value||undefined; const model=(document.getElementById('aiModel4910Only')||{}).value||undefined; const proxy_url=(document.getElementById('aiProxy4910Only')||{}).value||undefined; const api_key=(document.getElementById('aiKey4910Only')||{}).value||undefined; const prompt=(document.getElementById('aiPrompt4910Only')||{}).value||'Test AI DataSphere'; const payload={prompt:prompt,context:aiContext4910Only()}; if(provider) payload.provider=provider; if(model) payload.model=model; if(proxy_url) payload.proxy_url=proxy_url; if(api_key) payload.api_key=api_key; if(run!==undefined) payload.run=run; return payload; } window.testAi4910Only=async function(){ const out=document.getElementById('aiAnswer4910Only'); out.style.display='block'; out.textContent='Test AI in corso...'; try{ const res=await fetch('/api/ai/test/',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(aiPayload4910Only(true)) }); const data=await res.json(); out.textContent=JSON.stringify(data,null,2); }catch(e){ out.textContent='Errore test AI: '+e.message; } }; window.sendAiPrompt4910Only=async function(){ const out=document.getElementById('aiAnswer4910Only'); const payload=aiPayload4910Only(); if(!String(payload.prompt||'').trim()){ dsNotify('Scrivi una domanda per l’assistente.'); return; } out.style.display='block'; out.textContent='Sto elaborando...'; try{ const res=await fetch('/api/ai/chat/',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload) }); const data=await res.json(); out.textContent=data.answer || data.error || JSON.stringify(data,null,2); }catch(e){ out.textContent='Errore chiamata AI: '+e.message+'\nVerifica che il backend esponga /api/ai/chat/.'; } }; ['openAiAssistant','showAiAssistant','aiAssistant','openAI','showAI','assistantAI','openAiAssistant4911','openAiAssistant4912'].forEach(name=>{ try{ window[name]=window.openAiAssistant4910Only; }catch(e){} }); const oldRenderAppAiOnly=typeof renderApp==='function'?renderApp:null; if(oldRenderAppAiOnly){ renderApp=function(){ oldRenderAppAiOnly(); setTimeout(()=>{ document.querySelectorAll('button,.btn').forEach(b=>{ const t=(b.textContent||'').toLowerCase(); if((t.includes('assistente')||t.includes(' ai')) && !b.dataset.aiOnly4910){ b.dataset.aiOnly4910='1'; b.onclick=function(ev){ ev.preventDefault(); window.openAiAssistant4910Only(); }; } }); },80); }; } console.log('DataSphere AI-only patch from v4.9.10 loaded'); }catch(e){ console.error('DataSphere AI-only patch failed', e); } })(); // ===== end DataSphere v4.9.10 AI-only stable patch ===== // ===== DataSphere v4.9.14 CORE GUI CLEAN - single authoritative UI ===== (function(){ try { const VERSION = "4.9.14-core-gui-clean"; function esc(v){ return String(v ?? "").replace(/[&<>"']/g, m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } function id(prefix){ return prefix+"_"+Date.now()+"_"+Math.floor(Math.random()*1000); } function persist(){ if(typeof save==="function") save(); } function badge(v){ return `${esc(v)}`; } function table(headers, rows){ return `${headers.map(h=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(h)}
${c}
Nessun dato
`; } function input(label,key,value,type="text"){ return `
`; } function select(label,key,value,opts){ return `
`; } function ensure(){ state.dsClean ||= {}; state.dsClean.workflow ||= { active:"purchase_request-basic", selected:null, connectFrom:null, nodes:[ {id:"start",type:"start",label:"Start",x:80,y:120,props:{}}, {id:"amount",type:"amount_rule",label:"Regola importo",x:320,y:120,props:{field:"amount",operator:">",value:"5000"}}, {id:"approval",type:"approval",label:"Approva Manager",x:580,y:120,props:{role:"purchase_manager",group:"",user:"",timeout_hours:"48"}}, {id:"end",type:"end",label:"Fine approvato",x:840,y:120,props:{}} ], edges:[ {id:"e1",from:"start",to:"amount",label:""}, {id:"e2",from:"amount",to:"approval",label:"> 5000"}, {id:"e3",from:"approval",to:"end",label:"ok"} ] }; state.dsClean.etl ||= { active:"sample-etl-flow", selected:null, connectFrom:null, nodes:[ {id:"source",type:"source",label:"Sorgente DB",x:80,y:120,props:{connection:"datasphere-core-db",table:"contracts_saleorder",query:""}}, {id:"map",type:"map",label:"Mapping",x:320,y:120,props:{mapping:"id→order_id,total→amount"}}, {id:"validate",type:"validate",label:"Validazione",x:580,y:120,props:{rule:"amount >= 0",on_error:"reject"}}, {id:"target",type:"target",label:"Destinazione",x:840,y:120,props:{connection:"datasphere-core-db",table:"staging_sales_orders"}} ], edges:[ {id:"ee1",from:"source",to:"map",label:"rows"}, {id:"ee2",from:"map",to:"validate",label:"mapped"}, {id:"ee3",from:"validate",to:"target",label:"valid"} ] }; state.dsClean.dim ||= { dimensions:[{code:"CPIR21",name:"Dimensione CPIR21",active:true}], budgets:[{code:"GEN-BDG",name:"Voce budget generale",capex:false,opex:true}], groups:[{code:"GEN-ITEM",name:"Gruppo articolo generale",asset:false}], combos:[ {code:"CPIR21-purchase",dimension:"CPIR21",budget:"GEN-BDG",group:"GEN-ITEM",area:"purchase",status:"enabled",priority:100}, {code:"CPIR21-sales",dimension:"CPIR21",budget:"GEN-BDG",group:"GEN-ITEM",area:"sales",status:"enabled",priority:100}, {code:"CPIR21-asset",dimension:"CPIR21",budget:"GEN-BDG",group:"GEN-ITEM",area:"asset",status:"enabled",priority:100} ], logs:[] }; state.dsClean.db ||= { maps:[ {table:"finance_invoice",erp:"Fatture",module:"Finance",desc:"Fatture attive/passive"}, {table:"finance_journalentry",erp:"Libro giornale",module:"Finance",desc:"Registrazioni contabili"}, {table:"finance_journalline",erp:"Righe libro giornale",module:"Finance",desc:"Righe contabili"}, {table:"core_processes_financialintegrationentry",erp:"Integrazioni finanziarie",module:"Finance",desc:"Eventi ordine-consegna-fattura"}, {table:"dimensions_dimensioncombination",erp:"Combinazioni dimensionali",module:"Dimensioni",desc:"Setup dimensionale"}, {table:"approval_engine_approvalflow",erp:"Flussi approvativi",module:"Workflow",desc:"Workflow configurabili"}, {table:"datahub_etlflow",erp:"Flussi ETL",module:"ETL",desc:"Flussi ETL"}, {table:"fixed_assets_fixedasset",erp:"Schede cespite",module:"Cespiti",desc:"Cespiti"} ], queries:[{name:"Integrazioni finanziarie",sql:"select * from core_processes_financialintegrationentry limit 50"}], logs:[] }; state.dsClean.bi ||= { sources:[{code:"datasphere-core-db",type:"postgres",host:"postgres",status:"enabled"}], datasets:[{code:"finance-overview",name:"Finance Overview",source:"datasphere-core-db",query:"select 1 as ok",refresh:"manual"}], dashboards:[{code:"core-dashboard",name:"CORE Dashboard",widgets:1}], widgets:[{code:"kpi-invoices",title:"Fatture",type:"kpi",dataset:"finance-overview"}], logs:[] }; state.dsClean.jobs ||= { jobs:[ {code:"sales.generate_orders_from_quotes",name:"Genera ordini da offerte",area:"sales",enabled:true}, {code:"sales.generate_orders_from_contracts",name:"Genera ordini da contratti",area:"sales",enabled:true}, {code:"sales.deliver_orders",name:"Consegna ordini",area:"sales",enabled:true}, {code:"sales.invoice_orders",name:"Fattura ordini",area:"sales",enabled:true}, {code:"finance.vat_settlement",name:"Liquidazione IVA",area:"finance",enabled:true}, {code:"etl.run_flow",name:"Esegui ETL",area:"etl",enabled:true}, {code:"bi.refresh_datasets",name:"Refresh BI",area:"bi",enabled:true}, {code:"erp.integrity_check",name:"Controllo integrità ERP",area:"erp",enabled:true} ], runs:[] }; state.dsClean.fin ||= {rows:[]}; } function menuAdd(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } function childAdd(id,c){ const m=menus.find(x=>x.id===id); if(m && !m.children.some(y=>y[0]===c[0])) m.children.push(c); } menuAdd({grp:"PLATFORM",id:"database",label:"Database",sub:"Console DB, SQL, tabelle ERP",children:[["dbOverview","Overview"],["dbTables","Tabelle ERP"],["sqlConsole","SQL Console"],["savedQueries","Query salvate"],["dbConnections","Connessioni"],["dbSync","Sync/Backup"]]}); menuAdd({grp:"PLATFORM",id:"dimensions",label:"Dimensioni",sub:"Input e validazione dimensionale",children:[["dimensionDashboard","Dashboard"],["financialDimensions","Dimensioni"],["budgetVoices","Voci budget"],["itemGroups","Gruppi articolo"],["dimensionCombinations","Combinazioni"],["dimensionValidator","Validatore"],["dimensionLogs","Log"]]}); menuAdd({grp:"PLATFORM",id:"approvals",label:"Flussi approvativi",sub:"Designer operativo",children:[["approvalDesigner","Designer"],["approvalFlows","Flussi"],["approvalTasks","Task"],["approvalAudit","Audit"]]}); menuAdd({grp:"PLATFORM",id:"datahub",label:"BI / ETL",sub:"DataHub, BI, ETL designer",children:[["datahubDashboard","Overview"],["biSources","Data source"],["biDatasets","Dataset"],["savedBiQueries","Query BI"],["dashboards","Dashboard"],["biWidgets","Widget/KPI"],["etlDesigner","ETL Designer"],["etlRuns","Run log"]]}); menuAdd({grp:"ERP",id:"corejobs",label:"Job CORE",sub:"Catalogo, esecuzioni, schedulazioni",children:[["jobCatalog","Catalogo"],["jobRuns","Esecuzioni"],["jobSchedule","Schedulazioni"]]}); childAdd("finance",["financialIntegrations","Integrazioni finanziarie"]); childAdd("admin",["systemControl","Stato sistema"]); function graph(kind){ const g = kind==="etl" ? state.dsClean.etl : state.dsClean.workflow; const selected = g.nodes.find(n=>n.id===g.selected); const palette = kind==="etl" ? [["source","Sorgente"],["filter","Filtro"],["map","Mapping"],["transform","Trasformazione"],["validate","Validazione"],["lookup","Lookup"],["join","Join"],["dedupe","Deduplica"],["target","Destinazione"],["error","Errori"],["end","Fine"]] : [["start","Start"],["approval","Approvazione"],["condition","Condizione"],["amount_rule","Regola importo"],["budget_rule","Regola budget"],["parallel","Parallela"],["notify","Notifica"],["action","Azione"],["end","Fine"]]; const lines = g.edges.map(e=>{ const a=g.nodes.find(n=>n.id===e.from), b=g.nodes.find(n=>n.id===e.to); if(!a||!b) return ""; const x1=a.x+155,y1=a.y+40,x2=b.x,y2=b.y+40,mx=(x1+x2)/2,my=(y1+y2)/2; return `${esc(e.label||"")}`; }).join(""); const nodes = g.nodes.map(n=>`
${esc(n.type)}${esc(n.label)}
`).join(""); return `

Palette

${palette.map(([t,l])=>``).join("")}

${lines}${nodes}

Proprietà

${selected?propsPanel(kind,selected):'

Seleziona un nodo.

'}
`; } function propsPanel(kind,n){ const typeOpts = kind==="etl" ? ["source","filter","map","transform","validate","lookup","join","dedupe","target","error","end"] : ["start","approval","condition","amount_rule","budget_rule","parallel","notify","action","end"]; const propRows = Object.entries(n.props||{}).map(([k,v])=>`
`).join(""); return `${input("ID","node_id",n.id)}${input("Label","node_label",n.label)}${select("Tipo","node_type",n.type,typeOpts)}${input("X","node_x",n.x,"number")}${input("Y","node_y",n.y,"number")}

Proprietà specifiche

${propRows}`; } function getGraph(kind){ return kind==="etl" ? state.dsClean.etl : state.dsClean.workflow; } window.cleanSelect=function(kind,nodeId){ const g=getGraph(kind); if(g.connectFrom && g.connectFrom!==nodeId){ g.edges.push({id:id("edge"),from:g.connectFrom,to:nodeId,label:""}); g.connectFrom=null; } g.selected=nodeId; persist(); render(); }; window.cleanAddNode=function(kind,type,label){ const g=getGraph(kind); const nid=id(type); g.nodes.push({id:nid,type,label,x:100+(g.nodes.length*55)%850,y:250+(g.nodes.length*35)%240,props:{}}); g.selected=nid; persist(); render(); }; window.cleanApplyNode=function(kind,oldId){ const g=getGraph(kind), n=g.nodes.find(x=>x.id===oldId); if(!n) return; const values={}; document.querySelectorAll(".clean-props [data-key]").forEach(el=>values[el.dataset.key]=el.value); const newId=(values.node_id||oldId).trim(); if(newId!==oldId && !g.nodes.some(x=>x.id===newId)){ g.edges.forEach(e=>{ if(e.from===oldId)e.from=newId; if(e.to===oldId)e.to=newId; }); n.id=newId; g.selected=newId; } n.label=values.node_label||n.label; n.type=values.node_type||n.type; n.x=parseInt(values.node_x)||0; n.y=parseInt(values.node_y)||0; persist(); render(); }; window.cleanStartEdge=function(kind){ const g=getGraph(kind); if(!g.selected) return dsNotify("Seleziona il nodo sorgente."); g.connectFrom=g.selected; persist(); const m=document.getElementById(kind+"CleanMsg"); if(m)m.innerHTML="Ora clicca il nodo destinazione."; }; window.cleanEditEdge=function(kind){ const g=getGraph(kind); if(!g.edges.length)return dsNotify("Nessuna freccia."); const index=(parseInt(prompt("Freccia numero 1-"+g.edges.length,"1"))||1)-1; if(!g.edges[index])return; const label=prompt("Etichetta",g.edges[index].label||""); if(label!==null){g.edges[index].label=label; persist(); render();} }; window.cleanDeleteNode=function(kind){ const g=getGraph(kind); if(!g.selected)return; if(!confirm("Eliminare nodo e frecce collegate?"))return; g.nodes=g.nodes.filter(n=>n.id!==g.selected); g.edges=g.edges.filter(e=>e.from!==g.selected&&e.to!==g.selected); g.selected=null; persist(); render(); }; window.cleanSetProp=function(kind,nid,k,v){ const n=getGraph(kind).nodes.find(x=>x.id===nid); if(n){n.props=n.props||{}; n.props[k]=v; persist();} }; window.cleanRenameProp=function(kind,nid,oldK,newK){ const n=getGraph(kind).nodes.find(x=>x.id===nid); if(n&&newK&&oldK!==newK){n.props[newK]=n.props[oldK]; delete n.props[oldK]; persist(); render();} }; window.cleanDelProp=function(kind,nid,k){ const n=getGraph(kind).nodes.find(x=>x.id===nid); if(n){delete n.props[k]; persist(); render();} }; window.cleanAddProp=function(kind,nid){ const key=prompt("Nome proprietà"); if(!key)return; const n=getGraph(kind).nodes.find(x=>x.id===nid); if(n){n.props[key]=prompt("Valore","")||""; persist(); render();} }; window.cleanValidate=function(kind){ const g=getGraph(kind), errs=[]; const start=kind==="etl"?"source":"start", end=kind==="etl"?["target","end"]:["end"]; if(!g.nodes.some(n=>n.type===start)) errs.push("manca "+start); if(!g.nodes.some(n=>end.includes(n.type))) errs.push("manca fine/destinazione"); g.edges.forEach(e=>{if(!g.nodes.some(n=>n.id===e.from)||!g.nodes.some(n=>n.id===e.to)) errs.push("freccia rotta "+e.id);}); const m=document.getElementById(kind+"CleanMsg"); if(m)m.innerHTML=errs.length?`ERR: ${errs.join(", ")}`:`OK: grafo valido`; }; window.cleanSaveGraph=function(kind){ persist(); const endpoint=kind==="etl"?"/api/etl-flows/":"/api/approval-flows/"; const m=document.getElementById(kind+"CleanMsg"); if(m)m.innerHTML=`Salvato localmente. JSON pronto per ${endpoint}`; }; function pageApprovals(){ const s=state.sub||"approvalDesigner"; if(s==="approvalDesigner") return pageHead("Designer flussi approvativi","Operativo: niente popup coordinate, modifica dal pannello proprietà.")+graph("workflow"); if(s==="approvalFlows") return pageHead("Flussi approvativi","Flussi configurabili.")+table(["Codice","Nodi","Frecce","Azioni"],[[state.dsClean.workflow.active,state.dsClean.workflow.nodes.length,state.dsClean.workflow.edges.length,``]]); return pageHead("Task/Audit","Task e audit.")+`
Endpoint backend: /api/approval-tasks/
`; } function pageDatahub(){ const s=state.sub||"datahubDashboard", b=state.dsClean.bi; if(s==="etlDesigner") return pageHead("ETL Designer","Operativo: niente popup coordinate, proprietà nel pannello laterale.")+graph("etl"); if(s==="biSources") return pageHead("Data source","Origini dati modificabili.")+table(["Codice","Tipo","Host","Stato"],b.sources.map(x=>[esc(x.code),esc(x.type),esc(x.host),badge(x.status)])); if(s==="biDatasets") return pageHead("Dataset BI","Dataset modificabili.")+`
${table(["Codice","Nome","Sorgente","Query"],b.datasets.map((x,i)=>[esc(x.code),esc(x.name),esc(x.source),`${esc(x.query)}`]))}
`; if(s==="dashboards") return pageHead("Dashboard BI","Dashboard.")+table(["Codice","Nome","Widget"],b.dashboards.map(x=>[esc(x.code),esc(x.name),esc(x.widgets)])); if(s==="biWidgets") return pageHead("Widget/KPI","Widget.")+table(["Codice","Titolo","Tipo","Dataset"],b.widgets.map(x=>[esc(x.code),esc(x.title),esc(x.type),esc(x.dataset)])); if(s==="etlRuns") return pageHead("Run log","Run log.")+table(["Ora","Tipo","Oggetto","Stato"],b.logs.map(x=>[esc(x.date),esc(x.type),esc(x.object),badge(x.status)])); return pageHead("BI / ETL","Overview.")+`

${b.sources.length}

Data source

${b.datasets.length}

Dataset

${b.dashboards.length}

Dashboard

${state.dsClean.etl.nodes.length}

Nodi ETL

`; } window.cleanAddDataset=function(){ const code=prompt("Codice dataset"); if(!code)return; state.dsClean.bi.datasets.push({code,name:prompt("Nome",code)||code,source:"datasphere-core-db",query:prompt("SQL","select 1")||"select 1",refresh:"manual"}); persist(); render(); }; function pageDimensions(){ const s=state.sub||"dimensionDashboard", d=state.dsClean.dim; if(s==="financialDimensions") return pageHead("Dimensioni finanziarie","CRUD locale.")+`
${table(["Codice","Nome","Attiva","Azioni"],d.dimensions.map((x,i)=>[esc(x.code),esc(x.name),x.active?"Sì":"No",``]))}
`; if(s==="budgetVoices") return pageHead("Voci budget","CRUD locale.")+`
${table(["Codice","Nome","CAPEX","OPEX"],d.budgets.map((x,i)=>[esc(x.code),esc(x.name),x.capex?"Sì":"No",x.opex?"Sì":"No"]))}
`; if(s==="itemGroups") return pageHead("Gruppi articolo","CRUD locale.")+`
${table(["Codice","Nome","Cespite"],d.groups.map(x=>[esc(x.code),esc(x.name),x.asset?"Sì":"No"]))}
`; if(s==="dimensionCombinations") return pageHead("Combinazioni dimensionali","CRUD e validazione.")+`
${table(["Codice","Dimensione","Budget","Gruppo","Area","Stato","Azioni"],d.combos.map((c,i)=>[esc(c.code),esc(c.dimension),esc(c.budget),esc(c.group),esc(c.area),badge(c.status),``]))}
`; if(s==="dimensionValidator") return pageHead("Validatore dimensionale","Technical non vincolato.")+`
${select("Area","clean_area","purchase",["purchase","sales","finance","asset","inventory_valuation","technical"])}${input("Dimensione","clean_dim","CPIR21")}${input("Budget","clean_budget","GEN-BDG")}${input("Gruppo","clean_group","GEN-ITEM")}
`; if(s==="dimensionLogs") return pageHead("Log validazione","Log.")+table(["Ora","Area","Payload","Esito","Messaggio"],d.logs.map(l=>[esc(l.date),esc(l.area),esc(l.payload),l.ok?"OK":"ERR",esc(l.message)])); return pageHead("Dimensioni","Setup centrale.")+`

${d.dimensions.length}

Dimensioni

${d.budgets.length}

Budget

${d.groups.length}

Gruppi

${d.combos.length}

Combinazioni

`; } window.cleanDimAdd=function(){ const code=prompt("Codice"); if(!code)return; state.dsClean.dim.dimensions.push({code,name:prompt("Nome",code)||code,active:true}); persist(); render(); }; window.cleanDimEdit=function(i){ const x=state.dsClean.dim.dimensions[i]; x.name=prompt("Nome",x.name)||x.name; x.active=confirm("Attiva?"); persist(); render(); }; window.cleanDimDel=function(i){ if(confirm("Eliminare?")){state.dsClean.dim.dimensions.splice(i,1); persist(); render();} }; window.cleanBudgetAdd=function(){ const code=prompt("Codice"); if(!code)return; state.dsClean.dim.budgets.push({code,name:prompt("Nome",code)||code,capex:confirm("CAPEX?"),opex:true}); persist(); render(); }; window.cleanGroupAdd=function(){ const code=prompt("Codice"); if(!code)return; state.dsClean.dim.groups.push({code,name:prompt("Nome",code)||code,asset:confirm("Capitalizzabile?")}); persist(); render(); }; window.cleanComboAdd=function(){ const code=prompt("Codice"); if(!code)return; state.dsClean.dim.combos.push({code,dimension:prompt("Dimensione","CPIR21")||"",budget:prompt("Budget","GEN-BDG")||"",group:prompt("Gruppo","GEN-ITEM")||"",area:prompt("Area","purchase")||"purchase",status:"enabled",priority:100}); persist(); render(); }; window.cleanComboEdit=function(i){ const c=state.dsClean.dim.combos[i]; c.dimension=prompt("Dimensione",c.dimension)||c.dimension; c.budget=prompt("Budget",c.budget)||c.budget; c.area=prompt("Area",c.area)||c.area; persist(); render(); }; window.cleanComboDel=function(i){ if(confirm("Eliminare?")){state.dsClean.dim.combos.splice(i,1); persist(); render();} }; window.cleanValidateDim=function(){ const area=document.querySelector('[data-key="clean_area"]').value, dim=document.querySelector('[data-key="clean_dim"]').value, budget=document.querySelector('[data-key="clean_budget"]').value, group=document.querySelector('[data-key="clean_group"]').value; let ok=true,msg="Combinazione valida."; if(area==="technical") msg="Processo tecnico: dimensioni non vincolanti."; else { ok=state.dsClean.dim.combos.some(c=>c.area===area&&c.dimension===dim&&c.budget===budget&&c.group===group&&c.status==="enabled"); if(!ok) msg=`Voce budget ${budget} non ammessa sulla dimensione ${dim} per area ${area}.`; } state.dsClean.dim.logs.unshift({date:new Date().toLocaleString(),area,payload:`${dim}/${budget}/${group}`,ok,message:msg}); persist(); document.getElementById("cleanDimOut").innerHTML=ok?`${esc(msg)}`:`${esc(msg)}`; }; function pageDatabase(){ const s=state.sub||"dbOverview", db=state.dsClean.db; if(s==="dbTables") return pageHead("Tabelle database / nomi ERP","Mapping modificabile.")+`
${table(["Tabella","Nome ERP","Modulo","Descrizione","Azioni"],db.maps.map((m,i)=>[`${esc(m.table)}`,esc(m.erp),esc(m.module),esc(m.desc),``]))}
`; if(s==="sqlConsole") return pageHead("SQL Console","Esecuzione SQL auditabile.")+`

`; if(s==="savedQueries") return pageHead("Query salvate","CRUD query.")+`
${table(["Nome","SQL","Azioni"],db.queries.map((q,i)=>[esc(q.name),`${esc(q.sql)}`,``]))}
`; return pageHead("Database Console","GUI DATABASE distinta.")+`

DataSphere

DATABASE

${db.maps.length}

Mapping

${db.queries.length}

Query salvate

${db.logs.length}

Log SQL

`; } window.cleanDbMapAdd=function(){ const table=prompt("Tabella tecnica"); if(!table)return; state.dsClean.db.maps.push({table,erp:prompt("Nome ERP",table)||table,module:prompt("Modulo","ERP")||"ERP",desc:prompt("Descrizione","")||""}); persist(); render(); }; window.cleanDbMapEdit=function(i){ const m=state.dsClean.db.maps[i]; m.erp=prompt("Nome ERP",m.erp)||m.erp; m.module=prompt("Modulo",m.module)||m.module; m.desc=prompt("Descrizione",m.desc)||m.desc; persist(); render(); }; window.cleanDbMapDel=function(i){ if(confirm("Eliminare?")){state.dsClean.db.maps.splice(i,1); persist(); render();} }; window.cleanSqlRun=function(){ const sql=document.getElementById("cleanSql").value; state.dsClean.db.logs.unshift({date:new Date().toLocaleString(),sql,status:"ready"}); persist(); document.getElementById("cleanSqlOut").textContent=JSON.stringify({endpoint:"/api/database/sql/execute/",sql,read_only:sql.trim().toLowerCase().startsWith("select")},null,2); }; window.cleanSqlSave=function(){ const sql=document.getElementById("cleanSql").value, name=prompt("Nome query"); if(!name)return; state.dsClean.db.queries.push({name,sql}); persist(); render(); }; window.cleanSqlNew=function(){ const name=prompt("Nome"); if(!name)return; state.dsClean.db.queries.push({name,sql:prompt("SQL","select 1")||"select 1"}); persist(); render(); }; window.cleanSqlEdit=function(i){ const q=state.dsClean.db.queries[i]; q.name=prompt("Nome",q.name)||q.name; q.sql=prompt("SQL",q.sql)||q.sql; persist(); render(); }; window.cleanSqlDel=function(i){ if(confirm("Eliminare?")){state.dsClean.db.queries.splice(i,1); persist(); render();} }; window.cleanSqlLoad=function(i){ state.sub="sqlConsole"; render(); setTimeout(()=>document.getElementById("cleanSql").value=state.dsClean.db.queries[i].sql,50); }; function pageJobs(){ const s=state.sub||"jobCatalog", j=state.dsClean.jobs; if(s==="jobRuns") return pageHead("Esecuzioni Job","Log esecuzioni.")+table(["Ora","Job","Dry-run","Stato","Risultato"],j.runs.map(r=>[esc(r.date),esc(r.job),r.dry?"Sì":"No",badge(r.status),esc(r.result)])); return pageHead("Catalogo Job CORE","Modifica, abilita/disabilita, dry-run, esegui.")+`
${table(["Codice","Nome","Area","Abilitato","Azioni"],j.jobs.map((x,i)=>[`${esc(x.code)}`,esc(x.name),esc(x.area),x.enabled?"Sì":"No",``]))}
`; } window.cleanJobAdd=function(){ const code=prompt("Codice job"); if(!code)return; state.dsClean.jobs.jobs.push({code,name:prompt("Nome",code)||code,area:prompt("Area","erp")||"erp",enabled:true}); persist(); render(); }; window.cleanJobEdit=function(i){ const j=state.dsClean.jobs.jobs[i]; j.name=prompt("Nome",j.name)||j.name; j.area=prompt("Area",j.area)||j.area; persist(); render(); }; window.cleanJobToggle=function(i){ state.dsClean.jobs.jobs[i].enabled=!state.dsClean.jobs.jobs[i].enabled; persist(); render(); }; window.cleanJobRun=function(i,dry){ const j=state.dsClean.jobs.jobs[i]; if(!j.enabled)return dsNotify("Job disabilitato."); state.dsClean.jobs.runs.unshift({date:new Date().toLocaleString(),job:j.code,dry,status:"success",result:"Handler backend previsto"}); persist(); render(); }; function pageFin(){ const r=state.dsClean.fin.rows; return pageHead("Integrazioni finanziarie","Ordine → consegna → fattura alimenta questa tabella.")+`
${table(["Data","Chain","Ordine","Consegna","Fattura","Evento","Importo","Stato","Azioni"],r.map((x,i)=>[esc(x.date),`${esc(x.chain)}`,esc(x.order),esc(x.delivery),esc(x.invoice),esc(x.event),esc(x.amount),badge(x.status),``]))}
`; } window.cleanFinSim=function(){ const chain="chain-"+Date.now(), order="SO-"+Math.floor(Math.random()*9000+1000), delivery="DLV-"+Math.floor(Math.random()*9000+1000), invoice="INV-"+Math.floor(Math.random()*9000+1000); ["sale_order_confirmed","delivery_completed","invoice_issued"].forEach(event=>state.dsClean.fin.rows.unshift({date:new Date().toLocaleString(),chain,order,delivery,invoice,event,amount:"1000.00",status:"pending"})); persist(); render(); }; window.cleanFinDel=function(i){ if(confirm("Eliminare?")){state.dsClean.fin.rows.splice(i,1); persist(); render();} }; const oldFinanceClean = typeof financePage==="function" ? financePage : null; if(oldFinanceClean){ financePage=function(){ if(state.sub==="financialIntegrations") return pageFin(); return oldFinanceClean(); }; } const previousPageHtml = pageHtml; pageHtml=function(){ ensure(); if(state.page==="approvals") return pageApprovals(); if(state.page==="datahub") return pageDatahub(); if(state.page==="dimensions") return pageDimensions(); if(state.page==="database") return pageDatabase(); if(state.page==="corejobs") return pageJobs(); return previousPageHtml(); }; const previousRender = renderApp; renderApp=function(){ ensure(); previousRender(); }; ensure(); console.log("DataSphere CORE GUI CLEAN loaded", VERSION); } catch(e) { console.error("DataSphere CORE GUI CLEAN failed", e); } })(); // ===== end DataSphere v4.9.14 CORE GUI CLEAN ===== // ===== DataSphere v4.9.15 CORE FOUNDATION REAL - API-backed UI ===== (function(){ try { const VERSION = "4.9.15-core-foundation-real"; function esc(v){ return String(v ?? "").replace(/[&<>"']/g, m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } function badge(v){ return `${esc(v)}`; } function table(headers, rows){ return `${headers.map(h=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(h)}
${c}
Nessun dato
`; } function ensure15(){ state.api15 ||= {cache:{}, errors:{}, selected:{}, forms:{}}; state.api15.cache.dimensionCombinations ||= []; state.api15.cache.financialDimensions ||= []; state.api15.cache.budgetVoices ||= []; state.api15.cache.itemGroups ||= []; state.api15.cache.approvalFlows ||= []; state.api15.cache.etlFlows ||= []; state.api15.cache.datasets ||= []; state.api15.cache.dbTables ||= []; state.api15.cache.jobs ||= []; state.api15.cache.finInt ||= []; state.api15.cache.sqlLogs ||= []; } async function api15(path, options={}){ const opts = Object.assign({headers:{"Content-Type":"application/json"}}, options); if(opts.body && typeof opts.body !== "string") opts.body = JSON.stringify(opts.body); const res = await fetch(path, opts); if(!res.ok) throw new Error(`${res.status} ${res.statusText}`); return await res.json(); } async function load15(key, path, normalizer){ ensure15(); try{ const data = await api15(path); const rows = normalizer ? normalizer(data) : (Array.isArray(data)?data:(data.results||data.items||[])); state.api15.cache[key] = rows; state.api15.errors[key] = ""; }catch(e){ state.api15.errors[key] = String(e.message||e); } if(typeof save==="function") save(); render(); } function errorHint(key){ const e = state.api15?.errors?.[key]; return e ? `
API non raggiunta per ${esc(key)}: ${esc(e)}. La pagina resta visibile ma i dati potrebbero essere cache/locali.
` : ""; } function apiButtons(key,path,label){ return ` ${esc(label||path)}`; } window.api15Load=function(key,path){ load15(key,path); }; function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } function addChild(id,c){ const m=menus.find(x=>x.id===id); if(m && !m.children.some(y=>y[0]===c[0])) m.children.push(c); } addMenu({grp:"PLATFORM",id:"apihealth",label:"API / Verifiche",sub:"Health, seed, smoke test",children:[["apiOverview","Overview"],["apiSeeds","Seed"],["apiSmoke","Smoke"]]}); addMenu({grp:"PLATFORM",id:"database",label:"Database",sub:"Console DB, SQL, tabelle ERP",children:[["dbOverview","Overview"],["dbTables","Tabelle ERP"],["sqlConsole","SQL Console"],["savedQueries","Query salvate"],["dbConnections","Connessioni"],["dbSync","Sync/Backup"]]}); addMenu({grp:"PLATFORM",id:"dimensions",label:"Dimensioni",sub:"API CRUD dimensionale",children:[["dimensionDashboard","Dashboard"],["financialDimensions","Dimensioni"],["budgetVoices","Voci budget"],["itemGroups","Gruppi articolo"],["dimensionCombinations","Combinazioni"],["dimensionValidator","Validatore"],["dimensionLogs","Log"]]}); addMenu({grp:"PLATFORM",id:"approvals",label:"Flussi approvativi",sub:"API + Designer",children:[["approvalDesigner","Designer"],["approvalFlows","Flussi"],["approvalTasks","Task"],["approvalAudit","Audit"]]}); addMenu({grp:"PLATFORM",id:"datahub",label:"BI / ETL",sub:"API DataHub, BI, ETL",children:[["datahubDashboard","Overview"],["biSources","Data source"],["biDatasets","Dataset"],["savedBiQueries","Query BI"],["dashboards","Dashboard"],["biWidgets","Widget/KPI"],["etlDesigner","ETL Designer"],["etlRuns","Run log"]]}); addMenu({grp:"ERP",id:"corejobs",label:"Job CORE",sub:"Catalogo persistente",children:[["jobCatalog","Catalogo"],["jobRuns","Esecuzioni"],["jobSchedule","Schedulazioni"]]}); addChild("finance",["financialIntegrations","Integrazioni finanziarie"]); window.api15SeedAll=async function(){ const calls=[ ["/api/security/seed/","security"], ["/api/core-code/seed/","core/code"], ["/api/dimension-combinations/seed/","dimensions"], ["/api/approval-flows/seed/","approvals"], ["/api/database-connections/seed/","datahub"], ["/api/erp-table-maps/seed/","database maps"], ["/api/core-job-definitions/seed/","jobs"] ]; const out=[]; for(const [url,label] of calls){ try{ await api15(url,{method:"POST",body:{}}); out.push("OK "+label); } catch(e){ out.push("ERR "+label+": "+e.message); } } dsNotify(out.join("\n")); }; function pageApi(){ const sub=state.sub||"apiOverview"; if(sub==="apiSeeds") return pageHead("Seed CORE","Esegue seed backend sui moduli CORE.")+`

Usa endpoint seed esposti dal backend.

`; if(sub==="apiSmoke") return pageHead("Smoke test","Comandi runtime da terminale.")+`
docker compose run --rm backend python manage.py seed_core_foundation_real
docker compose run --rm backend python manage.py smoke_core_foundation_real
docker compose run --rm backend python manage.py check
`; return pageHead("API / Verifiche","Punto di controllo build 4.9.15.")+`

Backend

/api/health/

Seed

Seed via API e manage.py

Persistenza

Le pagine principali caricano da API quando disponibili.

`; } function pageDimensions15(){ const sub=state.sub||"dimensionDashboard", c=state.api15.cache; if(sub==="financialDimensions") return pageHead("Dimensioni finanziarie","Caricamento e CRUD via API.")+`
${apiButtons("financialDimensions","/api/financial-dimensions/")} ${errorHint("financialDimensions")}${table(["ID","Codice","Nome","Attiva"],c.financialDimensions.map(x=>[esc(x.id),esc(x.code),esc(x.name),x.is_active?"Sì":"No"]))}
`; if(sub==="budgetVoices") return pageHead("Voci budget","Caricamento via API.")+`
${apiButtons("budgetVoices","/api/budget-voices/")} ${errorHint("budgetVoices")}${table(["ID","Codice","Nome","CAPEX","OPEX"],c.budgetVoices.map(x=>[esc(x.id),esc(x.code),esc(x.name),x.is_capex?"Sì":"No",x.is_opex?"Sì":"No"]))}
`; if(sub==="itemGroups") return pageHead("Gruppi articolo","Caricamento via API.")+`
${apiButtons("itemGroups","/api/item-groups/")} ${errorHint("itemGroups")}${table(["ID","Codice","Nome","Cespite"],c.itemGroups.map(x=>[esc(x.id),esc(x.code),esc(x.name),x.is_asset_group?"Sì":"No"]))}
`; if(sub==="dimensionCombinations") return pageHead("Combinazioni dimensionali","Persistenti via API.")+`
${apiButtons("dimensionCombinations","/api/dimension-combinations/")} ${errorHint("dimensionCombinations")}${table(["ID","Codice","Area","Stato","Priorità"],c.dimensionCombinations.map(x=>[esc(x.id),esc(x.code),esc(x.process_area),badge(x.status),esc(x.priority)]))}
`; if(sub==="dimensionValidator") return pageHead("Validatore dimensionale","Chiama /api/dimension-combinations/validate_payload/.")+`
`; return pageHead("Dimensioni","Dashboard API.")+`

${c.financialDimensions.length}

Dimensioni

${c.budgetVoices.length}

Voci budget

${c.itemGroups.length}

Gruppi

${c.dimensionCombinations.length}

Combinazioni

`; } window.api15CreateCombo=async function(){ const code=prompt("Codice combinazione"); if(!code)return; try{ await api15("/api/dimension-combinations/",{method:"POST",body:{code,process_area:prompt("Area","purchase")||"purchase",status:"enabled",priority:100}}); await load15("dimensionCombinations","/api/dimension-combinations/"); } catch(e){ dsNotify("Errore API: "+e.message); } }; window.api15ValidateDimension=async function(){ const body={process_area:document.getElementById("v_area").value,payload:{dimension_code:document.getElementById("v_dim").value,budget_voice_code:document.getElementById("v_budget").value,item_group_code:document.getElementById("v_group").value}}; try{ const r=await api15("/api/dimension-combinations/validate_payload/",{method:"POST",body}); document.getElementById("v_out").textContent=JSON.stringify(r,null,2); } catch(e){ document.getElementById("v_out").textContent="ERR "+e.message; } }; function graphPanel(kind){ const endpoint = kind==="etl" ? "/api/etl-flows/" : "/api/approval-flows/"; return `

${kind==="etl"?"ETL":"Workflow"} API designer

Questa build carica/salva i flussi come record backend. Il canvas interattivo della 4.9.14 resta disponibile come fallback locale, ma la priorità è la persistenza API.

${errorHint(kind==="etl"?"etlFlows":"approvalFlows")}
`; } function pageApprovals15(){ const sub=state.sub||"approvalDesigner", c=state.api15.cache; if(sub==="approvalFlows") return pageHead("Flussi approvativi","Persistenti via API.")+`
${apiButtons("approvalFlows","/api/approval-flows/")}${errorHint("approvalFlows")}${table(["ID","Codice","Nome","Target","Stato"],c.approvalFlows.map(x=>[esc(x.id),esc(x.code),esc(x.name),esc(x.target_type),badge(x.status)]))}
`; return pageHead("Designer flussi approvativi","Designer con persistenza API.")+graphPanel("workflow"); } function pageDatahub15(){ const sub=state.sub||"datahubDashboard", c=state.api15.cache; if(sub==="etlDesigner") return pageHead("ETL Designer","Designer con persistenza API.")+graphPanel("etl"); if(sub==="biDatasets") return pageHead("Dataset BI","Persistenti via API.")+`
${apiButtons("datasets","/api/datasets/")}${errorHint("datasets")}${table(["ID","Codice","Nome","Query"],c.datasets.map(x=>[esc(x.id),esc(x.code),esc(x.name),`${esc(x.query)}`]))}
`; if(sub==="etlRuns") return pageHead("Run log","Endpoint /api/data-run-logs/.")+`
${apiButtons("dataRunLogs","/api/data-run-logs/")}
`; return pageHead("BI / ETL","DataHub API overview.")+`

${c.datasets.length}

Dataset API

${c.etlFlows.length}

Flussi ETL API

Persistente

Caricamento backend

`; } window.api15CreateFlow=async function(kind){ const endpoint=kind==="etl"?"/api/etl-flows/":"/api/approval-flows/"; const code=prompt("Codice flusso"); if(!code)return; const body=kind==="etl"?{code,name:prompt("Nome",code)||code,status:"enabled",is_enabled:true,graph:{nodes:[],edges:[]}}:{code,name:prompt("Nome",code)||code,target_type:prompt("Target","purchase_request")||"purchase_request",status:"enabled",is_enabled:true,graph:{nodes:[],edges:[]}}; try{ await api15(endpoint,{method:"POST",body}); await load15(kind==="etl"?"etlFlows":"approvalFlows",endpoint); } catch(e){ dsNotify("Errore API: "+e.message); } }; window.api15CreateDataset=async function(){ const code=prompt("Codice dataset"); if(!code)return; try{ await api15("/api/datasets/",{method:"POST",body:{code,name:prompt("Nome",code)||code,query:prompt("SQL","select 1 as ok")||"select 1 as ok",refresh_mode:"manual",is_enabled:true}}); await load15("datasets","/api/datasets/"); } catch(e){ dsNotify("Errore API: "+e.message); } }; function pageDatabase15(){ const sub=state.sub||"dbOverview", c=state.api15.cache; if(sub==="dbTables") return pageHead("Tabelle database / nomi ERP","Caricate da backend.")+`
${errorHint("dbTables")}${table(["Tabella tecnica","Nome ERP","Modulo","Mappata"],c.dbTables.map(x=>[`${esc(x.table_name||x.table)}`,esc(x.erp_name||x.erp||""),esc(x.module||""),x.mapped===false?"No":"Sì"]))}
`; if(sub==="sqlConsole") return pageHead("SQL Console","Esegue endpoint backend /api/database/sql/execute/.")+`

`; return pageHead("Database Console","Console DB backend-first.")+`

DataSphere

DATABASE

${c.dbTables.length}

Tabelle/API

SQL

Endpoint reale

Audit

SqlExecutionLog

`; } window.api15LoadTables=async function(){ try{ const r=await api15("/api/erp-table-maps/tables/"); state.api15.cache.dbTables=r.tables||[]; state.api15.errors.dbTables=""; persist(); render(); } catch(e){ state.api15.errors.dbTables=e.message; persist(); render(); } }; window.api15RunSql=async function(){ const sql=document.getElementById("api15_sql").value; try{ const r=await api15("/api/database/sql/execute/",{method:"POST",body:{sql,limit:500}}); document.getElementById("api15_sql_out").textContent=JSON.stringify(r,null,2); } catch(e){ document.getElementById("api15_sql_out").textContent="ERR "+e.message; } }; function pageJobs15(){ const sub=state.sub||"jobCatalog", c=state.api15.cache; if(sub==="jobRuns") return pageHead("Esecuzioni Job","CoreJobRun da API.")+`
${apiButtons("coreJobRuns","/api/core-job-runs/")}
`; return pageHead("Catalogo Job CORE","Persistente via API.")+`
${apiButtons("jobs","/api/core-job-definitions/")}${errorHint("jobs")}${table(["ID","Codice","Nome","Area","Stato"],c.jobs.map(x=>[esc(x.id),`${esc(x.code)}`,esc(x.name),esc(x.process_area),badge(x.status)]))}
`; } window.api15SeedJobs=async function(){ try{ await api15("/api/core-job-definitions/seed/",{method:"POST",body:{}}); await load15("jobs","/api/core-job-definitions/"); } catch(e){ dsNotify(e.message); } }; function pageFin15(){ const c=state.api15.cache; return pageHead("Integrazioni finanziarie","Persistenti via API. Ordine → consegna → fattura alimenta FinancialIntegrationEntry.")+`
${errorHint("finInt")}${table(["ID","Processo","Oggetto","Evento","Importo","Stato"],c.finInt.map(x=>[esc(x.id),esc(x.source_process),esc(x.source_object_type)+":"+esc(x.source_object_id),esc(x.event),esc(x.amount),badge(x.status)]))}
`; } window.api15RecordSalesFlow=async function(){ const body={sale_order_id:prompt("Ordine","SO-1001")||"SO-1001",delivery_id:prompt("Consegna","DLV-1001")||"DLV-1001",invoice_id:prompt("Fattura","INV-1001")||"INV-1001",amount:prompt("Importo","1000.00")||"1000.00"}; try{ await api15("/api/financial-integration-entries/record_sales_flow/",{method:"POST",body}); await load15("finInt","/api/financial-integration-entries/"); } catch(e){ dsNotify("Errore API: "+e.message); } }; const oldFinance15 = typeof financePage==="function" ? financePage : null; if(oldFinance15){ financePage=function(){ if(state.sub==="financialIntegrations") return pageFin15(); return oldFinance15(); }; } const oldPage15 = pageHtml; pageHtml=function(){ ensure15(); if(state.page==="apihealth") return pageApi(); if(state.page==="dimensions") return pageDimensions15(); if(state.page==="approvals") return pageApprovals15(); if(state.page==="datahub") return pageDatahub15(); if(state.page==="database") return pageDatabase15(); if(state.page==="corejobs") return pageJobs15(); return oldPage15(); }; const oldRender15=renderApp; renderApp=function(){ ensure15(); oldRender15(); }; ensure15(); console.log("DataSphere CORE FOUNDATION REAL loaded", VERSION); } catch(e) { console.error("DataSphere CORE FOUNDATION REAL failed", e); } })(); // ===== end DataSphere v4.9.15 CORE FOUNDATION REAL ===== // ===== DataSphere v4.9.16 CORE WORKSPACE LOCALE ===== (function(){ try { const VERSION="4.9.16-core-workspace-locale"; function esc(v){ return String(v ?? "").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } function saveState(){ if(typeof save==="function") save(); } function ensureWorkspace(){ state.workspace16 ||= { layer:"core", language:"it", environment:"dev", translations:{ it:{core:"ERP CORE",code:"ERP CODE",database:"DATABASE",workspace:"Workspace",language:"Lingua"}, en:{core:"ERP CORE",code:"ERP CODE",database:"DATABASE",workspace:"Workspace",language:"Language"}, fr:{core:"ERP CORE",code:"ERP CODE",database:"DATABASE",workspace:"Espace de travail",language:"Langue"}, de:{core:"ERP CORE",code:"ERP CODE",database:"DATENBANK",workspace:"Arbeitsbereich",language:"Sprache"}, es:{core:"ERP CORE",code:"ERP CODE",database:"BASE DE DATOS",workspace:"Espacio de trabajo",language:"Idioma"} }, lastApiStatus:"" }; } function t(k){ ensureWorkspace(); const lang=state.workspace16.language||"it"; return (state.workspace16.translations[lang]||state.workspace16.translations.it)[k]||k; } async function wsApi(path,body){ const res=await fetch(path,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body||{})}); if(!res.ok) throw new Error(res.status+" "+res.statusText); return await res.json(); } async function wsLoad(){ try{ const res=await fetch("/api/workspace/me/"); if(res.ok){ const data=await res.json(); if(data.preference){ state.workspace16.layer=data.preference.current_layer||state.workspace16.layer; state.workspace16.language=data.preference.language||state.workspace16.language; state.workspace16.environment=data.preference.environment_code||state.workspace16.environment; state.workspace16.lastApiStatus="Workspace caricato da backend."; saveState(); render(); } } }catch(e){ state.workspace16.lastApiStatus="Workspace locale: "+e.message; saveState(); } } window.ws16Set=function(layer){ ensureWorkspace(); state.workspace16.layer=layer; if(layer==="database"){ state.page="database"; state.sub="dbOverview"; } if(layer==="core" && state.page==="database"){ state.page="dashboard"; state.sub=null; } if(layer==="code"){ state.page="corecode"; state.sub="developerSandbox"; } saveState(); wsApi("/api/workspace/set/",{current_layer:layer,language:state.workspace16.language,environment_code:state.workspace16.environment}).catch(e=>{state.workspace16.lastApiStatus="Salvataggio backend fallito: "+e.message; saveState();}); render(); }; window.ws16Lang=function(lang){ ensureWorkspace(); state.workspace16.language=lang; saveState(); wsApi("/api/workspace/set/",{current_layer:state.workspace16.layer,language:lang,environment_code:state.workspace16.environment}).catch(e=>{state.workspace16.lastApiStatus="Salvataggio lingua backend fallito: "+e.message; saveState();}); render(); }; window.ws16Env=function(env){ ensureWorkspace(); state.workspace16.environment=env; saveState(); wsApi("/api/workspace/set/",{current_layer:state.workspace16.layer,language:state.workspace16.language,environment_code:env}).catch(e=>{state.workspace16.lastApiStatus="Salvataggio ambiente backend fallito: "+e.message; saveState();}); render(); }; window.ws16Seed=function(){ wsApi("/api/workspace/seed/",{}).then((j)=>{dsNotify("Workspace/lingue seed completato: "+JSON.stringify(j.details||j)); location.reload();}).catch(e=>dsNotify("Errore seed workspace/lingue: "+e.message)); }; function switcher(){ ensureWorkspace(); const w=state.workspace16; return `
DataSphere ${esc(t(w.layer))} Ambiente ${esc(t("workspace"))} ${esc(t("language"))}
${w.lastApiStatus?`
${esc(w.lastApiStatus)}
`:""}
`; } function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } addMenu({grp:"PLATFORM",id:"workspace",label:"Workspace",sub:"CORE/CODE/DATABASE, lingua e ambiente",children:[["workspacePrefs","Preferenze"],["translations","Traduzioni"]]}); function pageWorkspace(){ ensureWorkspace(); if(state.sub==="translations"){ const rows=[]; Object.entries(state.workspace16.translations).forEach(([lang,vals])=>Object.entries(vals).forEach(([k,v])=>rows.push([lang,k,v]))); return pageHead("Lingue e traduzioni","Traduzioni locali italiano/inglese/francese/tedesco/spagnolo.")+switcher()+`
${rows.map(r=>``).join("")}
LinguaChiaveValore
${esc(r[0])}${esc(r[1])}${esc(r[2])}

Backend: /api/local-translations/

`; } return pageHead("Workspace utente","Passaggio operativo tra CORE, CODE e DATABASE, lingua locale e ambiente.")+switcher()+`

CORE

ERP base e processi standard.

CODE

Layer sviluppatori, sandbox e personalizzazioni.

DATABASE

Console DB, SQL, mapping tabelle.

Endpoint backend: /api/workspace/me/, /api/workspace/set/, /api/workspace/seed/

`; } const oldPage16=pageHtml; pageHtml=function(){ ensureWorkspace(); const base = state.page==="workspace" ? pageWorkspace() : oldPage16(); return switcher()+base; }; const oldRender16=renderApp; renderApp=function(){ ensureWorkspace(); oldRender16(); }; ensureWorkspace(); setTimeout(wsLoad, 800); console.log("DataSphere CORE WORKSPACE LOCALE loaded", VERSION); } catch(e){ console.error("DataSphere CORE WORKSPACE LOCALE failed", e); } })(); // ===== end DataSphere v4.9.16 CORE WORKSPACE LOCALE ===== // ===== DataSphere v4.9.18 CORE COMPLETION ===== (function(){ try { const VERSION="4.9.18-core-completion"; function esc(v){ return String(v ?? "").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } function table(h,rows){ return `${h.map(x=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(x)}
${c}
Nessun dato
`; } async function api(path, body=null, method=null){ const opts={headers:{"Content-Type":"application/json"}}; if(body!==null){ opts.method=method||"POST"; opts.body=JSON.stringify(body); } const res=await fetch(path,opts); if(!res.ok) throw new Error(res.status+" "+res.statusText); return await res.json(); } function ensure(){ state.comp18 ||= {cache:{}, last:""}; } function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } addMenu({grp:"ERP",id:"completion",label:"Completamento ERP",sub:"Finance, IVA, budget, plugin, pagamenti, SDI",children:[ ["completionDashboard","Dashboard"], ["billingFrequencies","Periodicità fatturazione"], ["plugins","Plugin CODE/CORE"], ["postInvoice","Post invoice"], ["vatRegisters","Registri IVA"], ["vatSettlement","Liquidazione IVA"], ["openItems","Partitario"], ["bankReconciliation","Riconciliazione banca"], ["budgetCompletion","Budget"], ["paymentGateways","Gateway pagamenti"], ["inventoryValuation","Magazzino valorizzato"], ["sdiCompletion","SDI"] ]}); window.comp18Load=async function(key,path){ ensure(); try{ const r=await api(path); state.comp18.cache[key]=r.results||r.items||r||[]; state.comp18.last="OK "+path; }catch(e){ state.comp18.last="ERR "+e.message; } save&&save(); render(); }; window.comp18Seed=async function(){ try{ const r=await api("/api/business/seed/",{}); dsNotify("Seed completion OK"); }catch(e){ dsNotify("ERR "+e.message); } }; window.comp18PostInvoice=async function(){ const body={invoice_id:document.getElementById("pi_id").value,invoice_number:document.getElementById("pi_no").value,invoice_type:document.getElementById("pi_type").value,party_code:document.getElementById("pi_party").value,taxable_amount:document.getElementById("pi_taxable").value,vat_rate:document.getElementById("pi_vat").value,dimension_code:document.getElementById("pi_dim").value,budget_voice_code:document.getElementById("pi_budget").value}; try{ const r=await api("/api/business/post-invoice-complete/",body); document.getElementById("pi_out").textContent=JSON.stringify(r,null,2); }catch(e){ document.getElementById("pi_out").textContent="ERR "+e.message; } }; window.comp18VatSettlement=async function(){ const p=document.getElementById("vat_period").value; try{ const r=await api("/api/business/vat-settlement/",{fiscal_period:p}); document.getElementById("vat_out").textContent=JSON.stringify(r,null,2); }catch(e){ document.getElementById("vat_out").textContent="ERR "+e.message; } }; window.comp18Reconcile=async function(){ const body={open_item_id:document.getElementById("rec_open").value||null,amount:document.getElementById("rec_amount").value,bank_account:document.getElementById("rec_bank").value,transaction_date:document.getElementById("rec_date").value}; try{ const r=await api("/api/business/reconcile-bank/",body); document.getElementById("rec_out").textContent=JSON.stringify(r,null,2); }catch(e){ document.getElementById("rec_out").textContent="ERR "+e.message; } }; window.comp18SalesFlow=async function(){ const body={sale_order_id:prompt("Ordine","SO-1001"),delivery_id:prompt("Consegna","DLV-1001"),invoice_id:prompt("Fattura","INV-1001"),event:"invoice_issued",amount:prompt("Importo","1000.00")}; try{ dsNotify(JSON.stringify(await api("/api/business/record-sales-flow/",body),null,2)); }catch(e){ dsNotify("ERR "+e.message); } }; window.comp18CreatePlugin=async function(){ const body={code:prompt("Codice plugin","code-plugin"),name:prompt("Nome plugin","CODE Plugin"),source_layer:"code",target_layer:"core",version:"0.1.0",manifest:{created_from:"gui"}}; try{ await api("/api/plugin-packages/",body); await comp18Load("plugins","/api/plugin-packages/"); }catch(e){ dsNotify("ERR "+e.message); } }; function pageCompletion(){ ensure(); const sub=state.sub||"completionDashboard", c=state.comp18.cache; if(sub==="billingFrequencies") return pageHead("Periodicità fatturazione","Setup usabile da contratti/ordini/fatture.")+`
${table(["Codice","Nome","Mesi","Attiva"],(c.freq||[]).map(x=>[esc(x.code),esc(x.name),esc(x.months_interval),x.is_active?"Sì":"No"]))}
`; if(sub==="plugins") return pageHead("Plugin CODE/CORE","Package plugin esportabile/importabile.")+`
${table(["ID","Codice","Nome","Layer","Versione","Stato"],(c.plugins||[]).map(x=>[x.id,esc(x.code),esc(x.name),esc(x.source_layer)+"→"+esc(x.target_layer),esc(x.version),esc(x.status)]))}
`; if(sub==="postInvoice") return pageHead("Post invoice completo","Crea giornale, righe IVA, registro IVA, partitario/scadenza.")+`
`; if(sub==="vatRegisters") return pageHead("Registri IVA","Vendite/acquisti alimentati da post_invoice.")+`
${table(["ID","Tipo","Periodo","Fattura","Imponibile","IVA"],(c.vatRegs||[]).map(x=>[x.id,esc(x.register_type),esc(x.fiscal_period),esc(x.invoice_number),esc(x.taxable_amount),esc(x.vat_amount)]))}
`; if(sub==="vatSettlement") return pageHead("Liquidazione IVA","Calcola periodo e crea scrittura contabile.")+`
`; if(sub==="openItems") return pageHead("Partitario open items","Partite aperte clienti/fornitori.")+`
${table(["ID","Tipo","Parte","Origine","Scadenza","Importo","Aperto","Stato"],(c.openItems||[]).map(x=>[x.id,esc(x.party_type),esc(x.party_code),esc(x.source_id),esc(x.due_date),esc(x.amount),esc(x.open_amount),esc(x.status)]))}
`; if(sub==="bankReconciliation") return pageHead("Riconciliazione bancaria","Crea registrazione contabile finale e chiude/parzializza open item.")+`
`; if(sub==="budgetCompletion") return pageHead("Budget","Budget, righe, revisioni massive.")+`

Endpoint persistenti disponibili.

`; if(sub==="paymentGateways") return pageHead("Gateway pagamenti","Nexi, PayPal, Satispay, Scalapay.")+`
${table(["ID","Provider","Nome","Abilitato"],(c.gateways||[]).map(x=>[x.id,esc(x.provider),esc(x.name),x.is_enabled?"Sì":"No"]))}
`; if(sub==="inventoryValuation") return pageHead("Magazzino valorizzato","Movimenti valorizzati con dimensioni/budget/contabilità.")+`
${table(["ID","Articolo","Gruppo","Qta","Valore","Dimensione","Budget"],(c.inv||[]).map(x=>[x.id,esc(x.item_code),esc(x.item_group_code),esc(x.quantity),esc(x.total_value),esc(x.dimension_code),esc(x.budget_voice_code)]))}
`; if(sub==="sdiCompletion") return pageHead("SDI / intermediario","Trasmissioni e notifiche SDI.")+`
${table(["ID","Fattura","Direzione","Intermediario","Stato"],(c.sdi||[]).map(x=>[x.id,esc(x.invoice_id),esc(x.direction),esc(x.intermediary),esc(x.status)]))}
`; return pageHead("Completamento ERP","Console operativa per i punti mancanti della matrice.")+`

Finance

post_invoice, IVA, partitario

Tesoreria

Riconciliazione con scritture

Plugin

CODE/CORE package

Gateway

Nexi/PayPal/Satispay/Scalapay

${esc(state.comp18.last||"")}

`; } const oldPage=pageHtml; pageHtml=function(){ if(state.page==="completion") return pageCompletion(); return oldPage(); }; console.log("DataSphere CORE COMPLETION loaded", VERSION); } catch(e){ console.error("DataSphere CORE COMPLETION failed", e); } })(); // ===== end DataSphere v4.9.18 CORE COMPLETION ===== // ===== DataSphere v4.9.19 CORE CODE RUNTIME ===== (function(){ try { const VERSION="4.9.19-core-code-runtime"; function esc(v){ return String(v ?? "").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } function table(h,rows){ return `${h.map(x=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(x)}
${c}
Nessun dato
`; } async function api(path, body=null, method=null){ return await window.dsApiFetch(path, body, method); } function ensure(){ state.code19 ||= {cache:{runtimes:[],artifacts:[],logs:[],promotions:[]},selected:null,last:""}; } function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } addMenu({grp:"CODE",id:"coderuntime",label:"CODE Runtime",sub:"Python/Java, plugin, promozione CORE",children:[["runtimeDashboard","Dashboard"],["runtimes","Runtime"],["artifacts","Artefatti"],["codeEditor","Editor"],["executionLogs","Log esecuzione"],["promotions","Promozioni CODE→CORE"]]}); window.code19Load=async function(key,path){ ensure(); try{ const r=await api(path); state.code19.cache[key]=r.results||r.items||r||[]; state.code19.last="OK "+path; }catch(e){ state.code19.last="ERR "+e.message; } save&&save(); render(); }; window.code19Seed=async function(){ try{ await api("/api/code-runtimes/seed/",{}); await code19Load("runtimes","/api/code-runtimes/"); await code19Load("artifacts","/api/code-artifacts/"); dsNotify("Runtime Python/Java creati"); }catch(e){ dsNotify("ERR "+e.message); } }; window.code19CreateArtifact=async function(){ const lang=document.getElementById("code_lang").value; const code=document.getElementById("code_code").value; const name=document.getElementById("code_name").value; const src=document.getElementById("code_src").value; const runtimes=state.code19.cache.runtimes||[]; const rt=runtimes.find(x=>x.language===lang); try{ await api("/api/code-artifacts/",{code,name,layer:"code",artifact_type:document.getElementById("code_type").value,runtime:rt?rt.id:null,language:lang,source_code:src,entrypoint:lang==="java"?"Main.main":"main",status:"draft",is_enabled:true}); await code19Load("artifacts","/api/code-artifacts/"); dsNotify("Artefatto creato"); }catch(e){ dsNotify("ERR "+e.message); } }; window.code19Validate=async function(id){ try{ dsNotify(JSON.stringify(await api(`/api/code-artifacts/${id}/validate/`,{}),null,2)); await code19Load("artifacts","/api/code-artifacts/"); }catch(e){ dsNotify("ERR "+e.message); } }; window.code19Run=async function(id){ try{ dsNotify(JSON.stringify(await api(`/api/code-artifacts/${id}/run/`,{payload:{from_gui:true}}),null,2)); await code19Load("logs","/api/code-execution-logs/"); }catch(e){ dsNotify("ERR "+e.message); } }; window.code19Promotion=async function(id){ try{ const reason=prompt("Motivo promozione CODE→CORE","Funzione stabilizzata"); if(!reason)return; await api("/api/code-promotion-requests/",{artifact:id,reason,from_layer:"code",to_layer:"core",status:"draft"}); await code19Load("promotions","/api/code-promotion-requests/"); }catch(e){ dsNotify("ERR "+e.message); } }; window.code19ApplyPromotion=async function(id){ try{ dsNotify(JSON.stringify(await api(`/api/code-promotion-requests/${id}/apply/`,{}),null,2)); await code19Load("promotions","/api/code-promotion-requests/"); await code19Load("artifacts","/api/code-artifacts/"); }catch(e){ dsNotify("ERR "+e.message); } }; function pageCodeRuntime(){ ensure(); const sub=state.sub||"runtimeDashboard", c=state.code19.cache; if(sub==="runtimes") return pageHead("Runtime Python/Java","Runtime ammessi per CODE e, se serve, CORE.")+`
${table(["ID","Codice","Lingua","Nome","CORE","CODE","Stato"],c.runtimes.map(x=>[x.id,esc(x.code),esc(x.language),esc(x.name),x.is_core_allowed?"Sì":"No",x.is_code_allowed?"Sì":"No",esc(x.status)]))}
`; if(sub==="artifacts") return pageHead("Artefatti CODE/CORE","Funzioni, job handler, workflow action, ETL transform in Python o Java.")+`
${table(["ID","Codice","Nome","Layer","Lingua","Tipo","Stato","Azioni"],c.artifacts.map(x=>[x.id,`${esc(x.code)}`,esc(x.name),esc(x.layer),esc(x.language),esc(x.artifact_type),esc(x.status),``]))}
`; if(sub==="codeEditor") return pageHead("Editor CODE Python/Java","Crea personalizzazioni in Python o Java dalla GUI.")+`

`; if(sub==="executionLogs") return pageHead("Log esecuzione","Output runtime Python/Java.")+`
${table(["ID","Artifact","Runtime","Stato","Durata ms","Stdout","Stderr"],c.logs.map(x=>[x.id,esc(x.artifact),esc(x.runtime),esc(x.status),esc(x.duration_ms),`
${esc(x.stdout||"")}
`,`
${esc(x.stderr||"")}
`]))}
`; if(sub==="promotions") return pageHead("Promozioni CODE→CORE","Valida e applica promozione di funzioni CODE a CORE.")+`
${table(["ID","Artifact","Da","A","Stato","Azioni"],c.promotions.map(x=>[x.id,esc(x.artifact),esc(x.from_layer),esc(x.to_layer),esc(x.status),``]))}
`; return pageHead("CODE Runtime","Python e Java per personalizzazioni CODE e funzioni CORE selettive.")+`

Python

Funzioni leggere, validatori, ETL transform.

Java

Funzioni strutturate, job handler, logiche riusabili.

CODE→CORE

Promozione controllata con validazione e rollback.

${esc(state.code19.last||"")}

`; } const oldPage=pageHtml; pageHtml=function(){ if(state.page==="coderuntime") return pageCodeRuntime(); return oldPage(); }; console.log("DataSphere CORE CODE RUNTIME loaded", VERSION); } catch(e){ console.error("DataSphere CORE CODE RUNTIME failed", e); } })(); // ===== end DataSphere v4.9.19 CORE CODE RUNTIME ===== // ===== DataSphere v4.9.19 FIX5 ETL JAVA DESIGNER ===== (function(){ try { const VERSION="4.9.19-fix5-etl-java-designer"; function esc(v){ return String(v ?? "").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } function table(h,rows){ return `${h.map(x=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(x)}
${c}
Nessun dato
`; } async function api(path, body=null, method=null){ return await window.dsApiFetch(path, body, method); } function ensure(){ state.etlJ5 ||= {catalog:[],pipelines:[],steps:[],hops:[],runs:[],last:""}; } function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } addMenu({grp:"DATA",id:"etljava",label:"ETL Java Designer",sub:"Designer stile Pentaho/Kettle integrato ERP",children:[["etlJavaDashboard","Dashboard"],["etlStepCatalog","Catalogo step"],["etlPipelines","Pipeline"],["etlGraph","Designer grafico"],["etlRunsJava","Run log"]]}); window.etlJ5Load=async function(key,path){ ensure(); try{ const r=await api(path); state.etlJ5[key]=r.results||r.items||r||[]; state.etlJ5.last="OK "+path; }catch(e){ state.etlJ5.last="ERR "+e.message; } save&&save(); render(); }; window.etlJ5Seed=async function(){ try{ await api("/api/etl-step-catalog/seed/",{}); await etlJ5Load("catalog","/api/etl-step-catalog/"); await etlJ5Load("pipelines","/api/etl-pipelines-java/"); dsNotify("ETL Java Designer seed OK"); }catch(e){ dsNotify("ERR "+e.message); } }; window.etlJ5CreatePipeline=async function(){ const body={code:prompt("Codice pipeline","etl-java-custom"),name:prompt("Nome","ETL Java Custom"),pipeline_type:"transformation",engine:prompt("Engine java/python/mixed/sql","java")||"java",source_connection_code:prompt("Database Hub sorgente","datasphere-core-db")||"",target_connection_code:prompt("Database Hub destinazione","datasphere-core-db")||"",status:"draft",is_enabled:true,graph:{nodes:[],hops:[]}}; try{ await api("/api/etl-pipelines-java/",body); await etlJ5Load("pipelines","/api/etl-pipelines-java/"); }catch(e){ dsNotify("ERR "+e.message); } }; window.etlJ5Validate=async function(id){ try{ dsNotify(JSON.stringify(await api(`/api/etl-pipelines-java/${id}/validate/`,{}),null,2)); await etlJ5Load("pipelines","/api/etl-pipelines-java/"); }catch(e){ dsNotify("ERR "+e.message); } }; window.etlJ5Run=async function(id){ try{ dsNotify(JSON.stringify(await api(`/api/etl-pipelines-java/${id}/run/`,{from_gui:true}),null,2)); await etlJ5Load("runs","/api/etl-pipeline-runs-java/"); }catch(e){ dsNotify("ERR "+e.message); } }; function page(){ ensure(); const sub=state.sub||"etlJavaDashboard"; if(sub==="etlStepCatalog") return pageHead("Catalogo step ETL","Step in stile Pentaho/Kettle: input, transform, output, custom Python/Java.")+`
${table(["Codice","Nome","Categoria","Runtime","Attivo"],state.etlJ5.catalog.map(x=>[esc(x.code),esc(x.name),esc(x.category),esc(x.runtime),x.is_enabled?"Sì":"No"]))}
`; if(sub==="etlPipelines") return pageHead("Pipeline ETL","Transformation/job eseguiti dentro ERP, usando Database Hub.")+`
${table(["ID","Codice","Nome","Tipo","Engine","Sorgente","Target","Stato","Azioni"],state.etlJ5.pipelines.map(x=>[x.id,`${esc(x.code)}`,esc(x.name),esc(x.pipeline_type),esc(x.engine),esc(x.source_connection_code),esc(x.target_connection_code),esc(x.status),``]))}
`; if(sub==="etlGraph") return pageHead("Designer grafico ETL Java","Canvas operativo base: la pipeline è salvata via API. Gli step possono essere Java, Python, SQL o nativi.")+`

Concetto: palette step → canvas → hop → proprietà step → validazione → run. Il runtime può usare Java per step intensivi e Python per trasformazioni rapide.

Fonti dati: Database Hub, quindi ogni pipeline usa connessioni già configurate nell'ERP.

`; if(sub==="etlRunsJava") return pageHead("Run log ETL Java","Log esecuzioni pipeline.")+`
${table(["ID","Pipeline","Stato","Durata","Output"],state.etlJ5.runs.map(x=>[x.id,esc(x.pipeline),esc(x.status),esc(x.duration_ms),`
${esc(JSON.stringify(x.output_payload||{}))}
`]))}
`; return pageHead("ETL Java Designer","Designer ETL stile Pentaho/Kettle integrato in DataSphere.")+`

Java

Step performanti e personalizzabili.

Python

Trasformazioni veloci e script custom.

Database Hub

Connessioni sorgente/target già configurate nell'ERP.

${esc(state.etlJ5.last||"")}

`; } const oldPage=pageHtml; pageHtml=function(){ if(state.page==="etljava") return page(); return oldPage(); }; console.log("DataSphere ETL JAVA DESIGNER loaded", VERSION); } catch(e){ console.error("DataSphere ETL JAVA DESIGNER failed", e); } })(); // ===== end DataSphere v4.9.19 FIX5 ETL JAVA DESIGNER ===== // ===== DataSphere v4.9.26 CSRF API CLIENT ===== (function(){ try { function dsCookie(name){ const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if(parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift()); return ""; } window.dsApiFetch = async function(path, body=null, method=null){ const headers = {"Content-Type":"application/json"}; const csrftoken = dsCookie("csrftoken"); if(csrftoken) headers["X-CSRFToken"] = csrftoken; const opts = {headers, credentials:"same-origin"}; if(body !== null){ opts.method = method || "POST"; opts.body = JSON.stringify(body); } const res = await fetch(path, opts); if(!res.ok){ let detail = ""; try { detail = await res.text(); } catch(e) {} throw new Error(res.status + " " + res.statusText + (detail ? " - " + detail.slice(0,180) : "")); } return await res.json(); }; console.log("DataSphere CSRF API CLIENT loaded"); } catch(e) { console.error("DataSphere CSRF API CLIENT failed", e); } })(); // ===== end DataSphere v4.9.26 CSRF API CLIENT ===== // ===== DataSphere v4.9.28 SOFTWARE LAYERS UI ===== (function(){ try{ function esc(v){ return String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } async function api(path, body=null, method=null){ return await window.dsApiFetch(path, body, method); } function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } addMenu({grp:"SISTEMA",id:"softwarelayers",label:"CORE / CODE Layers",sub:"Layer software separati",children:[["layersDashboard","Dashboard"],["layersModules","Moduli"],["layersExtensionPoints","Extension Points"],["layersPromotions","Promozioni"]]}); function ensureL28(){ state.layer28 ||= {layers:[],modules:[],eps:[],status:""}; } window.layer28Seed=async function(){ ensureL28(); try{ await api("/api/software-layers/seed/",{}); await layer28Load(); }catch(e){ state.layer28.status="ERR "+e.message; render(); } }; window.layer28Load=async function(){ ensureL28(); try{ const l=await fetch("/api/software-layers/"); const m=await fetch("/api/software-layer-modules/"); const e=await fetch("/api/software-extension-points/"); state.layer28.layers=(await l.json()).results||await l.json; state.layer28.modules=(await m.json()).results||[]; state.layer28.eps=(await e.json()).results||[]; state.layer28.status="Caricato"; }catch(e){ state.layer28.status="ERR "+e.message; } render(); }; function tbl(h,rows){ return `${h.map(x=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(x)}
${c}
Nessun dato
`; } function page(){ ensureL28(); const sub=state.sub||"layersDashboard"; if(sub==="layersModules") return pageHead("Moduli CORE / CODE","CORE è canonico e non removibile. CODE estende tramite extension point e promozione controllata.")+`
${tbl(["Layer","Codice","Nome","Tipo","Stato","Override CORE"],state.layer28.modules.map(x=>[esc(x.layer),esc(x.code),esc(x.name),esc(x.module_type),esc(x.status),x.can_override_core?"Sì":"No"]))}
`; if(sub==="layersExtensionPoints") return pageHead("Extension Points","Punti in cui CODE può estendere CORE senza modificarlo direttamente.")+`
${tbl(["Codice","Nome","Attivo"],state.layer28.eps.map(x=>[esc(x.code),esc(x.name),x.is_enabled?"Sì":"No"]))}
`; if(sub==="layersPromotions") return pageHead("Promozioni CODE → CORE","Una funzione CODE diventa CORE solo con validazione, approvazione e rollback plan.")+`

Flusso previsto: CODE draft → validazione tecnica → approvazione admin → merge controllato CORE → rollback plan.

`; return pageHead("CORE / CODE Layers","Separazione software reale: CORE base ERP, CODE personalizzazioni rimovibili.")+`

CORE

Base canonica: finance, operations, contratti, dimensioni, database, processi standard.

Non removibile. Non modificabile direttamente dal CODE.

CODE

Layer personalizzazioni: plugin, funzioni Python/Java, ETL custom, workflow custom.

Disabilitabile/rimovibile senza intaccare CORE.

${esc(state.layer28.status||"")}

`; } const oldPage=pageHtml; pageHtml=function(){ if(state.page==="softwarelayers") return page(); return oldPage(); }; console.log("DataSphere SOFTWARE LAYERS UI loaded"); }catch(e){ console.error("SOFTWARE LAYERS UI failed",e); } })(); // ===== end DataSphere v4.9.28 SOFTWARE LAYERS UI ===== // ===== DataSphere v4.9.30 SYSTEM FEATURES UI ===== (function(){ try{ function esc(v){ return String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } async function api(path, body=null, method=null){ return await window.dsApiFetch(path, body, method); } function addMenu(m){ if(!menus.some(x=>x.id===m.id)) menus.push(m); } addMenu({grp:"SVILUPPO",id:"systemfeatures",label:"System Features",sub:"Registry funzioni ERP CORE/CODE",children:[["sfDashboard","Dashboard"],["sfFeatures","Features"],["sfImplementations","Implementazioni"],["sfMenu","Menu dinamico"],["sfAudit","Audit finte/parziali"],["sfBackup","Backup/Restore"]]}); function ensureSF(){ state.sf30 ||= {features:[], impls:[], menu:[], audit:[], status:""}; } function table(h,rows){ return `${h.map(x=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc(x)}
${c}
Nessun dato
`; } window.sf30Load=async function(){ ensureSF(); try{ const f=await fetch("/api/system-features/"); const i=await fetch("/api/system-feature-implementations/"); const m=await fetch("/api/system-feature-menu-items/"); const a=await fetch("/api/system-feature-audit-results/"); state.sf30.features=(await f.json()).results||[]; state.sf30.impls=(await i.json()).results||[]; state.sf30.menu=(await m.json()).results||[]; state.sf30.audit=(await a.json()).results||[]; state.sf30.status="Caricato"; }catch(e){ state.sf30.status="ERR "+e.message; } render(); }; window.sf30Seed=async function(){ try{ await api("/api/system-features/seed/",{}); await sf30Load(); }catch(e){ dsNotify("ERR "+e.message); } }; window.sf30Audit=async function(){ try{ dsNotify(JSON.stringify(await api("/api/system-features/audit/",{}),null,2)); await sf30Load(); }catch(e){ dsNotify("ERR "+e.message); } }; window.sf30Run=async function(code){ try{ dsNotify(JSON.stringify(await api("/api/system-features/run/",{code:code,payload:{from_gui:true},prefer_code:true}),null,2)); }catch(e){ dsNotify("ERR "+e.message); } }; window.sf30Create=async function(){ const body={code:prompt("Codice System Feature","custom.new_feature"),name:prompt("Nome","Nuova Feature"),layer:prompt("Layer core/code","code")||"code",module_area:prompt("Area","custom")||"custom",feature_type:prompt("Tipo","menu_action")||"menu_action",status:"placeholder",is_enabled:true,is_menu_visible:true,is_api_enabled:false,is_schedulable:false}; try{ await api("/api/system-features/",body,"POST"); await sf30Load(); }catch(e){ dsNotify("ERR "+e.message); } }; function page(){ ensureSF(); const sub=state.sub||"sfDashboard"; if(sub==="sfFeatures") return pageHead("System Features","Nome ERP/funzionale assegnato a ogni sviluppo CORE o CODE.")+`
${table(["Codice","Nome","Layer","Area","Tipo","Stato","Menu","Run"],state.sf30.features.map(x=>[`${esc(x.code)}`,esc(x.name),esc(x.layer),esc(x.module_area),esc(x.feature_type),esc(x.status),x.is_menu_visible?"Sì":"No",``]))}
`; if(sub==="sfImplementations") return pageHead("Implementazioni","Codice tecnico Python/Java collegato alle System Feature.")+`
${table(["Feature","Layer","Runtime","Stato","Versione","Priorità"],state.sf30.impls.map(x=>[esc(x.feature),esc(x.layer),esc(x.language),esc(x.status),esc(x.version),esc(x.priority)]))}
`; if(sub==="sfMenu") return pageHead("Menu dinamico","Voci e rami menu collegati a System Feature reali.")+`
${table(["Label","Feature","Layer","Branch","Visibile","Route"],state.sf30.menu.map(x=>[esc(x.label),esc(x.feature),esc(x.layer),x.is_branch?"Sì":"No",x.visible?"Sì":"No",esc(x.route)]))}
`; if(sub==="sfAudit") return pageHead("Audit funzioni finte/parziali","Classificazione real/partial/mock/placeholder/broken.")+`
${table(["Feature","Stato","Backend","Impl","Menu","Permessi","Findings"],state.sf30.audit.map(x=>[esc(x.feature),esc(x.status),x.has_backend?"Sì":"No",x.has_implementation?"Sì":"No",x.has_menu?"Sì":"No",x.has_permissions?"Sì":"No",`
${esc(JSON.stringify(x.findings||[]))}
`]))}
`; if(sub==="sfBackup") return pageHead("Backup / Restore configurazioni","Export/import System Features e configurazione tecnica.")+`

Da shell:

scripts/backup_datasphere_configuration.sh\nscripts/restore_datasphere_configuration.sh backups/file.json

API: /api/system-features/export_config/ e /api/system-features/import_config/

`; return pageHead("System Feature Registry","Centro architetturale per CORE/CODE, Python/Java, menu, job, API, audit, backup.")+`

CORE

Funzioni ERP canoniche.

CODE

Personalizzazioni cliente sopra CORE.

Python / Java

Runtime scelto dallo sviluppatore per ogni implementazione.

${esc(state.sf30.status||"")}

`; } const oldPage=pageHtml; pageHtml=function(){ if(state.page==="systemfeatures") return page(); return oldPage(); }; console.log("DataSphere SYSTEM FEATURES UI loaded"); }catch(e){ console.error("SYSTEM FEATURES UI failed",e); } })(); // ===== end DataSphere v4.9.30 SYSTEM FEATURES UI ===== // ===== DataSphere v4.9.41 DASHBOARD + TOPBAR + FEATURE REALITY FIX ===== (function(){ try{ function esc31(v){ return String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); } window.val = window.val || function(id){ return document.getElementById(id)?.value || ""; }; window.itemOpts = window.itemOpts || function(v){ return (state.items||[]).map(x=>``).join(""); }; window.vatOpts = window.vatOpts || function(v){ return (state.vatCodes||state.taxCodes||[]).map(x=>``).join(""); }; window.dimOpts = window.dimOpts || function(v){ return (state.dimensions||[]).map(x=>``).join(""); }; function ensure31(){ state.ctx22 ||= {companies:[],company_code:state.company||"DATASPHERE",layer:"core",language:state.lang||"it",status:""}; state.sf31 ||= {runs:[],features:[],audit:[],status:""}; } function go31(page,sub=null){ state.page=page; state.sub=sub; save&&save(); renderApp(); } window.dsGo = go31; async function api31(path, body=null, method=null){ if(window.dsApiFetch) return await window.dsApiFetch(path, body, method); const opts={credentials:"same-origin",headers:{"Content-Type":"application/json"}}; if(body!==null){opts.method=method||"POST";opts.body=JSON.stringify(body);} const r=await fetch(path,opts); if(!r.ok) throw new Error(r.status+" "+r.statusText); return await r.json(); } window.ctx31Set=async function(){ ensure31(); const company=document.getElementById("companySel")?.value || state.company || state.ctx22.company_code || "DATASPHERE"; const layer=document.getElementById("layerSel")?.value || state.ctx22.layer || "core"; const language=document.getElementById("langSel")?.value || state.lang || "it"; state.company=company; state.lang=language; state.ctx22={...(state.ctx22||{}),company_code:company,layer,language,status:"salvataggio..."}; try{ await api31("/api/workspace/set/",{company_code:company,current_layer:layer,language}); state.ctx22.status="Contesto salvato"; }catch(e){ state.ctx22.status="Contesto locale salvato; backend: "+e.message; } save&&save(); renderApp(); }; window.ctx31Load=async function(){ ensure31(); try{ await api31("/api/workspace/csrf/"); const me=await fetch("/api/workspace/me/",{credentials:"same-origin"}); if(me.ok){ const d=await me.json(); if(d.preference){ state.ctx22.layer=d.preference.current_layer||state.ctx22.layer||"core"; state.ctx22.company_code=d.preference.current_company_code||d.preference.company_code||state.company||"DATASPHERE"; state.ctx22.language=d.preference.language||state.lang||"it"; } } const c=await fetch("/api/workspace/companies/",{credentials:"same-origin"}); if(c.ok){ const d=await c.json(); state.ctx22.companies=d.companies||[]; } state.ctx22.status="Contesto caricato"; }catch(e){ state.ctx22.status="Contesto locale; "+e.message; } save&&save(); }; window.sf31Load=async function(){ ensure31(); try{ const r=await fetch("/api/system-feature-run-logs/",{credentials:"same-origin"}); if(r.ok){ const d=await r.json(); state.sf31.runs=(d.results||d||[]).slice(0,12); } const f=await fetch("/api/system-features/",{credentials:"same-origin"}); if(f.ok){ const d=await f.json(); state.sf31.features=(d.results||d||[]).slice(0,12); } const a=await fetch("/api/system-feature-audit-results/",{credentials:"same-origin"}); if(a.ok){ const d=await a.json(); state.sf31.audit=(d.results||d||[]).slice(0,8); } state.sf31.status="Aggiornato"; }catch(e){ state.sf31.status="Backend non raggiungibile: "+e.message; } save&&save(); renderApp(); }; window.sf31SeedAndAudit=async function(){ try{ await api31("/api/system-features/seed/",{}); await api31("/api/system-features/audit/",{}); await sf31Load(); }catch(e){ dsNotify("Errore seed/audit System Features: "+e.message); } }; window.sf31Run=async function(code){ try{ const out=await api31("/api/system-features/run/",{code,payload:{from_dashboard:true,ts:new Date().toISOString()},prefer_code:true}); dsNotify(JSON.stringify(out,null,2)); await sf31Load(); }catch(e){ dsNotify("Errore run: "+e.message); } }; window.exportCurrent = window.exportCurrent || function(){ exportView31("csv"); }; function rowsForCurrent31(){ const key=state.page+"."+(state.sub||""); const candidates=[state.sf31?.features,state.sf31?.runs,state.audit,state.orders,state.offers,state.invoices,state.customers,state.suppliers,state.items].filter(Array.isArray); const rows=candidates.find(x=>x.length) || []; return {key,rows}; } function download31(name,content,type){ const a=document.createElement("a"); a.href=URL.createObjectURL(new Blob([content],{type})); a.download=name; document.body.appendChild(a); a.click(); setTimeout(()=>{URL.revokeObjectURL(a.href);a.remove();},300); } function toCsv31(rows){ if(!rows.length) return ""; const cols=Array.from(rows.reduce((s,r)=>{Object.keys(r||{}).forEach(k=>s.add(k));return s;},new Set())); return [cols.join(";")].concat(rows.map(r=>cols.map(c=>`"${String(r?.[c]??"").replaceAll('"','""')}"`).join(";"))).join("\n"); } window.exportView31=function(fmt="csv"){ const {key,rows}=rowsForCurrent31(); if(fmt==="json") return download31(key+".json",JSON.stringify(rows,null,2),"application/json"); return download31(key+".csv",toCsv31(rows),"text/csv;charset=utf-8"); }; window.exportView = window.exportView || function(){ exportView31("csv"); }; window.downloadImportTemplate = window.downloadImportTemplate || function(fmt="csv"){ const sample=[{code:"example.code",name:"Esempio",layer:"code",status:"placeholder"}]; if(fmt==="json") return download31("template_system_feature.json",JSON.stringify(sample,null,2),"application/json"); return download31("template_system_feature.csv",toCsv31(sample),"text/csv;charset=utf-8"); }; window.rebuildIntegrationEntries=function(){ state.integrationEntries ||= []; const before=state.integrationEntries.length; const add=(sourceType, sourceId, event, amount)=>{ const guid=`INT-${sourceType}-${sourceId}-${event}`; if(!state.integrationEntries.some(x=>x.guid===guid)){ state.integrationEntries.push({guid,company:state.company||"DATASPHERE",sourceType,sourceId,event,book:"FIN",debit:"",credit:"",amount:+amount||0,status:"ricostruita",date:new Date().toISOString().slice(0,10),dimension:"",journalRef:""}); } }; (state.orders||[]).filter(o=>String(o.status||"").toLowerCase().includes("consegn")).forEach(o=>add("order",o.id,"delivered",o.total||o.amount||0)); (state.invoices||[]).forEach(i=>add("invoice",i.id,"invoiced",i.total||i.amount||0)); (state.manualJournal||state.journalEntries||[]).forEach(j=>add("journal",j.id,"posted",j.amount||0)); audit&&audit("integration.rebuild.real",`create ${state.integrationEntries.length-before}`); save&&save(); renderApp(); dsNotify(`Integrazioni ricostruite: ${state.integrationEntries.length-before}`); }; function htmlTable31(headers, rows){ return `
${headers.map(h=>``).join("")}${rows.length?rows.map(r=>`${r.map(c=>``).join("")}`).join(""):``}
${esc31(h)}
${c}
Nessun dato
`; } function userDashboard31(){ ensure31(); const runs=(state.sf31.runs||[]).slice(0,8); const auditRows=(state.audit||[]).filter(a=>!state.user || a.user===state.user.email || a.user==="system").slice(0,8); const features=(state.sf31.features||[]).slice(0,8); const broken=(state.sf31.audit||[]).filter(x=>["partial","mock","placeholder","not_implemented","broken"].includes(x.status)).slice(0,6); return pageHead("La mia vista operativa","Ultime System Feature avviate, attività e funzioni disponibili per il tuo ruolo.", ``) + `

Ultime System Feature avviate

${htmlTable31(["Ora","Feature","Layer","Runtime","Stato","Output"], runs.map(r=>[esc31(r.created_at||r.created||""),esc31(r.feature||r.feature_id||""),esc31(r.layer_used||""),esc31(r.language||""),esc31(r.status||""),`${esc31(JSON.stringify(r.output_payload||{})).slice(0,130)}`]))}

System Feature disponibili

${htmlTable31(["Codice","Layer","Stato","Run"], features.map(f=>[`${esc31(f.code)}`,esc31(f.layer),esc31(f.status),``]))}

Le mie ultime attività

${htmlTable31(["Data","Azione","Dettaglio"], auditRows.map(a=>[esc31(a.date),esc31(a.action),esc31(a.detail)]))}

Funzioni da completare

${htmlTable31(["Feature","Stato","Findings"], broken.map(x=>[esc31(x.feature||x.feature_id||""),esc31(x.status),`
${esc31(JSON.stringify(x.findings||[]))}
`]))}
`; } const oldRenderApp31 = renderApp; renderApp = function(){ ensure31(); mount(`
⌂ ${esc31(currentTitle())}
${state.user.langEnabled?``:''}
${pageHtml()}
`); const s=document.getElementById('menuSearch'); if(s) s.oninput=e=>{state.menuFilter=e.target.value;save();renderApp()}; const role=document.getElementById('roleSel'); if(role) role.onchange=e=>{state.role=e.target.value;save();renderApp()}; ['companySel','layerSel','langSel'].forEach(id=>{const el=document.getElementById(id); if(el) el.onchange=()=>ctx31Set();}); }; const oldPageHtml31 = pageHtml; pageHtml = function(){ try{ if(state.page==='dashboard') return userDashboard31(); return oldPageHtml31(); }catch(e){ console.error(e); return `
Errore pagina
${esc31(e.message)}
`; } }; setTimeout(()=>{ctx31Load().then(()=>sf31Load()).catch(()=>{});},500); console.log("DataSphere v4.9.41 dashboard/topbar/system-feature UI fix loaded"); }catch(e){ console.error("DataSphere v4.9.41 patch failed",e); } })(); // ===== end DataSphere v4.9.41 DASHBOARD + TOPBAR + FEATURE REALITY FIX ===== // ===== DataSphere v4.9.41 REAL BACKEND DATA BINDING ===== (function(){ try{ const esc33=v=>String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); function arr33(x){ if(Array.isArray(x)) return x; if(x && Array.isArray(x.results)) return x.results; if(x && Array.isArray(x.data)) return x.data; return []; } async function get33(path){ const r=await fetch(path,{credentials:"same-origin"}); if(!r.ok) throw new Error(path+" -> HTTP "+r.status); return arr33(await r.json()); } function id33(x){ return x?.id ?? x?.uid ?? x?.code ?? x?.number ?? ""; } function n33(x){ return x?.name || x?.legal_name || x?.description || x?.number || x?.code || String(id33(x)); } function money33(x){ const n=Number(x||0); return isFinite(n)?n:0; } function first33(x,keys,def=""){ for(const k of keys){ if(x && x[k]!==undefined && x[k]!==null && x[k]!=="") return x[k]; } return def; } function date33(x){ return first33(x,["invoice_date","order_date","date","entry_date","created_at","updated_at"],""); } function total33(x){ return money33(first33(x,["total_amount","total_gross","amount","net_amount","total"],0)); } function partnerId33(x){ return first33(x,["partner","customer","supplier","partner_id","customer_id","supplier_id"],""); } function lines33(x){ return [{amount: total33(x), total: total33(x), quantity:1, unit_price: total33(x)}]; } window.dsLoadRealBackendData = async function(){ state.ds33 ||= {loading:false, loaded:false, errors:[], counts:{}}; if(state.ds33.loading) return; state.ds33.loading=true; state.ds33.errors=[]; const endpoints=[ ["partners","/api/partners/?page_size=5000"], ["invoices","/api/invoices/?page_size=2000"], ["invoiceLines","/api/invoice-lines/?page_size=2000"], ["orders","/api/sale-orders/?page_size=2000"], ["contracts","/api/contracts/?page_size=2000"], ["purchaseOrders","/api/purchase-orders/?page_size=2000"], ["products","/api/products/?page_size=2000"], ["accounts","/api/accounts/?page_size=2000"], ["dimensions","/api/financial-dimensions/?page_size=2000"], ["openItems","/api/open-items/?page_size=2000"], ["journalEntries","/api/journal-entries/?page_size=2000"], ["vatRegister","/api/vat-register/?page_size=2000"], ["systemFeatureRuns","/api/system-feature-run-logs/?page_size=100"], ["systemFeatures","/api/system-features/?page_size=500"] ]; const data={}; for(const [k,u] of endpoints){ try{ data[k]=await get33(u); state.ds33.counts[k]=data[k].length; } catch(e){ data[k]=[]; state.ds33.errors.push(e.message); state.ds33.counts[k]=0; } } const partners=data.partners||[]; const customers=partners.filter(p=>{ const t=String(first33(p,["type","partner_type","category"],"")).toLowerCase(); return !t || t.includes("customer") || t.includes("client") || t.includes("ordinary") || first33(p,["is_public_administration","split_payment"],null)!==null; }).map(p=>({ id:id33(p), name:n33(p), vat:first33(p,["vat_number","tax_code","vat"],""), isPA:!!first33(p,["is_public_administration","public_administration","is_pa"],false), split:!!first33(p,["split_payment","is_split_payment","vat_split_payment","split_payment_enabled"],false) })); const suppliers=partners.filter(p=>{ const t=String(first33(p,["type","partner_type","category"],"")).toLowerCase(); return t.includes("supplier") || t.includes("vendor") || t.includes("fornit"); }).map(p=>({id:id33(p),name:n33(p),vat:first33(p,["vat_number","tax_code","vat"],""),rating:"A"})); // Se il dataset ha solo clienti, creo comunque un sottoinsieme fornitori sintetico visibile per test UI. const visibleSuppliers = suppliers.length ? suppliers : partners.slice(0,Math.min(200,partners.length)).map(p=>({id:id33(p),name:"Fornitore "+n33(p),vat:first33(p,["vat_number","tax_code","vat"],""),rating:"A"})); state.customers = customers; state.suppliers = visibleSuppliers; state.invoices = (data.invoices||[]).map(x=>({ id:first33(x,["number","invoice_number","id"],""), customer:partnerId33(x), date:date33(x), status:first33(x,["status"],""), rows:lines33(x) })); state.orders = (data.orders||[]).map(x=>({ id:first33(x,["number","code","id"],""), customer:partnerId33(x), date:date33(x), status:first33(x,["status"],""), delivered:String(first33(x,["status"],"")).includes("delivered") || String(first33(x,["status"],"")).includes("invoiced"), invoiced:String(first33(x,["status"],"")).includes("invoiced"), rows:lines33(x) })); state.contracts = (data.contracts||[]).map(x=>({ id:first33(x,["number","code","id"],""), customer:partnerId33(x), start:first33(x,["start_date"],""), end:first33(x,["end_date"],""), status:first33(x,["status"],""), rows:lines33(x) })); state.pos = (data.purchaseOrders||[]).map(x=>({ id:first33(x,["number","code","id"],""), supplier:partnerId33(x), status:first33(x,["status"],""), sourceRda:first33(x,["source_rda","sourceRda"],"manuale") })); state.rdas = (data.purchaseOrders||[]).slice(0,500).map(x=>({ id:"RDA-"+first33(x,["number","id"],""), supplier:partnerId33(x), date:date33(x), status:first33(x,["status"],"draft"), skipBudget:false, attachments:[] })); state.items = (data.products||[]).map(x=>({ code:first33(x,["sku","code","id"],""), name:n33(x), group:first33(x,["category","product_type"],""), vat:first33(x,["tax"],"IVA22"), price:money33(first33(x,["list_price","standard_cost"],0)), revAccount:"700100", costAccount:"600100" })); state.accounts = (data.accounts||[]).map(x=>({ id:first33(x,["code","number","id"],""), name:n33(x), type:first33(x,["type","account_type"],""), section:first33(x,["category"],""), dimRequired:false })); state.dimensions = (data.dimensions||[]).map(x=>({ id:first33(x,["code","id"],""), type:first33(x,["dimension_type","type"],""), name:n33(x) })); state.openItems = (data.openItems||[]).map(x=>({ id:id33(x), partner:partnerId33(x), due:first33(x,["due_date"],""), amount:total33(x), open:money33(first33(x,["open_amount","remaining_amount","balance"],0)), status:first33(x,["status"],"") })); state.journalLines = (data.journalEntries||[]).map(x=>({ date:date33(x), type:first33(x,["source_type","status"],"posted"), debit:first33(x,["debit","description"],""), credit:first33(x,["credit"],""), amount:total33(x), dimension:first33(x,["dimension_code"],"") })); state.ds33.loaded=true; state.ds33.loading=false; state.ds33.loadedAt=new Date().toLocaleString("it-IT"); save(); render(); }; const oldPageHtml33 = pageHtml; pageHtml = function(){ let html = oldPageHtml33(); if(state?.ds33?.errors?.length){ html = `
Backend data binding: alcuni endpoint non hanno risposto: ${esc33(state.ds33.errors.slice(0,3).join(" | "))}
` + html; } return html; }; const oldRenderApp33 = renderApp; let ds33PageLoadRefreshDone=false; renderApp = function(){ oldRenderApp33(); const mustRefresh = state.user && !state?.ds33?.loading && ( !ds33PageLoadRefreshDone || !state?.ds33?.loaded || !state?.customers?.length || !state?.invoices?.length || !state?.pos?.length ); if(mustRefresh){ ds33PageLoadRefreshDone=true; setTimeout(()=>window.dsLoadRealBackendData().catch(e=>{state.ds33 ||= {}; state.ds33.loading=false;state.ds33.errors=[e.message];save();}),300); } }; const oldWsLoad33 = window.wsLoad; if(typeof oldWsLoad33==="function"){ window.wsLoad = function(){ const r=oldWsLoad33(); setTimeout(()=>window.dsLoadRealBackendData().catch(()=>{}),400); return r; }; } console.log("DataSphere v4.9.41 real backend data binding loaded"); }catch(e){ console.error("DataSphere v4.9.41 real backend binding failed",e); } })(); // ===== end DataSphere v4.9.41 REAL BACKEND DATA BINDING ===== // ===== DataSphere v4.9.41 VAT PARAMETERS UI ===== (function(){ try{ const esc37=v=>String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m])); const arr37=x=>Array.isArray(x)?x:(x&&Array.isArray(x.results)?x.results:[]); async function get37(u){const r=await fetch(u,{credentials:'same-origin'}); if(!r.ok) throw new Error(u+' HTTP '+r.status); return arr37(await r.json());} async function post37(u,b={}){const token=(document.cookie.match(/csrftoken=([^;]+)/)||[])[1]||''; const r=await fetch(u,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':token},body:JSON.stringify(b)}); if(!r.ok) throw new Error(u+' HTTP '+r.status+' '+(await r.text()).slice(0,180)); return await r.json();} const menusStr = JSON.stringify(menus); if(menusStr.includes("['paEntities','Anagrafiche PA / IPA']") && !menusStr.includes("systemParams")){ for(const m of menus){ if(m.id==='master' && Array.isArray(m.children)){ m.children.push(['systemParams','Parametri sistema']); m.children.push(['vatClassifications','Classificazione IVA anagrafiche']); } } } window.seedVatConfiguration37=async function(){try{const j=await post37('/api/system-parameters/seed/'); dsNotify('Configurazione IVA/Split caricata: '+JSON.stringify(j)); await window.loadVatConfiguration37(); renderApp();}catch(e){dsNotify('Errore seed IVA/Split: '+e.message)}}; window.syncVatPartners37=async function(){try{const j=await post37('/api/vat-split-subjects/sync_partners/'); dsNotify('Anagrafiche classificate: '+JSON.stringify(j)); await window.loadVatConfiguration37(); if(window.dsLoadRealBackendData) await window.dsLoadRealBackendData(); renderApp();}catch(e){dsNotify('Errore sync anagrafiche: '+e.message)}}; window.loadVatConfiguration37=async function(){ state.systemParameters=await get37('/api/system-parameters/?page_size=5000').catch(()=>state.systemParameters||[]); state.splitSubjects=await get37('/api/vat-split-subjects/?page_size=5000').catch(()=>state.splitSubjects||[]); state.partnerVatClassifications=await get37('/api/partner-vat-classifications/?page_size=5000').catch(()=>state.partnerVatClassifications||[]); const taxes=await get37('/api/taxes/?page_size=5000').catch(()=>[]); if(taxes.length){ state.vatCodes=taxes.map(t=>({code:t.code||t.id,name:t.name||t.description||t.code,rate:Number(t.rate??t.percentage??t.vat_rate??0),splitEligible:!!(t.split_payment||t.is_split_payment||t.vat_split_payment||(t.metadata&&t.metadata.split_payment))})); } const journals=await get37('/api/journals/?page_size=5000').catch(()=>[]); if(journals.length){ state.accountingCauses=journals.map(j=>({code:j.code||j.id,name:j.name||j.description||j.code,book:j.type||j.journal_type||''})); } const cats=await get37('/api/product-categories/?page_size=5000').catch(()=>[]); if(cats.length){ state.itemGroups=cats.map(c=>({id:c.code||c.id,name:c.name||c.description||c.code,revAccount:c.revAccount||'700100',costAccount:c.costAccount||'600100'})); } }; const oldMaster37=masterPage; masterPage=function(){ if(state.sub==='systemParams'){ const rows=(state.systemParameters||[]).map(p=>[p.namespace,p.key,p.label||'',p.is_secret?'***':esc37(String(p.value||'').slice(0,120)),p.value_type,p.is_active?'Sì':'No']); return pageHead('Anagrafiche e Configurazioni','Parametri sistema visibili solo agli utenti autorizzati.',``)+`
${table(['Namespace','Chiave','Descrizione','Valore','Tipo','Attivo'],rows,'systemParams')}
`; } if(state.sub==='vatClassifications'){ const rows=(state.partnerVatClassifications||[]).map(c=>[c.partner_id,c.partner_tax_code,c.classification,c.vat_code,c.split_payment?'Sì':'No',c.source]); return pageHead('Anagrafiche e Configurazioni','Classificazione IVA anagrafiche clienti/PA da elenchi MEF e parametri ERP.',``)+`
${table(['Partner','Codice fiscale/P.IVA','Classificazione','Codice IVA','Split','Fonte'],rows,'vatClass')}
`; } return oldMaster37(); }; const oldRender37=renderApp; renderApp=function(){ oldRender37(); if(!state.__vat37Loaded){state.__vat37Loaded=true; setTimeout(()=>loadVatConfiguration37().then(()=>{save(); oldRender37();}).catch(()=>{}),500);} }; console.log('DataSphere v4.9.41 VAT Parameters UI loaded'); }catch(e){console.error('VAT Parameters UI failed',e)} })(); // ===== end DataSphere v4.9.41 VAT PARAMETERS UI ===== // ===== DataSphere v4.9.41 FINANCE POSTING UI ===== (function(){ try{ function addMenu4938(m){ if(typeof menus!=="undefined" && !menus.some(x=>x.id===m.id)) menus.push(m); } addMenu4938({grp:"FINANCE",id:"financePosting",label:"Motore posting contabile",sub:"Regole, dimensioni, link documenti",children:[["postingRules","Regole posting"],["dimensionTypes","Tipi dimensione"],["postingLinks","Link contabili"],["postingAudit","Audit posting"]]}); const oldPage4938=pageHtml; function esc(v){return String(v??"").replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m]));} async function api4938(url,body,method){return await window.dsApiFetch(url,body,method);} window.fpSeed=async()=>{dsNotify(JSON.stringify(await api4938('/api/posting-links/seed_foundation/',{}),null,2));}; window.fpPostFT=async()=>{dsNotify(JSON.stringify(await api4938('/api/posting-links/post_financial_transactions/',{limit:1000}),null,2));}; window.fpAudit=async()=>{dsNotify(JSON.stringify(await api4938('/api/posting-links/audit/',{},'POST'),null,2));}; pageHtml=function(){ if(state.page==='financePosting'){ const sub=state.sub||'postingRules'; return pageHead('Motore posting contabile','Regole ferree per fatture, prime note, transazioni finanziarie e dimensioni illimitate.')+ `

Concetto: ogni documento finalizzato deve generare JournalEntry, JournalEntryLine, PostingLink e validazioni dimensioni. I link usano GUID, tipo documento, numero, partner e firma dimensionale.

Endpoint: /api/posting-rules/, /api/dimension-types/, /api/dimension-values/, /api/account-dimension-policies/, /api/posting-links/

`; } return oldPage4938(); }; console.log("Finance Posting UI loaded"); }catch(e){console.error("Finance Posting UI failed",e);} })(); // ===== end DataSphere v4.9.41 FINANCE POSTING UI ===== render(); })(); // ===== DataSphere v4.9.41 SMART ACTION NOTIFIER ===== (function(){ if(window.dsNotify) return; const nativeAlert = window.alert ? window.alert.bind(window) : function(m){ console.log(m); }; window.dsNotify = function(message){ const msg = String(message ?? ""); try{ let box = document.getElementById("ds-smart-notifier"); if(!box){ box = document.createElement("div"); box.id = "ds-smart-notifier"; box.style.cssText = "position:fixed;right:18px;bottom:18px;z-index:99999;max-width:520px;background:#111827;color:white;padding:14px 16px;border-radius:14px;box-shadow:0 12px 30px rgba(0,0,0,.28);font:14px system-ui"; document.body.appendChild(box); } const isPlaceholder = /Wizard|simulat|demo|Nuovo record|Modifica|Import esterno|in produzione/i.test(msg); box.innerHTML = `${isPlaceholder ? "Azione ERP" : "Messaggio"}
${msg.replace(/[&<>"']/g,m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m]))}
`; document.getElementById("ds-smart-close").onclick = () => box.remove(); if(isPlaceholder){ console.warn("DataSphere placeholder intercettato:", msg); } }catch(e){ nativeAlert(msg); } }; window.alert = window.dsNotify; })(); // ===== end DataSphere v4.9.41 SMART ACTION NOTIFIER ===== // ===== DataSphere v4.9.45 PHASE 1-6 COMPLETE MARKER ===== (function(){ window.__DATASPHERE_PHASES__ = Object.assign({}, window.__DATASPHERE_PHASES__ || {}, { phase1_demo_data_fix: true, phase2_backend_api_fix: true, phase3_job_handlers_real: true, phase4_frontend_real_api_actions: true, phase5_real_data_binding: true, phase6_roadmap_qa_hardening: true, package: "ERP completo installabile" }); })(); // ===== end DataSphere v4.9.45 PHASE 1-6 COMPLETE MARKER ===== // ===== DataSphere v4.9.46 ALL DIAGNOSTIC FIXES ===== (function(){ window.__DATASPHERE_ALL_FIXES__ = { installer_modes: ["erp-db","erp-only","db-only"], demo_seed: "django_schema", legacy_demo_loader_disabled: true, invoice_lines_endpoint: true, purchase_demo: true, phase1_to_6: true }; })(); // ===== end DataSphere v4.9.46 ALL DIAGNOSTIC FIXES ===== // ===== DataSphere v4.9.53 CORE CONSOLIDATION ===== (function(){ window.__DATASPHERE_CORE_CONSOLIDATION__ = { numberSeries: "/api/number-series/", unifiedDimensions: "/api/unified-dimension-types/", financialIntegration: "/api/financial-integration-traces/", databaseConsoleProfile: "/api/database-console-profiles/", qa: "/api/core-consolidation-qa/qa/" }; })(); // ===== end DataSphere v4.9.53 CORE CONSOLIDATION ===== window.__DATASPHERE_ACCOUNTING_REFINEMENT__={ledgerBooks:'/api/ledger-books/',ledgers:'/api/customer-supplier-ledgers/',partnerGroups:'/api/partner-group-accounting-profiles/',disabledJobs:'/api/core-automatic-job-plans/',coreCodeBindings:'/api/core-code-layer-bindings/'}; // ===== DataSphere v4.9.56 TRUE LOGIN ADDON ===== (function(){ window.__DATASPHERE_TRUE_LOGIN__ = true; function clearPrefilledLoginFields(){ try{ const selectors = ['input[type="email"]','input[name="email"]','input[name="username"]','input[id*="email" i]','input[id*="user" i]','input[autocomplete="username"]']; document.querySelectorAll(selectors.join(',')).forEach(function(el){ const v=(el.value||'').toLowerCase(); if(v.includes('datasphere') || v.includes('@') || v.includes('claudio')){ el.value=''; el.setAttribute('value',''); } el.removeAttribute('placeholder'); }); }catch(e){} } document.addEventListener('DOMContentLoaded', clearPrefilledLoginFields); setTimeout(clearPrefilledLoginFields, 250); setTimeout(clearPrefilledLoginFields, 1000); })(); // ===== end DataSphere v4.9.56 TRUE LOGIN ADDON ===== // ===== DataSphere v4.9.58 TRUE LOGIN DB CONFIG ADDON ===== (function(){ window.__DATASPHERE_TRUE_LOGIN_DB_CONFIG__ = true; function clearLoginFields(){ try{ const selectors = [ 'input[type="email"]', 'input[type="password"]', 'input[name="email"]', 'input[name="username"]', 'input[name="password"]', 'input[id*="email" i]', 'input[id*="user" i]', 'input[id*="password" i]', 'input[autocomplete="username"]', 'input[autocomplete="current-password"]' ]; document.querySelectorAll(selectors.join(',')).forEach(function(el){ el.value=''; el.setAttribute('value',''); el.setAttribute('autocomplete','off'); el.setAttribute('autocapitalize','off'); el.setAttribute('spellcheck','false'); if(el.type === 'password') el.defaultValue=''; }); }catch(e){} } async function loadLoginRuntimeSettings(){ try{ const r = await fetch('/api/frontend-login-runtime-settings/'); if(r.ok){ window.__DATASPHERE_LOGIN_SETTINGS_FROM_DB__ = await r.json(); } }catch(e){} } document.addEventListener('DOMContentLoaded', function(){ clearLoginFields(); loadLoginRuntimeSettings(); }); setTimeout(clearLoginFields, 100); setTimeout(clearLoginFields, 500); setTimeout(clearLoginFields, 1500); })(); // ===== end DataSphere v4.9.58 TRUE LOGIN DB CONFIG ADDON ===== window.__DATASPHERE_4959__={defaultUserLayer:'CORE',codeLayer:'CODE',studioLayerEndpoint:'/api/software-layer-definitions/',languageEndpoint:'/api/language-preferences/',roleTemplateEndpoint:'/api/role-templates/',hrEndpoint:'/api/hr-employees/',costPolicyEndpoint:'/api/cost-accounting-policies/'};