Designbest Core Sviluppo: differenze tra le versioni
| (225 versioni intermedie di 3 utenti non mostrate) | |||
| Riga 1: | Riga 1: | ||
== Goal == | == Goal == | ||
Realizzare una versione agile e super performante di Designbest usando le ultime tecnologie Microsoft disponibili (.NET Core 6). | Realizzare una versione agile e super performante di Designbest usando le ultime tecnologie Microsoft disponibili (<del>.NET Core 6</del> .NET Core 7). | ||
=== Page Speed Insights === | |||
* Riduzione del TTFB (Time To The First Byte) | |||
* Immagini di dimensioni adeguate al dispositivo (Imagekit.io) | |||
* Specificare width e height per ogni immagine | |||
* Specificare alt per ogni immagine | |||
* Minificazione delle dipendenze da terze parti (animazioni fatte in linguaggio nativo piuttosto che utilizzo di plugin esterni) | |||
* Raggruppamento (bundling) delle risorse CSS e Javascript | |||
* Minificazione delle risorse raggruppate | |||
* Ottimizzazione del tempo di Javascript (tramite promise e processi async). | |||
* Inclusione delle risorse Javascript strettamente necessarie alla pagina (e non librerie complete) | |||
* Inclusione delle risorse CSS strettamente necessarie alla pagina. | |||
* Precaricamento della Largest Contentful Paint per ogni pagina | |||
* Immagini e iframe lazy (caricamento pigro) | |||
* Riduzione elementi in pagina tramite caricamenti asincroni su richiesta (modali, librerie) | |||
* Compressione dell'HTML generato | |||
* Compressione delle risorse statiche | |||
* manifest con info per creare un PWA | |||
* Velocità media caricamento pagina costantemente < 3s | |||
=== SEO === | |||
* Semantica della pagina (h1, h2, section, main content, aside content, header, footer, nav). | |||
* Gestione di meta tag seo description, title | |||
* Href Lang (link seo che indicano la stessa pagina in lingua differente) | |||
* Logiche di redirect applicative (con listing vuoti => redirect al listing superiore, negozio chiuso => redirect a listing di quella città) | |||
* Structured Data (validate da Google) | |||
** Breadcrumbs | |||
** Loghi (nome organizzazione e canali social) | |||
** Snippet di prodotto | |||
** Snippet di recensioni (stelline) | |||
** Casella di ricerca | |||
** Caroselli (lista di prodotti) | |||
** Attività locali (negozi) | |||
=== Lato Applicativo === | |||
* Riduzione del TTFB (Time To The First Byte) tramite controllo completo sulle query e le stored procedure | |||
* Logiche di redirect applicative (con listing vuoti => redirect al listing superiore, negozio chiuso => redirect a listing di quella città) | |||
* Gestione geolocalizzazione (database MaxMind aggiornato) | |||
* Algoritmi di ordinamento prodotti | |||
* Gestione Ad manager (DFP secondo logiche custom di targeting ambiente/zona/manu) | |||
* Gestione logiche 4Dem (ultimo prodotto visto, data, spedizione flusso newsletter) | |||
* Bottoni Sponsor che appaiono nell'ambiente preciso | |||
== Database == | == Database == | ||
| Riga 27: | Riga 69: | ||
* ShopPointPropertyValue | * ShopPointPropertyValue | ||
* Mapping_ShopPoint_ShopPointPropertyValue | * Mapping_ShopPoint_ShopPointPropertyValue | ||
* ShopPointSponsor | |||
<hr/> | <hr/> | ||
* Agent | * Agent | ||
| Riga 40: | Riga 83: | ||
<hr/> | <hr/> | ||
* Picture | * Picture | ||
<hr/> | |||
* Customer | |||
* Mapping_Customer | |||
* User | |||
* UserUnregistered | |||
* Wishlist | |||
* Mapping_Wishlist_Product | |||
<hr/> | |||
* Selection | |||
* Mapping_Selection_Product | |||
<hr/> | |||
* ProductThumbs | |||
<hr/> | |||
* History_Email | |||
=== Popolamento Region/Province/City === | === Popolamento Region/Province/City === | ||
| Riga 118: | Riga 175: | ||
Per quanto riguarda gli ''ShopPointServices'', purtroppo abbiamo ereditato una logica per la quale, | Per quanto riguarda gli ''ShopPointServices'', purtroppo abbiamo ereditato una logica per la quale, | ||
se esiste il valore PropertyValue nel mapping, allora il negozio possiede quella Property. Di conseguenza, se il negozio possiede una certa Property e non specifica nessun suo valore, è necessario inserire un PropertyValue vuoto. | se esiste il valore PropertyValue nel mapping, allora il negozio possiede quella Property. Di conseguenza, se il negozio possiede una certa Property e non specifica nessun suo valore, è necessario inserire un PropertyValue vuoto. | ||
==== Schifezze ==== | ==== Schifezze ==== | ||
Controllare i valori delle custom properties perché il database è pieno di duplicati assegnati a prodotti diversi.<br/> | Controllare i valori delle custom properties perché il database è pieno di duplicati assegnati a prodotti diversi.<br/> | ||
Ad esempio la '''ProductProperty''' <code>Caratteristiche</code> con ''SeoName'' <code>Caratteristiche - Sedie</code> c'è due volte con ''ID'' differenti.<br/> | Ad esempio la '''ProductProperty''' <code>Caratteristiche</code> con ''SeoName'' <code>Caratteristiche - Sedie</code> c'è due volte con ''ID'' differenti.<br/> | ||
Per rimediare si potrebbero assegnare tutti i prodotti ad un unico di questi ''ID'' e cancellare l'altro doppio.<br/><br/> | Per rimediare si potrebbero assegnare tutti i prodotti ad un unico di questi ''ID'' e cancellare l'altro doppio.<br/> | ||
Questa roba fa casino nel count dei prodotti nel telecomando<br/> | |||
<syntaxhighlight lang="sql"> | |||
SELECT [Name],[SeoName], COUNT(SeoName) AS Duplic | |||
FROM ProductPropertyValue | |||
GROUP BY [Name], SeoName | |||
ORDER BY Duplic DESC, [Name] ASC | |||
</syntaxhighlight> | |||
<br/> | |||
=== ProductThumbs === | |||
Questa tabella purtroppo è necessaria in quanto i listing prodotti di una tipologia sono troppo lenti (tempo esecuz > 1s).<br/> | |||
Una singola riga rappresenta un ''ProductThumbanil'' comprensivo di tutti i dati necessari a creare l'anteprima quadrata, titolo, ambiente, tipo manu e dati per creare i link.<br/> | |||
Sarà tenuta aggiornata in tempo reale dalla backoffice (no task schedulati). | |||
=== Tabelle escluse === | === Tabelle escluse === | ||
| Riga 131: | Riga 201: | ||
=== Shop === | === Shop === | ||
==== ShopNet Casi Particolari ==== | ==== ShopNet Casi Particolari ==== | ||
'''Attenzione che ogni SHOPNET deve avere una mail univoca altrimenti non viene caricato il Vendor su DOKAN (Wordpress vuole che le mail siano univoche per ogni utente).''' | |||
Tutti i negozi ora appartengono obbligatoriamente ad una '''ShopNet''' | Tutti i negozi ora appartengono obbligatoriamente ad una '''ShopNet''' | ||
Queste ShopNet in particolare: | Queste ShopNet in particolare: | ||
* '''ShopNet Trovaprodotti Orfani''' Tutti i Trovaprodotti che non avevano una ShopNet. Hanno <code>Piva = '000000000000'</code>. Ne viene creata una per ognuno. | * '''ShopNet Trovaprodotti Orfani''' Tutti i Trovaprodotti che non avevano una ShopNet. Hanno <code>Piva = '000000000000'</code>. Ne viene creata una per ognuno. | ||
* '''ShopNet con PIVA nulla''' - Le vecchie ShopNet senza PIVA. Hanno <code>Piva = '000000000001'</code> | |||
* '''ShopNet Anagrafiche''' - Contiene tutti gli ShopPoint di tipo != 0 che sono anagrafiche. Un padre per molti figli. Ha <code>Piva = '000000000003'</code>. | |||
* '''ShopNet Estero''' - Tutti gli ShopPoint Esteri. Hanno <code>Piva = '000000000004'</code>. Ne viene creata una per ognuno. | * '''ShopNet Estero''' - Tutti gli ShopPoint Esteri. Hanno <code>Piva = '000000000004'</code>. Ne viene creata una per ognuno. | ||
==== ShopFulltextAdditions ==== | ==== ShopFulltextAdditions ==== | ||
Gestione della ricerca '''full-text dei negozi''' che aggiunge ambienti, tipologie e produttori alle chiavi di ricerca del singolo negozio.<br/> | Gestione della ricerca '''full-text dei negozi''' che aggiunge ambienti, tipologie e produttori alle chiavi di ricerca del singolo negozio.<br/> | ||
Sarà necessario reimplementare tutto il passaggio con il task notturno che genera le righe? | Sarà necessario reimplementare tutto il passaggio con il task notturno che genera le righe? | ||
==== E-mail duplicate in ShopNet differenti ==== | |||
Attualmente ci sono dei casi di ShopNet che hanno il flag <code>Enable_Outlet = 1</code> e la stessa email.<br/> | |||
Questa situazione non va bene perché la sincronizzazione su Outlet Wordpress prevede che la mail sia univoca per ogni venditore (che corrisponde ad un user wp).<br/> | |||
La backoffice, in caso di ShopNet Outlet Enabled, dovrà sempre controllare che la mail inserita non sia già presente.<br/> | |||
Ecco un elenco dei casi scorretti presenti sulla nostra base di dati ''(DesignbestCore db)'': | |||
<syntaxhighlight lang="sql"> | |||
SELECT MainMail, COUNT(MainMail) AS Quante | |||
FROM ShopNet | |||
WHERE Enable_Outlet = 1 | |||
GROUP BY MainMail | |||
ORDER BY Quante DESC | |||
</syntaxhighlight> | |||
=== Pictures === | === Pictures === | ||
La gestione delle immagini sarà basata su quella di Nop.<br/> | La gestione delle immagini sarà basata su quella di Nop.<br/> | ||
{| class="wikitable" | {| class="wikitable" | ||
! colspan="3"|Picture | !colspan="3"|Picture | ||
|- | |- | ||
|style="font-family: monospace;"|ID | |style="font-family: monospace;"|ID | ||
|int | |int | ||
|ID della picture | |ID della picture | ||
| Riga 174: | Riga 263: | ||
Tutte le immagini saranno in un unico folder | Tutte le immagini saranno in un unico folder | ||
<syntaxhighlight> | <syntaxhighlight> | ||
product-< | product-<EntityID>-<ID>.jpg | ||
bargain-< | bargain-<EntityID>-<ID>.jpg | ||
manufacturer-<EntityID>-<ID>.jpg | |||
shop-<EntityID>-<ID>.jpg | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Questa soluzione è meno leggibile dall'umano ma molto più elastica nel caso in cui ci siano diversi cambiamenti di ordine, master/alternative (evita di dover rinominare l'immagine su disco). | Questa soluzione è meno leggibile dall'umano ma molto più elastica nel caso in cui ci siano diversi cambiamenti di ordine, master/alternative (evita di dover rinominare l'immagine su disco).<br/><br/> | ||
'''Esempio''' | |||
Volendo trovare tutte le immagini relative ad uno shop si potrebbe usare la seguente query: | |||
<syntaxhighlight lang="sql"> | |||
SELECT * | |||
FROM Picture | |||
WHERE EntityID = 15314 AND KeyGroup = 'Shop' | |||
</syntaxhighlight > | |||
Avendo come risultato qualcosa di simile. | |||
<pre>bla bla bla ....</pre> | |||
==== Convenzioni ==== | |||
Per quanto riguarda i campi '''EntityGroup''' e '''EntityKey''' ecco cosa proponiamo | |||
* '''ShopPoint''' | |||
** ''cover'' | |||
** ''logo'' | |||
** ''gallery'' | |||
* '''Product''' | |||
** ''gallery'' | |||
** ''square'' | |||
* '''Manufacturer''' | |||
** ''logo'' | |||
** ''logo-square'' | |||
** ''cover'' | |||
** ''sponsored'' (bottone sponsor in HP) | |||
** ''gallery'' | |||
* '''Category''' | |||
** ''ambient'' | |||
* '''Trovaprodotti''' segue le stesse regole nelle tabelle ''_ext'' ma negli '''EntityValue''' cambia così | |||
** ''gallery'' (prodotti a catalogo creati da cliente) | |||
*** ''productext-<context>'' | |||
** ''logo'' | |||
*** ''manufacturerext-<context>'' | |||
=== Entity Deepening - Approfondimenti delle entità === | |||
{| class="wikitable" | |||
!colspan="3"|EntityDeepening | |||
|- | |||
|style="font-family: monospace;"|ID | |||
|int | |||
|ID della picture | |||
|- | |||
|style="font-family: monospace;"|LanguageID | |||
|int | |||
|Lingua | |||
|- | |||
|style="font-family: monospace;"|EntityID | |||
|int | |||
|ID dell'entità | |||
|- | |||
|style="font-family: monospace;"|EntityKeyGroup | |||
|nvarchar(100) | |||
|Nome della Tabella di riferimento | |||
|- | |||
|style="font-family: monospace;"|EntityKey | |||
|nvarchar(100) | |||
|Formato dell'immagine desiderato | |||
|- | |||
|style="font-family: monospace;"|ExternalID | |||
|int | |||
|ID esterno | |||
|- | |||
|style="font-family: monospace;"|Link | |||
|nvacrchar(255) | |||
|Link esterno | |||
|- | |||
|style="font-family: monospace;"|Title | |||
|nvacrchar(255) | |||
|Link esterno | |||
|- | |||
|style="font-family: monospace;"|ShortDescription | |||
|nvacrchar(255) | |||
|Descrizione breve | |||
|- | |||
|style="font-family: monospace;"|MediaLink | |||
|nvacrchar(255) | |||
|Link ad un media | |||
|- | |||
|style="font-family: monospace;"|DisplayOrder | |||
|int | |||
|Ordinamento | |||
|} | |||
La tabella <code>EntityDeepening</code> rappresenta un calderone generico per poter assegnare ad una qualsiasi nostra '''entità''' ''(Manu, Shop, Product ecc)'' i seguenti valori: | |||
* '''Id''' esterno | |||
* '''Link''' esterno | |||
* '''Titolo''' articolo | |||
* '''Descrizione breve''' articolo | |||
* '''Foto''' (facoltativa) | |||
che rappresentano ''approfondimenti'' su altre piattaforme (es. Magazine).<br/> | |||
Verrà utilizzata per '''Brand Channel''' (manu) e '''Progetti''' (manu e shop).<br/> | |||
Di seguito le chiavi '''EntityGroup''' ed '''EntityKey''': | |||
* '''Manufacturer''' | |||
** ''brandchannel'' | |||
** ''project'' | |||
* '''ShopNet''' | |||
** ''project'' | |||
=== Keywords === | |||
La tabella <code>Keywords</code> ha come obiettivo quello di estendere i match di una entità durante una fulltext ''(B&B => beb , Hartè => harte ecc...)''.<br/> | |||
Di seguito le tipologie di entità, '''EntityGroup''' | |||
* ''Manufacturer'' | |||
* ''Product'' | |||
* ''ShopPoint'' | |||
Utilizzata per la '''ricerca fulltext''' , quei termini che hanno accenti o caratteri strani.<br/><br/> | |||
La feature è attiva solo per ITALIA, le parole "extra" vengono aggiunte alle tabelle/viste di ricerca Fulltext. | |||
* <code>FullText_Products_Update</code> | |||
* <code>FT_Products_IT</code> | |||
* <code>FT_Shops_IT</code> | |||
=== Le selezioni === | |||
Utilizziamo il concetto di '''selezione''' per definire gruppi di prodotti (oppure negozi ecc) al fine di poterli richiamare in quei moduli che attualmente hanno una logica ''circostanziale'' spesso gestiti con xml.<br/> | |||
In questo modo possiamo creare N selezioni tramite il backoffice da usare nei casi più svariati. | |||
=== Importazione dati === | |||
Eseguire nell'ordine | |||
* Eseguire script sul database <code>Designbest</code> per normalizzare e copiare le Misure nel campo <code>ProductCulture.AdditionalNotes</code>, '''4 volte''' per tutte le lingue | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
/* | |||
La procedura, per ogni prodotto, normalizza le misure in una unica stringa e ne copia il contenuto | |||
dentro a ProductBargain.AdditionalNotes . | |||
Ci mette circa 1:25min per la cultura italiana. | |||
*/ | |||
BEGIN TRAN | |||
DECLARE @Culture VARCHAR(5) = 'it-IT', | |||
@LanguageId INT = 1 | |||
DECLARE @ProductID INT = 0, | |||
@MeasuresCurrent NVARCHAR(2000), | |||
@MeasuresValue NVARCHAR(2000) | |||
DECLARE cur CURSOR FOR | |||
SELECT ID FROM Product-- WHERE ID = 2 | |||
OPEN cur; | |||
FETCH NEXT FROM cur INTO @ProductID | |||
WHILE @@FETCH_STATUS = 0 BEGIN | |||
SET @MeasuresValue = '' | |||
DECLARE cur2 CURSOR FOR | |||
SELECT '<p>' +MeasureCulture.DisplayName + ' ' + L_Product_Measure.[Value] + ' ' + MeasureCulture.Unit + '</p>' | |||
FROM L_Product_Measure | |||
INNER JOIN Measure ON L_Product_Measure.MeasureID = Measure.ID | |||
INNER JOIN MeasureCulture ON Measure.ID = MeasureCulture.MeasureID AND MeasureCulture.Culture = @Culture | |||
WHERE ProductID = @ProductID | |||
ORDER BY MeasureCulture.Culture, Measure.Sort | |||
OPEN cur2; | |||
FETCH NEXT FROM cur2 INTO @MeasuresCurrent | |||
WHILE @@FETCH_STATUS = 0 BEGIN | |||
IF @MeasuresValue = '' BEGIN | |||
SET @MeasuresValue = @MeasuresCurrent + ' ' | |||
END | |||
ELSE BEGIN | |||
SET @MeasuresValue += @MeasuresCurrent | |||
END | |||
FETCH NEXT FROM cur2 INTO @MeasuresCurrent | |||
END | |||
CLOSE cur2; | |||
DEALLOCATE cur2; | |||
-- Update di ProductCulture.AdditionalNotes | |||
UPDATE ProductCulture | |||
SET AdditionalNotes = | |||
CASE WHEN @MeasuresValue = '' THEN NULL | |||
ELSE @MeasuresValue | |||
END | |||
WHERE ProductID = @ProductID AND Culture = @Culture | |||
PRINT CAST(@ProductID AS VARCHAR(10)) +':'+ @MeasuresValue | |||
-- QUESTA PARTE SCRIVE DIRETTAMENTE SU DesignbestCore | |||
--IF @MeasuresValue <> '' BEGIN | |||
-- -- Tabella Product.Measures (lingua neutra, cioè ita) | |||
-- IF @Culture = 'it-IT' BEGIN | |||
-- UPDATE DesignbestCore.dbo.Product | |||
-- SET Measures = CASE WHEN @MeasuresValue = '' THEN NULL | |||
-- ELSE @MeasuresValue | |||
-- END | |||
-- WHERE ID = @ProductID-- AND Culture = @Culture | |||
-- END | |||
-- -- LocalizedProperty | |||
-- UPDATE DesignbestCore.dbo.LocalizedProperty | |||
-- SET LocaleValue = @MeasuresValue | |||
-- WHERE EntityId = @ProductID AND LanguageId = @LanguageId AND LocaleKeyGroup = 'Product' AND LocaleKey = 'Measures' | |||
--END | |||
FETCH NEXT FROM cur INTO @ProductID | |||
END | |||
CLOSE cur; | |||
DEALLOCATE cur; | |||
COMMIT TRAN | |||
</syntaxhighlight> | |||
</div> | |||
* Reset_All_Tables (database <code>DesignbestCore</code>) ~ 4 min | |||
* WM_CategorySynchCore | |||
* Eliminare temporaneamente vincolo <code>FK_ProductBargain_ShopPoint</code> da <code>ProductBargain</code> | |||
* WM_ProductSynchCore ~ 3 min | |||
* Fare la chiamata API a <code>https://www.designbestoutlet.com/wp-json/designbest-outlet/v1/listallvendors</code> con Postman e piazzare il file json restituito in <code>D:\Temp\Outlet-Vendors.json</code> sulla macchina <code>BIANCANEVE</code> | |||
* Piazzare il file <code>homepage_highlights.xml</code> in <code>D:\Temp\homepage_highlights.xml</code> | |||
* WM_ShopNetSynchCore | |||
* Ripristinare la chiave esterna <code>FK_ProductBargain_ShopPoint</code> su <code>ProductBargain</code> | |||
* Verificare che non ci siano designer con seoname duplicato sul database <code>Designbest</code>, nel caso correggerli | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
SELECT SEODesigner, COUNT(SEODesigner) AS contegg | |||
FROM Designer | |||
where AllowSearch = 1 OR AllowSearchOutlet = 1 | |||
group by SEODesigner | |||
order by contegg desc | |||
</syntaxhighlight> | |||
</div> | |||
* WM_ProductPropertySynchCore | |||
* FullTextSearch, eseguire i comandi del paragrafo [[#Negozi]] nel database <code>DesignbestCore</code> | |||
* WM_PicturesSynchCore (verificare aggiornamento path di XML BrandChannelPhotoGalleries su <code>D:\Temp</code>) su BIANCANEVE | |||
* Rinominare fisicamente le 22 immagini degli ambienti aggiungendo PictureID generato al nome es <code>bagno-14-217177</code> e copiarle in una cartella a parte (perché la pictures verrà cancellata) | |||
* WM_Nop_SyncCore (verificare i negozi nella <code>DesignbestCore.dbo.NopMapping_ShopNets</code> confrontandoli con la vecchia) | |||
* WM_UsersCore | |||
* WM_TpCustomSynchCore | |||
* Cancellare tutte le immagini nella cartella <code>pictures</code> (occhio, salvarsi prima le 22 immagini degli ambienti) | |||
* Copiare le 22 immagini ambiente che sono state salvate prima dentro alla nuova ''pictures'' | |||
* Immagini occasioni - Eseguire script Python tramite <code>D:\Programs\MigrazioneDB_DBCore\DesignbestBargainMigration.bat</code> su ARIEL ''(~ 10 min)''. | |||
* Immagini cover shop - Eseguire script Python <code>D:\Programs\MigrazioneDB_DBCore\DesignbestShopCoverAltMigration.py</code> su ARIEL | |||
* Utility_ProductThumbsMerge (database <code>DesignbestCore</code>) | |||
* Immagini fisiche - Eseguire la query SQL per generare il file batch che sposterà i file, database <code>DesignbestCore</code> ''(~ 3:40h)'' | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
SELECT | |||
'IF EXIST "' + REPLACE(old_PicturePath, '/', '\') + '" (', | |||
'xcopy /y "' + REPLACE(old_PicturePath, '/', '\') + '" ' + '"D:\WM3Resources\ImmaginiWM3\pictures\' + EntityValue + '-'+ CAST(EntityID AS VARCHAR(10)) + '-' + CAST(ID AS VARCHAR(10)) + '.jpg*"', | |||
') ELSE ( echo ' + CAST(ID AS VARCHAR(10)) + ' BigPath >> missingimages.txt )' | |||
FROM Picture | |||
</syntaxhighlight > | |||
</div> | |||
* <del>Update Width e Height delle Pictures - Eseguire script Python tramite <code>D:\Programs\python-CoreGetSetPicturesWidthHeight\main.py</code> su ARIEL ''(~ oo min)''</del>. | |||
* Immagini selezioni TP - Eseguire script Python tramite <code>D:\Programs\MigrazioneDB_DBCore\DesignbestSelectionMigration.bat</code> su ARIEL ''(~ 3 min)''. | |||
* Immagini fisiche selezioni - Eseguire la query SQL per generare il file batch che sposterà i file, database <code>DesignbestCore</code> | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
SELECT | |||
'IF EXIST "' + REPLACE(old_PicturePath, '/', '\') + '" (', | |||
'xcopy /y "' + REPLACE(old_PicturePath, '/', '\') + '" ' + '"D:\WM3Resources\ImmaginiWM3\pictures\' + EntityValue + '-'+ CAST(EntityID AS VARCHAR(10)) + '-' + CAST(ID AS VARCHAR(10)) + '.jpg*"', | |||
') ELSE ( echo ' + CAST(ID AS VARCHAR(10)) + ' BigPath >> missingimages2.txt )' | |||
FROM Picture_Ext | |||
WHERE old_PicturePath IS NOT NULL | |||
</syntaxhighlight > | |||
</div> | |||
* Update Width e Height delle Pictures delle Selezioni - Eseguire script Python tramite <code>D:\Programs\python-CoreGetSetPicturesWidthHeight\CoreGetSetWidthHeight.bat</code> su ARIEL per le <code>Picture_Ext</code> (occhio allo script, eseguire la parte Picture_Ext e commentare l'altra). | |||
* Update Width e Height delle Pictures delle Selezioni - Eseguire script Python tramite <code>D:\Programs\python-CoreGetSetPicturesWidthHeight\CoreGetSetWidthHeight.bat</code> su ARIEL per le <code>Picture</code> ''(~ 3h e 7 min)'' (occhio allo script, eseguire la parte Picture e commentare l'altra). | |||
* Volendo mentre si aspetta questo task è possibile fare la parte di '''LeadManager''' [[#Operazione per Outlet WP, Nop Commerce e Lead manager]] | |||
* Rilanciare SP -> Utility_ProductThumbsMerge (database <code>DesignbestCore</code>) | |||
* Rilanciare SP -> Utility_ProductThumbsMerge_Ext e FullText_Products_Update_Ext usando il seguente cursore | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
DECLARE @TrovaprodottiID int | |||
DECLARE cur3 CURSOR FOR | |||
SELECT DISTINCT Context FROM DesignbestCore.dbo.Product_Ext | |||
OPEN cur3; | |||
FETCH NEXT FROM cur3 INTO @TrovaprodottiID | |||
WHILE @@FETCH_STATUS = 0 BEGIN | |||
EXEC DesignbestCore.dbo.Utility_ProductThumbsMerge_Ext @TrovaprodottiID, 1 | |||
EXEC DesignbestCore.dbo.FullText_Products_Update_Ext @TrovaprodottiID | |||
FETCH NEXT FROM cur3 INTO @TrovaprodottiID | |||
END | |||
CLOSE cur3; | |||
DEALLOCATE cur3; | |||
</syntaxhighlight > | |||
</div> | |||
* SU NOP (aggiornare il trigger della tabella Product) | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
USE [Designbest-Commerce] | |||
GO | |||
SET ANSI_NULLS ON | |||
GO | |||
SET QUOTED_IDENTIFIER ON | |||
GO | |||
ALTER TRIGGER [dbo].[Product_ChangeQuantity] | |||
ON [dbo].[Product] | |||
AFTER UPDATE | |||
AS | |||
BEGIN | |||
SET NOCOUNT ON; | |||
DECLARE @OccNopID INT = 0, @NumPezziAggiornato INT; | |||
IF UPDATE([StockQuantity]) | |||
BEGIN | |||
SELECT @OccNopID = ProductBargainCore.NopID FROM Db_ProductBargain as ProductBargainCore | |||
WHERE ProductBargainCore.NopID IN ( | |||
SELECT i.Id FROM inserted i | |||
) | |||
IF @OccNopID = 0 return; | |||
SELECT @NumPezziAggiornato = i.StockQuantity FROM inserted i; | |||
UPDATE Db_ProductBargain | |||
SET Db_ProductBargain.Pieces = @NumPezziAggiornato | |||
WHERE Db_ProductBargain.NopID = @OccNopID | |||
PRINT 'TRIGGER [Product_ChangeQuantity] ESEGUITO e Aggiornato su TP' | |||
END | |||
ELSE | |||
BEGIN | |||
PRINT 'TRIGGER [Product_ChangeQuantity] ESEGUITO, ma nessuna modifica su TP' | |||
END | |||
END | |||
</syntaxhighlight> | |||
</div> | |||
* Aggiungere e mettere gli '''indici fulltext''' (sotto ''Storage'') per le viste <code>FT_Shops_IT</code>, <code>FT_Shops_EN</code>, <code>FT_Shops_DE</code> e <code>FT_Shops_FR</code> | |||
* Query di '''pulizia dati''', database <code>DesignbestCore</code> | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
-- Pulizia campo ausiliario per oldSelectionID | |||
UPDATE Product SET [Weight] = 0 | |||
-- Pulizia campi sporchi TpApproved = 1 quando prodotto non visibile | |||
UPDATE ProductBargain SET TpApproved = 0, OutletApproved = 0, CommerceApproved = 0, OutletToModerate = NULL, CommerceToModerate = NULL | |||
FROM ProductBargain | |||
INNER JOIN ProductCulture ON ProductBargain.ProductId = ProductCulture.ProductId | |||
WHERE ProductCulture.Visible = 0 AND ProductCulture.LanguageId = 1 | |||
</syntaxhighlight> | |||
</div> | |||
* Creazione delle shopnet dei '''Flagshipstore''', database <code>DesignbestCore</code> | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
DECLARE @ShopNetID INT, | |||
@ID INT, | |||
@Name NVARCHAR(200), | |||
@RagSociale VARCHAR(200), | |||
@Referente NVARCHAR(100), | |||
@Mail NVARCHAR(255) | |||
SELECT @ShopNetID = ID FROM ShopNet WHERE RagSociale = 'Anagrafiche' | |||
DECLARE @CurrentShopNetID INT | |||
DECLARE my_cursor CURSOR FOR | |||
SELECT ID, [Name] AS ShopNetName, [Name] AS RagSociale, Referente, Mail | |||
FROM ShopPoint | |||
WHERE ShopNetID = @ShopNetID AND VisibilityTypeID = 0 AND Visible = 1 | |||
OPEN my_cursor; | |||
FETCH NEXT FROM my_cursor INTO @ID, @Name, @RagSociale, @Referente, @Mail | |||
WHILE @@FETCH_STATUS = 0 BEGIN | |||
INSERT INTO ShopNet | |||
SELECT @Name, @RagSociale, @Referente, '000000000005', @Mail, 'Flagship Store', | |||
0, | |||
NULL, | |||
0, | |||
NULL, | |||
0, | |||
0, | |||
NULL, | |||
NULL, | |||
@ID, -- Main ShopPoint | |||
NULL, -- PermalinkWP | |||
NULL -- PermalinkNop | |||
SELECT @CurrentShopNetID = @@IDENTITY | |||
UPDATE ShopPoint SET ShopNetID = @CurrentShopNetID WHERE ID = @ID | |||
--SELECT * FROM ShopNet WHERE ID = @CurrentShopNetID | |||
FETCH NEXT FROM my_cursor INTO @ID, @Name, @RagSociale, @Referente, @Mail | |||
END | |||
CLOSE my_cursor; | |||
DEALLOCATE my_cursor; | |||
</syntaxhighlight> | |||
</div> | |||
* '''CustomSorting''', tabella che per ora regola il sort dei prodotti da scheda catalogo, database <code>Designbest</code> | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
TRUNCATE TABLE _DesignbestCore.dbo.CustomSorting | |||
INSERT INTO _DesignbestCore.dbo.CustomSorting | |||
SELECT Product.ID, 'Product', 'ManufacturerProducts', Product.ManufacturerSort | |||
FROM Product | |||
INNER JOIN Manufacturer ON Product.ManufacturerID = Manufacturer.ID | |||
WHERE Product.Visible = 1 AND Manufacturer.Visible = 1 AND Product.ManufacturerSort <> 50 | |||
</syntaxhighlight> | |||
</div> | |||
==== Operazione per Outlet WP, Nop Commerce e Lead manager ==== | |||
* Outlet Plugin - Usare il pulsante '''POST IMPORT SKU CHANGE''' per cambiare tutti gli SKU da <code>old_bargainID</code> a nuovo <code>ProductID</code> di DesignbestCore. | |||
* Nop - Sql, copiare il '''trigger''' come specificato nell'ultimo punto del paragrafo precedente (verificare esistenza '''utente''' <code>wmuser</code> su <code>_DesignbestCore</code>) | |||
* LM - Backuppare database <code>LeadManager</code> con il comando ''"Export Data-tier Application"'' in un file <code>bacpac</code> | |||
* LM - Query per <br/> | |||
::modificare il campo <code>category.code</code> con i nuovi id ambiente ai 23 record presenti che non iniziano con ''O'',<br/> | |||
::modificare il campo <code>product.code</code> con i nuovi id occasione, lanciare i risultati di queste due query come update su database <code>LeadManager</code> | |||
<br/><br/> | |||
'''N.B.''' Se si fa una import questa parte risutla sbagliata. Conviene ripristinare il DB da produzione e poi lanciare queste. | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
-- Lead Manager - Adeguamento nuovi ID category e product (occasioni) (lanciare su database DesignbestCore e eseguire l'output su LeadManager) | |||
SELECT 'UPDATE category SET code = ''' + CAST(ID AS VARCHAR(10)) + ''' WHERE code = ''' + CAST(temp_old_id AS VARCHAR(10)) + ''' AND category = ''' + [Name] + '''' | |||
FROM Category | |||
WHERE CategoryLevel = 1 | |||
-- REMEMBER: l'esecuzione di queste update dura quasi 7 minuti (lanciare su database DesignbestCore e eseguire l'output su LeadManager) | |||
SELECT 'UPDATE product SET code = ''' + CAST(ProductId AS VARCHAR(10)) + ''' WHERE code = ''O' + CAST(old_BargainID AS VARCHAR(10)) + '''' | |||
FROM ProductBargain | |||
WHERE old_BargainID <> 0 | |||
-- category change in product (database LeadManager) | |||
UPDATE product SET categoryID = 2 WHERE categoryID = 23 | |||
UPDATE product SET categoryID = 17 WHERE categoryID = 24 | |||
UPDATE product SET categoryID = 2 WHERE categoryID = 28 | |||
UPDATE product SET categoryID = 2 WHERE categoryID = 30 | |||
UPDATE product SET categoryID = 2 WHERE categoryID = 36 | |||
UPDATE product SET categoryID = 10 WHERE categoryID = 37 | |||
UPDATE product SET categoryID = 6 WHERE categoryID = 40 | |||
UPDATE product SET categoryID = 20 WHERE categoryID = 41 | |||
UPDATE product SET categoryID = 12 WHERE categoryID = 44 | |||
UPDATE product SET categoryID = 1 WHERE categoryID = 45 | |||
UPDATE product SET categoryID = 2 WHERE categoryID = 46 | |||
UPDATE product SET categoryID = 3 WHERE categoryID = 47 | |||
UPDATE product SET categoryID = 4 WHERE categoryID = 48 | |||
UPDATE product SET categoryID = 5 WHERE categoryID = 49 | |||
UPDATE product SET categoryID = 6 WHERE categoryID = 50 | |||
UPDATE product SET categoryID = 7 WHERE categoryID = 51 | |||
UPDATE product SET categoryID = 8 WHERE categoryID = 52 | |||
UPDATE product SET categoryID = 20 WHERE categoryID = 53 | |||
UPDATE product SET categoryID = 1 WHERE categoryID = 54 | |||
UPDATE product SET categoryID = 10 WHERE categoryID = 55 | |||
UPDATE product SET categoryID = 11 WHERE categoryID = 56 | |||
UPDATE product SET categoryID = 12 WHERE categoryID = 57 | |||
UPDATE product SET categoryID = 13 WHERE categoryID = 58 | |||
UPDATE product SET categoryID = 14 WHERE categoryID = 59 | |||
UPDATE product SET categoryID = 18 WHERE categoryID = 60 | |||
UPDATE product SET categoryID = 19 WHERE categoryID = 61 | |||
UPDATE product SET categoryID = 21 WHERE categoryID = 62 | |||
UPDATE product SET categoryID = 22 WHERE categoryID = 63 | |||
-- category change in repository | |||
UPDATE repository SET categoryID = 2 WHERE categoryID = 23 | |||
UPDATE repository SET categoryID = 17 WHERE categoryID = 24 | |||
UPDATE repository SET categoryID = 2 WHERE categoryID = 28 | |||
UPDATE repository SET categoryID = 2 WHERE categoryID = 30 | |||
UPDATE repository SET categoryID = 2 WHERE categoryID = 36 | |||
UPDATE repository SET categoryID = 10 WHERE categoryID = 37 | |||
UPDATE repository SET categoryID = 6 WHERE categoryID = 40 | |||
UPDATE repository SET categoryID = 20 WHERE categoryID = 41 | |||
UPDATE repository SET categoryID = 12 WHERE categoryID = 44 | |||
UPDATE repository SET categoryID = 1 WHERE categoryID = 45 | |||
UPDATE repository SET categoryID = 2 WHERE categoryID = 46 | |||
UPDATE repository SET categoryID = 3 WHERE categoryID = 47 | |||
UPDATE repository SET categoryID = 4 WHERE categoryID = 48 | |||
UPDATE repository SET categoryID = 5 WHERE categoryID = 49 | |||
UPDATE repository SET categoryID = 6 WHERE categoryID = 50 | |||
UPDATE repository SET categoryID = 7 WHERE categoryID = 51 | |||
UPDATE repository SET categoryID = 8 WHERE categoryID = 52 | |||
UPDATE repository SET categoryID = 20 WHERE categoryID = 53 | |||
UPDATE repository SET categoryID = 1 WHERE categoryID = 54 | |||
UPDATE repository SET categoryID = 10 WHERE categoryID = 55 | |||
UPDATE repository SET categoryID = 11 WHERE categoryID = 56 | |||
UPDATE repository SET categoryID = 12 WHERE categoryID = 57 | |||
UPDATE repository SET categoryID = 13 WHERE categoryID = 58 | |||
UPDATE repository SET categoryID = 14 WHERE categoryID = 59 | |||
UPDATE repository SET categoryID = 18 WHERE categoryID = 60 | |||
UPDATE repository SET categoryID = 19 WHERE categoryID = 61 | |||
UPDATE repository SET categoryID = 21 WHERE categoryID = 62 | |||
UPDATE repository SET categoryID = 22 WHERE categoryID = 63 | |||
-- Tronchiamo config_company_category | |||
TRUNCATE TABLE config_company_category | |||
-- Cancelliamo le category outlet | |||
DELETE FROM category WHERE categoryID >= 23 | |||
-- Ci inseriamo tutto di tutti | |||
INSERT INTO config_company_category(companyID, categoryID) | |||
SELECT DISTINCT company.companyID, category.categoryID | |||
FROM category, company | |||
ORDER BY company.companyID, category.categoryID | |||
</syntaxhighlight> | |||
</div> | |||
==== Eccezione 44334 (DEPRECATO) ==== | |||
La shop net di test, 44334, dopo l'import risulta "azzoppata". Bisogna mettere i record dei suoi Manufacturer Custom dentro al mapping: | |||
<syntaxhighlight lang="sql"> | |||
INSERT INTO Mapping_ShopPoint_Manufacturer_Ext(Context,ShopPointID,ManufacturerID,DisplayOrder) | |||
SELECT 44334, 44334, Manufacturer_Ext.ID, 100 | |||
FROM Manufacturer_Ext | |||
WHERE Context = 44334 | |||
</syntaxhighlight> | |||
==== Tabelle Pre-popolate ==== | |||
Queste tabelle sono statiche e non vengono toccate dall'importazione: | |||
* Agent | |||
* ShopNetStatus | |||
* City | |||
* Province | |||
* Region | |||
* Language | |||
* Selection | |||
* ContactRoles | |||
* ManufacturerContractType | |||
==== Cosa manca ==== | |||
L'importazione lascia vuote le tabelle:<br/> | |||
* Mapping_Selection_ShopPoint | |||
* ShopPointSponsor | |||
==== SEO Title e Description ==== | |||
Le nuove tabelle ospitano i campi MetTitle e MetaDescription, nell'importazione vengono lasciati vuoti.<br/> | |||
Questi dati li abbiamo, stanno nella <code>MetaTagsHelper</code>, dobbiamo successivamente pensare a come importarli nei campi giusti. | |||
===== SEO Regole ===== | |||
Creata una tabella '''SeoRules''' culturizzata tramite LanguageId. | |||
=== Full Text Search === | |||
La full text ha bisogno di tabelle con unica chiave primaria e i campi testuali sui quali lavorare.<br/> | |||
Nel progetto precedente (WM5) utilizzavamo delle '''viste indicizzate''', in questo non è più possibile farlo perché la struttura delle nuove tabelle implica delle ''self-join'' che per la fulltext non sono ammesse.<br/> | |||
Non ci resta che optare per la creazione di tabelle apposite per la ricerca che dovranno essere mantenute aggiornate con qualche task schedulato.<br/> | |||
==== Prodotti ==== | |||
Le tabelle implicate sono | |||
* <code>FT_Products_IT</code> | |||
* <code>FT_Products_EN</code> | |||
* <code>FT_Products_FR</code> | |||
* <code>FT_Products_DE</code> | |||
* <code>FT_Products_IT_Ext</code> | |||
* <code>FT_Products_EN_Ext</code> | |||
* <code>FT_Products_FR_Ext</code> | |||
* <code>FT_Products_DE_Ext</code> | |||
e vengono aggiornate (con MERGE) dalla SP <code>FullText_Products_Update</code> e dalla SP <code>FullText_Products_Update_Ext</code> (in caso di ''contesto TP'') | |||
Per effettuare una '''ricerca prodotti''' utilizzare la <code>FullText_Products_Search</code> oppure <code>FullText_Products_Search_Ext</code> (in caso di ''contesto TP'') | |||
==== Negozi ==== | |||
Qui rimaniamo col vecchio approccio delle '''viste indicizzate''' dato che non ci sono self-join al momento.<br/> | |||
Script di creazione delle viste: | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
DROP VIEW IF EXISTS dbo.FT_Shops_IT | |||
GO | |||
DROP VIEW IF EXISTS dbo.FT_Shops_EN | |||
GO | |||
DROP VIEW IF EXISTS dbo.FT_Shops_FR | |||
GO | |||
DROP VIEW IF EXISTS dbo.FT_Shops_DE | |||
GO | |||
-- ITALIA | |||
CREATE VIEW dbo.FT_Shops_IT WITH SCHEMABINDING | |||
AS | |||
SELECT | |||
ShopPoint.ID, | |||
ShopPoint.[Name], | |||
ShopNet.[Name] AS ShopNetName, | |||
LocDescription.[LocaleValue] AS [Description], | |||
ShopPoint.Referente, | |||
ShopPoint.[Address], | |||
ShopPoint.Phone, | |||
ShopPoint.Mail, | |||
ShopPoint.BackofficeNotes, | |||
Region.[Name] AS Region, | |||
Province.[Name] AS Province, | |||
City.[Name] AS City, | |||
ShopPoint.Visible | |||
FROM dbo.ShopPoint | |||
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID | |||
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID | |||
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID | |||
INNER JOIN dbo.Region ON Province.RegionID = Region.ID | |||
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 1 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description' | |||
WHERE ShopPoint.VisibilityTypeID = 0 | |||
GO | |||
CREATE UNIQUE CLUSTERED INDEX UQ_ID | |||
ON dbo.FT_Shops_IT(ID) | |||
GO | |||
-- WORLD | |||
CREATE VIEW dbo.FT_Shops_EN WITH SCHEMABINDING | |||
AS | |||
SELECT | |||
ShopPoint.ID, | |||
ShopPoint.[Name], | |||
ShopNet.[Name] AS ShopNetName, | |||
LocDescription.[LocaleValue] AS [Description], | |||
ShopPoint.Referente, | |||
ShopPoint.[Address], | |||
ShopPoint.Phone, | |||
ShopPoint.Mail, | |||
ShopPoint.BackofficeNotes, | |||
Region.[Name] AS Region, | |||
Province.[Name] AS Province, | |||
City.[Name] AS City, | |||
ShopPoint.Visible | |||
FROM dbo.ShopPoint | |||
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID | |||
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID | |||
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID | |||
INNER JOIN dbo.Region ON Province.RegionID = Region.ID | |||
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 2 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description' | |||
WHERE ShopPoint.VisibilityTypeID = 0 | |||
GO | |||
CREATE UNIQUE CLUSTERED INDEX UQ_ID | |||
ON dbo.FT_Shops_EN(ID) | |||
GO | |||
-- FRANCIA | |||
CREATE VIEW dbo.FT_Shops_FR WITH SCHEMABINDING | |||
AS | |||
SELECT | |||
ShopPoint.ID, | |||
ShopPoint.[Name], | |||
ShopNet.[Name] AS ShopNetName, | |||
LocDescription.[LocaleValue] AS [Description], | |||
ShopPoint.Referente, | |||
ShopPoint.[Address], | |||
ShopPoint.Phone, | |||
ShopPoint.Mail, | |||
ShopPoint.BackofficeNotes, | |||
Region.[Name] AS Region, | |||
Province.[Name] AS Province, | |||
City.[Name] AS City, | |||
ShopPoint.Visible | |||
FROM dbo.ShopPoint | |||
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID | |||
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID | |||
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID | |||
INNER JOIN dbo.Region ON Province.RegionID = Region.ID | |||
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 3 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description' | |||
WHERE ShopPoint.VisibilityTypeID = 0 | |||
GO | |||
CREATE UNIQUE CLUSTERED INDEX UQ_ID | |||
ON dbo.FT_Shops_FR(ID) | |||
GO | |||
-- GERMANIA | |||
CREATE VIEW dbo.FT_Shops_DE WITH SCHEMABINDING | |||
AS | |||
SELECT | |||
ShopPoint.ID, | |||
ShopPoint.[Name], | |||
ShopNet.[Name] AS ShopNetName, | |||
LocDescription.[LocaleValue] AS [Description], | |||
ShopPoint.Referente, | |||
ShopPoint.[Address], | |||
ShopPoint.Phone, | |||
ShopPoint.Mail, | |||
ShopPoint.BackofficeNotes, | |||
Region.[Name] AS Region, | |||
Province.[Name] AS Province, | |||
City.[Name] AS City, | |||
ShopPoint.Visible | |||
FROM dbo.ShopPoint | |||
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID | |||
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID | |||
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID | |||
INNER JOIN dbo.Region ON Province.RegionID = Region.ID | |||
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 4 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description' | |||
WHERE ShopPoint.VisibilityTypeID = 0 | |||
GO | |||
CREATE UNIQUE CLUSTERED INDEX UQ_ID | |||
ON dbo.FT_Shops_DE(ID) | |||
GO | |||
</syntaxhighlight> | |||
</div> | |||
Le viste implicate sono | |||
* <code>FT_Shops_IT</code> | |||
* <code>FT_Shops_EN</code> | |||
* <code>FT_Shops_FR</code> | |||
* <code>FT_Shops_DE</code> | |||
Per effettuare una '''ricerca negozi''' utilizzare la <code>FullText_Shops_Search</code> | |||
=== Tabelle senza vincoli === | |||
Qui sotto l'elenco delle tabelle sconnesse dai cascading, che devono essere trattate separatamente in caso di cancellazione dati | |||
* <code>LocalizedProperty</code> | |||
* <code>LocalizedProperty_Ext</code> | |||
* <code>Picture</code> | |||
* <code>Picture_Ext</code> | |||
* <code>CustomSorting</code> | |||
* <code>EntityDeepening</code> | |||
* <code>NopMapping_ShopNets</code> | |||
* <code>NopMapping_Manufs</code> | |||
* <code>NopMapping_Designers</code> | |||
* <code>NopMapping_Categories</code> | |||
== Sul Server == | |||
È necessario installare '''.NET 8.0''', la versione '''Hosting Bundle''' da https://dotnet.microsoft.com/en-us/download/dotnet/8.0<br/> | |||
Il server non chiede riavvio.<br/> | |||
Per verificare che il framework sia presente digitare in una console: | |||
<syntaxhighlight lang="bash">dotnet --list-runtimes</syntaxhighlight> | |||
per vedere '''.NET 8''' | |||
== Progetto VS == | |||
=== Program.cs === | |||
Nel file <code>Program.cs</code> pregenerato aggiungere i seguenti concetti | |||
==== Sessione ==== | |||
In un progetto Net.Core la sessione parte disabilitata.<br/> | |||
Per '''iniettare la sessione''' è necessario '''attivare un gestore della cache''' integrato (<code>IDistributedCache</code>) '''e la sessione''' stessa con le sue opzioni di configurazione | |||
come da esempio | |||
<syntaxhighlight lang="c#"> | |||
builder.Services.AddDistributedMemoryCache(); //L'implementazione IDistributedCache viene usata come archivio di backup per la sessione | |||
builder.Services.AddSession(options => { | |||
//options.IdleTimeout = TimeSpan.FromSeconds(10); // default 20 minuti | |||
//options.Cookie.HttpOnly = true; // default true | |||
options.Cookie.Name = ".Designbest.Session"; // nome del cookie di sessione | |||
options.Cookie.IsEssential = true; | |||
}); | |||
</syntaxhighlight> | |||
Dopodiché è necessario attivarla inserendo <code>app.UseSession()</code>:<br/> | |||
L' '''ordine del middleware è importante'''.<br/> | |||
Chiamare <code>UseSession</code> dopo e <code>UseRouting</code> prima di <code>MapRazorPages</code> e <code>MapDefaultControllerRoute</code> | |||
<syntaxhighlight lang="c#"> | |||
// ... | |||
app.UseAuthorization(); | |||
app.UseSession(); // attivazione sessione | |||
app.MapRazorPages(); | |||
app.Run(); | |||
</syntaxhighlight> | |||
==== HttpContextAccessor e HttpClient ==== | |||
Abilitare questi servizi | |||
* <code>HttpContextAccessor </code> permette di accedere all' '''HttpContext''' da qualsiasi classe. | |||
* <code>HttpClient</code> permette di fare request. | |||
<syntaxhighlight lang="c#"> | |||
builder.Services.AddHttpContextAccessor(); // per avere l'oggetto IHttpContextAccessor nelle classi | |||
builder.Services.AddHttpClient(); // per avere l'oggetto IHttpClientFactory nelle classi | |||
</syntaxhighlight> | |||
==== Services/Dipendenze ==== | |||
Implementare tutti i servizi bene separati utilizzando il '''pattern Interfaccia-Implementazione'''.<br/> | |||
Per abilitarli nel progetto, segnalare ''interfaccia'' e ''implementazione'' come segue: | |||
<syntaxhighlight lang="c#"> | |||
builder.Services.AddScoped<IDesignbestContext, DesignbestContext>(); | |||
builder.Services.AddScoped<IGeolocalization, MaxMindGeolocalization>(); | |||
</syntaxhighlight> | |||
Ci sono 3 diverse '''durate del servizio''' | |||
* '''Temporaneo''' <code>builder.Services.AddTransient(..)</code> <br/> istanza normale che muore alla fine dello scope della funzione in cui viene chiamata | |||
* '''Con Ambito''' <code>builder.Services.AddScoped(..)</code> <br/> viene utilizzata la stessa istanza per tutta la durata della richiesta utente | |||
* '''Singleton''' <code>builder.Services.AddSingleton(...)</code> <br/> viene utilizzata la stessa istanza finche IIS non viene stoppato | |||
=== Javascript === | |||
Tutti i moduli, le funzioni e gli eventi del sito sono gestiti dai nostri script che non dipendono da terze parti (tantomeno da jQuery che per i dispositivi mobile è solo un rallentamento).<br/> | |||
Sfruttiamo le ultime funzioni standardizzate in '''ECMASCRIPT 6''', con le definizioni di classi, metodi, membri di classe e chiamate asincrone.<br/><br/> | |||
Il cosiddetto '''thread principale''' prevede un bundle minificato delle nostre risorse principali. | |||
=== Compilazione a runtime === | |||
Abilitare la compilazione a runtime solo in ambiente di sviluppo. | |||
* Scaricare il pacchetto nuget <code>Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation</code> | |||
* Aggiungere il seguente codice al '''Program.cs''' | |||
<syntaxhighlight lang="c#"> | |||
if (builder.Environment.IsDevelopment()) { | |||
builder.Services.AddRazorPages().AddRazorRuntimeCompilation(); | |||
} else { | |||
builder.Services.AddRazorPages(); | |||
} | |||
</syntaxhighlight> | |||
==== Problema CopyRefAssembliesToPublishDirectory ==== | |||
Se dopo aver attivato la compilazione a Runtime il progetto dà un errore che parla di | |||
<pre>CopyRefAssembliesToPublishDirectory </pre> | |||
il problema potrebbe essere che dalle viste è stata fatta una <code>@inject</code> di un Service del quale non è stata specificata la <code>@using</code> all'interno di <code>/Pages/_ViewImports.cshtml</code>.<br/><br/> | |||
Mi è successo con <code>IConfiguration</code>, ho dovuto aggiungere questa riga | |||
<syntaxhighlight lang="c#"> | |||
@using Microsoft.Extensions.Configuration | |||
</syntaxhighlight> | |||
=== Environment - Development, Staging, Production === | |||
Per decidere con quale '''environment''' la web app deve essere pubblicata è necessario editare manualmente il '''publish profile''' aggiungendo la seguente | |||
<syntaxhighlight lang="xml"> | |||
<Project> | |||
<PropertyGroup> | |||
<!-- Altre direttive --> | |||
<EnvironmentName>Staging</EnvironmentName> | |||
</PropertyGroup> | |||
</Project> | |||
</syntaxhighlight> | |||
I valori consentiti sono | |||
* '''Production''' | |||
* '''Staging''' | |||
* '''Development''' - L'unico che permette la visualizzazione degli errori | |||
=== Passare a .Net Core 8 === | |||
https://learn.microsoft.com/en-us/aspnet/core/migration/70-80?view=aspnetcore-8.0&tabs=visual-studio | |||
Versione attuale delle 17:44, 9 gen 2024
Goal
[modifica]Realizzare una versione agile e super performante di Designbest usando le ultime tecnologie Microsoft disponibili (.NET Core 6 .NET Core 7).
Page Speed Insights
[modifica]- Riduzione del TTFB (Time To The First Byte)
- Immagini di dimensioni adeguate al dispositivo (Imagekit.io)
- Specificare width e height per ogni immagine
- Specificare alt per ogni immagine
- Minificazione delle dipendenze da terze parti (animazioni fatte in linguaggio nativo piuttosto che utilizzo di plugin esterni)
- Raggruppamento (bundling) delle risorse CSS e Javascript
- Minificazione delle risorse raggruppate
- Ottimizzazione del tempo di Javascript (tramite promise e processi async).
- Inclusione delle risorse Javascript strettamente necessarie alla pagina (e non librerie complete)
- Inclusione delle risorse CSS strettamente necessarie alla pagina.
- Precaricamento della Largest Contentful Paint per ogni pagina
- Immagini e iframe lazy (caricamento pigro)
- Riduzione elementi in pagina tramite caricamenti asincroni su richiesta (modali, librerie)
- Compressione dell'HTML generato
- Compressione delle risorse statiche
- manifest con info per creare un PWA
- Velocità media caricamento pagina costantemente < 3s
SEO
[modifica]- Semantica della pagina (h1, h2, section, main content, aside content, header, footer, nav).
- Gestione di meta tag seo description, title
- Href Lang (link seo che indicano la stessa pagina in lingua differente)
- Logiche di redirect applicative (con listing vuoti => redirect al listing superiore, negozio chiuso => redirect a listing di quella città)
- Structured Data (validate da Google)
- Breadcrumbs
- Loghi (nome organizzazione e canali social)
- Snippet di prodotto
- Snippet di recensioni (stelline)
- Casella di ricerca
- Caroselli (lista di prodotti)
- Attività locali (negozi)
Lato Applicativo
[modifica]- Riduzione del TTFB (Time To The First Byte) tramite controllo completo sulle query e le stored procedure
- Logiche di redirect applicative (con listing vuoti => redirect al listing superiore, negozio chiuso => redirect a listing di quella città)
- Gestione geolocalizzazione (database MaxMind aggiornato)
- Algoritmi di ordinamento prodotti
- Gestione Ad manager (DFP secondo logiche custom di targeting ambiente/zona/manu)
- Gestione logiche 4Dem (ultimo prodotto visto, data, spedizione flusso newsletter)
- Bottoni Sponsor che appaiono nell'ambiente preciso
Database
[modifica]Accorpamento database:
- Designbest
- Trovaprodotti
Tabelle
[modifica]- Language
- LocalizedProperty
- Category
- ManufacturerContractType
- Manufacturer
- Product
- ProductCulture
- ProductBargain
- ProductBargainCulture
- ProductProperty
- ProductPropertyValue
- Mapping_Category_ProductProperty
- Mapping_Product_ProductPropertyValue
- ShopPointProperty
- ShopPointPropertyValue
- Mapping_ShopPoint_ShopPointPropertyValue
- ShopPointSponsor
- Agent
- ShopNetStatus
- ShopNet
- ShopPointVisibilityType
- ShopPoint
- Trovaprodotti
- TrovaprodottiCulture
- Region
- Province
- City
- Picture
- Customer
- Mapping_Customer
- User
- UserUnregistered
- Wishlist
- Mapping_Wishlist_Product
- Selection
- Mapping_Selection_Product
- ProductThumbs
- History_Email
Popolamento Region/Province/City
[modifica]Attualmente abbiamo importato i dati sporchi così come sono con la seguente query
INSERT INTO DesignbestCore.dbo.Region
SELECT Region.ID,
CASE
WHEN Region.CountryID = 3 THEN 1
WHEN Region.CountryID = 2 THEN 3
WHEN Region.CountryID = 4 THEN 4
WHEN Region.CountryID = 1000 THEN 2
END AS LanguageID,
Region.Coordinates,
Region.LocalizerCode,
RegionCulture.Region AS [Name],
LOWER(RegionCulture.SEORegion) AS SeoName
FROM Region
INNER JOIN RegionCulture ON Region.ID = RegionCulture.RegionID
INSERT INTO DesignbestCore.dbo.Province
SELECT Province.ID,
CASE
WHEN Province.CountryID = 3 THEN 1
WHEN Province.CountryID = 2 THEN 3
WHEN Province.CountryID = 4 THEN 4
WHEN Province.CountryID = 1000 THEN 2
END AS LocalizationID,
Province.Coordinates,
Province.ProvinceCode,
Province.RegionID,
ProvinceCulture.Province AS Name,
LOWER(ProvinceCulture.SEOProvince) AS SeoName
FROM Province
INNER JOIN ProvinceCulture ON Province.ID = ProvinceCulture.ProvinceID
INNER JOIN Region ON Province.RegionID = Region.ID
INSERT INTO DesignbestCore.dbo.City
SELECT City.ID,
CASE
WHEN City.CountryID = 3 THEN 1
WHEN City.CountryID = 2 THEN 3
WHEN City.CountryID = 4 THEN 4
WHEN City.CountryID = 1000 THEN 2
END AS LocalizationID,
City.ProvinceID,
City.City AS [Name],
LOWER(City.SeoCity) AS SeoName
FROM City
INNER JOIN Province ON Province.ID = City.ProvinceID
INNER JOIN Region ON Province.RegionID = Region.ID
Custom Properties
[modifica]L'idea è quella di creare da una semplicissima struttura di tabelle
- ProductProperty
- ProductPropertyValue
- Mapping_Category_ProductProperty
- Mapping_Product_ProductPropertyValue
l'assegnazione di qualsiasi proprietà (stile, finitura, colori, decori, formati, proprietà di tipologia, designer, campi custom)
Per ognuna di queste custom property è possibile specificare
- Nome
- Nome SEO
- Valore
- Immagine (opzionale, gestita da
Pictures)
Stesso concetto su tabelle separate per gli ShopPoint
- ShopPointProperty
- ShopPointPropertyValue
- Mapping_ShopPoint_ShopPointPropertyValue
assegnazione delle proprietà come gli ShopPointServices
Per quanto riguarda gli ShopPointServices, purtroppo abbiamo ereditato una logica per la quale, se esiste il valore PropertyValue nel mapping, allora il negozio possiede quella Property. Di conseguenza, se il negozio possiede una certa Property e non specifica nessun suo valore, è necessario inserire un PropertyValue vuoto.
Schifezze
[modifica]Controllare i valori delle custom properties perché il database è pieno di duplicati assegnati a prodotti diversi.
Ad esempio la ProductProperty Caratteristiche con SeoName Caratteristiche - Sedie c'è due volte con ID differenti.
Per rimediare si potrebbero assegnare tutti i prodotti ad un unico di questi ID e cancellare l'altro doppio.
Questa roba fa casino nel count dei prodotti nel telecomando
SELECT [Name],[SeoName], COUNT(SeoName) AS Duplic
FROM ProductPropertyValue
GROUP BY [Name], SeoName
ORDER BY Duplic DESC, [Name] ASC
ProductThumbs
[modifica]Questa tabella purtroppo è necessaria in quanto i listing prodotti di una tipologia sono troppo lenti (tempo esecuz > 1s).
Una singola riga rappresenta un ProductThumbanil comprensivo di tutti i dati necessari a creare l'anteprima quadrata, titolo, ambiente, tipo manu e dati per creare i link.
Sarà tenuta aggiornata in tempo reale dalla backoffice (no task schedulati).
Tabelle escluse
[modifica]Di seguito le tabelle che non riteniamo più utili:
ShopManufacturerExceptionShopContentExtra
Shop
[modifica]ShopNet Casi Particolari
[modifica]Attenzione che ogni SHOPNET deve avere una mail univoca altrimenti non viene caricato il Vendor su DOKAN (Wordpress vuole che le mail siano univoche per ogni utente).
Tutti i negozi ora appartengono obbligatoriamente ad una ShopNet Queste ShopNet in particolare:
- ShopNet Trovaprodotti Orfani Tutti i Trovaprodotti che non avevano una ShopNet. Hanno
Piva = '000000000000'. Ne viene creata una per ognuno. - ShopNet con PIVA nulla - Le vecchie ShopNet senza PIVA. Hanno
Piva = '000000000001' - ShopNet Anagrafiche - Contiene tutti gli ShopPoint di tipo != 0 che sono anagrafiche. Un padre per molti figli. Ha
Piva = '000000000003'. - ShopNet Estero - Tutti gli ShopPoint Esteri. Hanno
Piva = '000000000004'. Ne viene creata una per ognuno.
ShopFulltextAdditions
[modifica]Gestione della ricerca full-text dei negozi che aggiunge ambienti, tipologie e produttori alle chiavi di ricerca del singolo negozio.
Sarà necessario reimplementare tutto il passaggio con il task notturno che genera le righe?
E-mail duplicate in ShopNet differenti
[modifica]Attualmente ci sono dei casi di ShopNet che hanno il flag Enable_Outlet = 1 e la stessa email.
Questa situazione non va bene perché la sincronizzazione su Outlet Wordpress prevede che la mail sia univoca per ogni venditore (che corrisponde ad un user wp).
La backoffice, in caso di ShopNet Outlet Enabled, dovrà sempre controllare che la mail inserita non sia già presente.
Ecco un elenco dei casi scorretti presenti sulla nostra base di dati (DesignbestCore db):
SELECT MainMail, COUNT(MainMail) AS Quante
FROM ShopNet
WHERE Enable_Outlet = 1
GROUP BY MainMail
ORDER BY Quante DESC
Pictures
[modifica]La gestione delle immagini sarà basata su quella di Nop.
| Picture | ||
|---|---|---|
| ID | int | ID della picture |
| EntityID | int | ID dell'entità |
| EntityKeyGroup | nvarchar(100) | Nome della Tabella di riferimento |
| EntityKey | nvarchar(100) | Formato dell'immagine desiderato |
| EntityValue | nvarchar(255) | Nome dell'immagine |
| DisplayOrder | int | Ordinamento |
Tutte le immagini saranno in un unico folder
product-<EntityID>-<ID>.jpg
bargain-<EntityID>-<ID>.jpg
manufacturer-<EntityID>-<ID>.jpg
shop-<EntityID>-<ID>.jpgQuesta soluzione è meno leggibile dall'umano ma molto più elastica nel caso in cui ci siano diversi cambiamenti di ordine, master/alternative (evita di dover rinominare l'immagine su disco).
Esempio Volendo trovare tutte le immagini relative ad uno shop si potrebbe usare la seguente query:
SELECT *
FROM Picture
WHERE EntityID = 15314 AND KeyGroup = 'Shop'
Avendo come risultato qualcosa di simile.
bla bla bla ....
Convenzioni
[modifica]Per quanto riguarda i campi EntityGroup e EntityKey ecco cosa proponiamo
- ShopPoint
- cover
- logo
- gallery
- Product
- gallery
- square
- Manufacturer
- logo
- logo-square
- cover
- sponsored (bottone sponsor in HP)
- gallery
- Category
- ambient
- Trovaprodotti segue le stesse regole nelle tabelle _ext ma negli EntityValue cambia così
- gallery (prodotti a catalogo creati da cliente)
- productext-<context>
- logo
- manufacturerext-<context>
- gallery (prodotti a catalogo creati da cliente)
Entity Deepening - Approfondimenti delle entità
[modifica]| EntityDeepening | ||
|---|---|---|
| ID | int | ID della picture |
| LanguageID | int | Lingua |
| EntityID | int | ID dell'entità |
| EntityKeyGroup | nvarchar(100) | Nome della Tabella di riferimento |
| EntityKey | nvarchar(100) | Formato dell'immagine desiderato |
| ExternalID | int | ID esterno |
| Link | nvacrchar(255) | Link esterno |
| Title | nvacrchar(255) | Link esterno |
| ShortDescription | nvacrchar(255) | Descrizione breve |
| MediaLink | nvacrchar(255) | Link ad un media |
| DisplayOrder | int | Ordinamento |
La tabella EntityDeepening rappresenta un calderone generico per poter assegnare ad una qualsiasi nostra entità (Manu, Shop, Product ecc) i seguenti valori:
- Id esterno
- Link esterno
- Titolo articolo
- Descrizione breve articolo
- Foto (facoltativa)
che rappresentano approfondimenti su altre piattaforme (es. Magazine).
Verrà utilizzata per Brand Channel (manu) e Progetti (manu e shop).
Di seguito le chiavi EntityGroup ed EntityKey:
- Manufacturer
- brandchannel
- project
- ShopNet
- project
Keywords
[modifica]La tabella Keywords ha come obiettivo quello di estendere i match di una entità durante una fulltext (B&B => beb , Hartè => harte ecc...).
Di seguito le tipologie di entità, EntityGroup
- Manufacturer
- Product
- ShopPoint
Utilizzata per la ricerca fulltext , quei termini che hanno accenti o caratteri strani.
La feature è attiva solo per ITALIA, le parole "extra" vengono aggiunte alle tabelle/viste di ricerca Fulltext.
FullText_Products_UpdateFT_Products_ITFT_Shops_IT
Le selezioni
[modifica]Utilizziamo il concetto di selezione per definire gruppi di prodotti (oppure negozi ecc) al fine di poterli richiamare in quei moduli che attualmente hanno una logica circostanziale spesso gestiti con xml.
In questo modo possiamo creare N selezioni tramite il backoffice da usare nei casi più svariati.
Importazione dati
[modifica]Eseguire nell'ordine
- Eseguire script sul database
Designbestper normalizzare e copiare le Misure nel campoProductCulture.AdditionalNotes, 4 volte per tutte le lingue
/*
La procedura, per ogni prodotto, normalizza le misure in una unica stringa e ne copia il contenuto
dentro a ProductBargain.AdditionalNotes .
Ci mette circa 1:25min per la cultura italiana.
*/
BEGIN TRAN
DECLARE @Culture VARCHAR(5) = 'it-IT',
@LanguageId INT = 1
DECLARE @ProductID INT = 0,
@MeasuresCurrent NVARCHAR(2000),
@MeasuresValue NVARCHAR(2000)
DECLARE cur CURSOR FOR
SELECT ID FROM Product-- WHERE ID = 2
OPEN cur;
FETCH NEXT FROM cur INTO @ProductID
WHILE @@FETCH_STATUS = 0 BEGIN
SET @MeasuresValue = ''
DECLARE cur2 CURSOR FOR
SELECT '<p>' +MeasureCulture.DisplayName + ' ' + L_Product_Measure.[Value] + ' ' + MeasureCulture.Unit + '</p>'
FROM L_Product_Measure
INNER JOIN Measure ON L_Product_Measure.MeasureID = Measure.ID
INNER JOIN MeasureCulture ON Measure.ID = MeasureCulture.MeasureID AND MeasureCulture.Culture = @Culture
WHERE ProductID = @ProductID
ORDER BY MeasureCulture.Culture, Measure.Sort
OPEN cur2;
FETCH NEXT FROM cur2 INTO @MeasuresCurrent
WHILE @@FETCH_STATUS = 0 BEGIN
IF @MeasuresValue = '' BEGIN
SET @MeasuresValue = @MeasuresCurrent + ' '
END
ELSE BEGIN
SET @MeasuresValue += @MeasuresCurrent
END
FETCH NEXT FROM cur2 INTO @MeasuresCurrent
END
CLOSE cur2;
DEALLOCATE cur2;
-- Update di ProductCulture.AdditionalNotes
UPDATE ProductCulture
SET AdditionalNotes =
CASE WHEN @MeasuresValue = '' THEN NULL
ELSE @MeasuresValue
END
WHERE ProductID = @ProductID AND Culture = @Culture
PRINT CAST(@ProductID AS VARCHAR(10)) +':'+ @MeasuresValue
-- QUESTA PARTE SCRIVE DIRETTAMENTE SU DesignbestCore
--IF @MeasuresValue <> '' BEGIN
-- -- Tabella Product.Measures (lingua neutra, cioè ita)
-- IF @Culture = 'it-IT' BEGIN
-- UPDATE DesignbestCore.dbo.Product
-- SET Measures = CASE WHEN @MeasuresValue = '' THEN NULL
-- ELSE @MeasuresValue
-- END
-- WHERE ID = @ProductID-- AND Culture = @Culture
-- END
-- -- LocalizedProperty
-- UPDATE DesignbestCore.dbo.LocalizedProperty
-- SET LocaleValue = @MeasuresValue
-- WHERE EntityId = @ProductID AND LanguageId = @LanguageId AND LocaleKeyGroup = 'Product' AND LocaleKey = 'Measures'
--END
FETCH NEXT FROM cur INTO @ProductID
END
CLOSE cur;
DEALLOCATE cur;
COMMIT TRAN
- Reset_All_Tables (database
DesignbestCore) ~ 4 min - WM_CategorySynchCore
- Eliminare temporaneamente vincolo
FK_ProductBargain_ShopPointdaProductBargain - WM_ProductSynchCore ~ 3 min
- Fare la chiamata API a
https://www.designbestoutlet.com/wp-json/designbest-outlet/v1/listallvendorscon Postman e piazzare il file json restituito inD:\Temp\Outlet-Vendors.jsonsulla macchinaBIANCANEVE - Piazzare il file
homepage_highlights.xmlinD:\Temp\homepage_highlights.xml - WM_ShopNetSynchCore
- Ripristinare la chiave esterna
FK_ProductBargain_ShopPointsuProductBargain - Verificare che non ci siano designer con seoname duplicato sul database
Designbest, nel caso correggerli
SELECT SEODesigner, COUNT(SEODesigner) AS contegg
FROM Designer
where AllowSearch = 1 OR AllowSearchOutlet = 1
group by SEODesigner
order by contegg desc
- WM_ProductPropertySynchCore
- FullTextSearch, eseguire i comandi del paragrafo #Negozi nel database
DesignbestCore - WM_PicturesSynchCore (verificare aggiornamento path di XML BrandChannelPhotoGalleries su
D:\Temp) su BIANCANEVE - Rinominare fisicamente le 22 immagini degli ambienti aggiungendo PictureID generato al nome es
bagno-14-217177e copiarle in una cartella a parte (perché la pictures verrà cancellata) - WM_Nop_SyncCore (verificare i negozi nella
DesignbestCore.dbo.NopMapping_ShopNetsconfrontandoli con la vecchia) - WM_UsersCore
- WM_TpCustomSynchCore
- Cancellare tutte le immagini nella cartella
pictures(occhio, salvarsi prima le 22 immagini degli ambienti) - Copiare le 22 immagini ambiente che sono state salvate prima dentro alla nuova pictures
- Immagini occasioni - Eseguire script Python tramite
D:\Programs\MigrazioneDB_DBCore\DesignbestBargainMigration.batsu ARIEL (~ 10 min). - Immagini cover shop - Eseguire script Python
D:\Programs\MigrazioneDB_DBCore\DesignbestShopCoverAltMigration.pysu ARIEL - Utility_ProductThumbsMerge (database
DesignbestCore) - Immagini fisiche - Eseguire la query SQL per generare il file batch che sposterà i file, database
DesignbestCore(~ 3:40h)
SELECT
'IF EXIST "' + REPLACE(old_PicturePath, '/', '\') + '" (',
'xcopy /y "' + REPLACE(old_PicturePath, '/', '\') + '" ' + '"D:\WM3Resources\ImmaginiWM3\pictures\' + EntityValue + '-'+ CAST(EntityID AS VARCHAR(10)) + '-' + CAST(ID AS VARCHAR(10)) + '.jpg*"',
') ELSE ( echo ' + CAST(ID AS VARCHAR(10)) + ' BigPath >> missingimages.txt )'
FROM Picture
Update Width e Height delle Pictures - Eseguire script Python tramite.D:\Programs\python-CoreGetSetPicturesWidthHeight\main.pysu ARIEL (~ oo min)- Immagini selezioni TP - Eseguire script Python tramite
D:\Programs\MigrazioneDB_DBCore\DesignbestSelectionMigration.batsu ARIEL (~ 3 min). - Immagini fisiche selezioni - Eseguire la query SQL per generare il file batch che sposterà i file, database
DesignbestCore
SELECT
'IF EXIST "' + REPLACE(old_PicturePath, '/', '\') + '" (',
'xcopy /y "' + REPLACE(old_PicturePath, '/', '\') + '" ' + '"D:\WM3Resources\ImmaginiWM3\pictures\' + EntityValue + '-'+ CAST(EntityID AS VARCHAR(10)) + '-' + CAST(ID AS VARCHAR(10)) + '.jpg*"',
') ELSE ( echo ' + CAST(ID AS VARCHAR(10)) + ' BigPath >> missingimages2.txt )'
FROM Picture_Ext
WHERE old_PicturePath IS NOT NULL
- Update Width e Height delle Pictures delle Selezioni - Eseguire script Python tramite
D:\Programs\python-CoreGetSetPicturesWidthHeight\CoreGetSetWidthHeight.batsu ARIEL per lePicture_Ext(occhio allo script, eseguire la parte Picture_Ext e commentare l'altra). - Update Width e Height delle Pictures delle Selezioni - Eseguire script Python tramite
D:\Programs\python-CoreGetSetPicturesWidthHeight\CoreGetSetWidthHeight.batsu ARIEL per lePicture(~ 3h e 7 min) (occhio allo script, eseguire la parte Picture e commentare l'altra). - Volendo mentre si aspetta questo task è possibile fare la parte di LeadManager #Operazione per Outlet WP, Nop Commerce e Lead manager
- Rilanciare SP -> Utility_ProductThumbsMerge (database
DesignbestCore) - Rilanciare SP -> Utility_ProductThumbsMerge_Ext e FullText_Products_Update_Ext usando il seguente cursore
DECLARE @TrovaprodottiID int
DECLARE cur3 CURSOR FOR
SELECT DISTINCT Context FROM DesignbestCore.dbo.Product_Ext
OPEN cur3;
FETCH NEXT FROM cur3 INTO @TrovaprodottiID
WHILE @@FETCH_STATUS = 0 BEGIN
EXEC DesignbestCore.dbo.Utility_ProductThumbsMerge_Ext @TrovaprodottiID, 1
EXEC DesignbestCore.dbo.FullText_Products_Update_Ext @TrovaprodottiID
FETCH NEXT FROM cur3 INTO @TrovaprodottiID
END
CLOSE cur3;
DEALLOCATE cur3;
- SU NOP (aggiornare il trigger della tabella Product)
USE [Designbest-Commerce]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[Product_ChangeQuantity]
ON [dbo].[Product]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @OccNopID INT = 0, @NumPezziAggiornato INT;
IF UPDATE([StockQuantity])
BEGIN
SELECT @OccNopID = ProductBargainCore.NopID FROM Db_ProductBargain as ProductBargainCore
WHERE ProductBargainCore.NopID IN (
SELECT i.Id FROM inserted i
)
IF @OccNopID = 0 return;
SELECT @NumPezziAggiornato = i.StockQuantity FROM inserted i;
UPDATE Db_ProductBargain
SET Db_ProductBargain.Pieces = @NumPezziAggiornato
WHERE Db_ProductBargain.NopID = @OccNopID
PRINT 'TRIGGER [Product_ChangeQuantity] ESEGUITO e Aggiornato su TP'
END
ELSE
BEGIN
PRINT 'TRIGGER [Product_ChangeQuantity] ESEGUITO, ma nessuna modifica su TP'
END
END
- Aggiungere e mettere gli indici fulltext (sotto Storage) per le viste
FT_Shops_IT,FT_Shops_EN,FT_Shops_DEeFT_Shops_FR - Query di pulizia dati, database
DesignbestCore
-- Pulizia campo ausiliario per oldSelectionID
UPDATE Product SET [Weight] = 0
-- Pulizia campi sporchi TpApproved = 1 quando prodotto non visibile
UPDATE ProductBargain SET TpApproved = 0, OutletApproved = 0, CommerceApproved = 0, OutletToModerate = NULL, CommerceToModerate = NULL
FROM ProductBargain
INNER JOIN ProductCulture ON ProductBargain.ProductId = ProductCulture.ProductId
WHERE ProductCulture.Visible = 0 AND ProductCulture.LanguageId = 1
- Creazione delle shopnet dei Flagshipstore, database
DesignbestCore
DECLARE @ShopNetID INT,
@ID INT,
@Name NVARCHAR(200),
@RagSociale VARCHAR(200),
@Referente NVARCHAR(100),
@Mail NVARCHAR(255)
SELECT @ShopNetID = ID FROM ShopNet WHERE RagSociale = 'Anagrafiche'
DECLARE @CurrentShopNetID INT
DECLARE my_cursor CURSOR FOR
SELECT ID, [Name] AS ShopNetName, [Name] AS RagSociale, Referente, Mail
FROM ShopPoint
WHERE ShopNetID = @ShopNetID AND VisibilityTypeID = 0 AND Visible = 1
OPEN my_cursor;
FETCH NEXT FROM my_cursor INTO @ID, @Name, @RagSociale, @Referente, @Mail
WHILE @@FETCH_STATUS = 0 BEGIN
INSERT INTO ShopNet
SELECT @Name, @RagSociale, @Referente, '000000000005', @Mail, 'Flagship Store',
0,
NULL,
0,
NULL,
0,
0,
NULL,
NULL,
@ID, -- Main ShopPoint
NULL, -- PermalinkWP
NULL -- PermalinkNop
SELECT @CurrentShopNetID = @@IDENTITY
UPDATE ShopPoint SET ShopNetID = @CurrentShopNetID WHERE ID = @ID
--SELECT * FROM ShopNet WHERE ID = @CurrentShopNetID
FETCH NEXT FROM my_cursor INTO @ID, @Name, @RagSociale, @Referente, @Mail
END
CLOSE my_cursor;
DEALLOCATE my_cursor;
- CustomSorting, tabella che per ora regola il sort dei prodotti da scheda catalogo, database
Designbest
TRUNCATE TABLE _DesignbestCore.dbo.CustomSorting
INSERT INTO _DesignbestCore.dbo.CustomSorting
SELECT Product.ID, 'Product', 'ManufacturerProducts', Product.ManufacturerSort
FROM Product
INNER JOIN Manufacturer ON Product.ManufacturerID = Manufacturer.ID
WHERE Product.Visible = 1 AND Manufacturer.Visible = 1 AND Product.ManufacturerSort <> 50
Operazione per Outlet WP, Nop Commerce e Lead manager
[modifica]- Outlet Plugin - Usare il pulsante POST IMPORT SKU CHANGE per cambiare tutti gli SKU da
old_bargainIDa nuovoProductIDdi DesignbestCore. - Nop - Sql, copiare il trigger come specificato nell'ultimo punto del paragrafo precedente (verificare esistenza utente
wmusersu_DesignbestCore) - LM - Backuppare database
LeadManagercon il comando "Export Data-tier Application" in un filebacpac - LM - Query per
- modificare il campo
category.codecon i nuovi id ambiente ai 23 record presenti che non iniziano con O, - modificare il campo
product.codecon i nuovi id occasione, lanciare i risultati di queste due query come update su databaseLeadManager
- modificare il campo
N.B. Se si fa una import questa parte risutla sbagliata. Conviene ripristinare il DB da produzione e poi lanciare queste.
-- Lead Manager - Adeguamento nuovi ID category e product (occasioni) (lanciare su database DesignbestCore e eseguire l'output su LeadManager)
SELECT 'UPDATE category SET code = ''' + CAST(ID AS VARCHAR(10)) + ''' WHERE code = ''' + CAST(temp_old_id AS VARCHAR(10)) + ''' AND category = ''' + [Name] + ''''
FROM Category
WHERE CategoryLevel = 1
-- REMEMBER: l'esecuzione di queste update dura quasi 7 minuti (lanciare su database DesignbestCore e eseguire l'output su LeadManager)
SELECT 'UPDATE product SET code = ''' + CAST(ProductId AS VARCHAR(10)) + ''' WHERE code = ''O' + CAST(old_BargainID AS VARCHAR(10)) + ''''
FROM ProductBargain
WHERE old_BargainID <> 0
-- category change in product (database LeadManager)
UPDATE product SET categoryID = 2 WHERE categoryID = 23
UPDATE product SET categoryID = 17 WHERE categoryID = 24
UPDATE product SET categoryID = 2 WHERE categoryID = 28
UPDATE product SET categoryID = 2 WHERE categoryID = 30
UPDATE product SET categoryID = 2 WHERE categoryID = 36
UPDATE product SET categoryID = 10 WHERE categoryID = 37
UPDATE product SET categoryID = 6 WHERE categoryID = 40
UPDATE product SET categoryID = 20 WHERE categoryID = 41
UPDATE product SET categoryID = 12 WHERE categoryID = 44
UPDATE product SET categoryID = 1 WHERE categoryID = 45
UPDATE product SET categoryID = 2 WHERE categoryID = 46
UPDATE product SET categoryID = 3 WHERE categoryID = 47
UPDATE product SET categoryID = 4 WHERE categoryID = 48
UPDATE product SET categoryID = 5 WHERE categoryID = 49
UPDATE product SET categoryID = 6 WHERE categoryID = 50
UPDATE product SET categoryID = 7 WHERE categoryID = 51
UPDATE product SET categoryID = 8 WHERE categoryID = 52
UPDATE product SET categoryID = 20 WHERE categoryID = 53
UPDATE product SET categoryID = 1 WHERE categoryID = 54
UPDATE product SET categoryID = 10 WHERE categoryID = 55
UPDATE product SET categoryID = 11 WHERE categoryID = 56
UPDATE product SET categoryID = 12 WHERE categoryID = 57
UPDATE product SET categoryID = 13 WHERE categoryID = 58
UPDATE product SET categoryID = 14 WHERE categoryID = 59
UPDATE product SET categoryID = 18 WHERE categoryID = 60
UPDATE product SET categoryID = 19 WHERE categoryID = 61
UPDATE product SET categoryID = 21 WHERE categoryID = 62
UPDATE product SET categoryID = 22 WHERE categoryID = 63
-- category change in repository
UPDATE repository SET categoryID = 2 WHERE categoryID = 23
UPDATE repository SET categoryID = 17 WHERE categoryID = 24
UPDATE repository SET categoryID = 2 WHERE categoryID = 28
UPDATE repository SET categoryID = 2 WHERE categoryID = 30
UPDATE repository SET categoryID = 2 WHERE categoryID = 36
UPDATE repository SET categoryID = 10 WHERE categoryID = 37
UPDATE repository SET categoryID = 6 WHERE categoryID = 40
UPDATE repository SET categoryID = 20 WHERE categoryID = 41
UPDATE repository SET categoryID = 12 WHERE categoryID = 44
UPDATE repository SET categoryID = 1 WHERE categoryID = 45
UPDATE repository SET categoryID = 2 WHERE categoryID = 46
UPDATE repository SET categoryID = 3 WHERE categoryID = 47
UPDATE repository SET categoryID = 4 WHERE categoryID = 48
UPDATE repository SET categoryID = 5 WHERE categoryID = 49
UPDATE repository SET categoryID = 6 WHERE categoryID = 50
UPDATE repository SET categoryID = 7 WHERE categoryID = 51
UPDATE repository SET categoryID = 8 WHERE categoryID = 52
UPDATE repository SET categoryID = 20 WHERE categoryID = 53
UPDATE repository SET categoryID = 1 WHERE categoryID = 54
UPDATE repository SET categoryID = 10 WHERE categoryID = 55
UPDATE repository SET categoryID = 11 WHERE categoryID = 56
UPDATE repository SET categoryID = 12 WHERE categoryID = 57
UPDATE repository SET categoryID = 13 WHERE categoryID = 58
UPDATE repository SET categoryID = 14 WHERE categoryID = 59
UPDATE repository SET categoryID = 18 WHERE categoryID = 60
UPDATE repository SET categoryID = 19 WHERE categoryID = 61
UPDATE repository SET categoryID = 21 WHERE categoryID = 62
UPDATE repository SET categoryID = 22 WHERE categoryID = 63
-- Tronchiamo config_company_category
TRUNCATE TABLE config_company_category
-- Cancelliamo le category outlet
DELETE FROM category WHERE categoryID >= 23
-- Ci inseriamo tutto di tutti
INSERT INTO config_company_category(companyID, categoryID)
SELECT DISTINCT company.companyID, category.categoryID
FROM category, company
ORDER BY company.companyID, category.categoryID
Eccezione 44334 (DEPRECATO)
[modifica]La shop net di test, 44334, dopo l'import risulta "azzoppata". Bisogna mettere i record dei suoi Manufacturer Custom dentro al mapping:
INSERT INTO Mapping_ShopPoint_Manufacturer_Ext(Context,ShopPointID,ManufacturerID,DisplayOrder)
SELECT 44334, 44334, Manufacturer_Ext.ID, 100
FROM Manufacturer_Ext
WHERE Context = 44334
Tabelle Pre-popolate
[modifica]Queste tabelle sono statiche e non vengono toccate dall'importazione:
- Agent
- ShopNetStatus
- City
- Province
- Region
- Language
- Selection
- ContactRoles
- ManufacturerContractType
Cosa manca
[modifica]L'importazione lascia vuote le tabelle:
- Mapping_Selection_ShopPoint
- ShopPointSponsor
SEO Title e Description
[modifica]Le nuove tabelle ospitano i campi MetTitle e MetaDescription, nell'importazione vengono lasciati vuoti.
Questi dati li abbiamo, stanno nella MetaTagsHelper, dobbiamo successivamente pensare a come importarli nei campi giusti.
SEO Regole
[modifica]Creata una tabella SeoRules culturizzata tramite LanguageId.
Full Text Search
[modifica]La full text ha bisogno di tabelle con unica chiave primaria e i campi testuali sui quali lavorare.
Nel progetto precedente (WM5) utilizzavamo delle viste indicizzate, in questo non è più possibile farlo perché la struttura delle nuove tabelle implica delle self-join che per la fulltext non sono ammesse.
Non ci resta che optare per la creazione di tabelle apposite per la ricerca che dovranno essere mantenute aggiornate con qualche task schedulato.
Prodotti
[modifica]Le tabelle implicate sono
FT_Products_ITFT_Products_ENFT_Products_FRFT_Products_DEFT_Products_IT_ExtFT_Products_EN_ExtFT_Products_FR_ExtFT_Products_DE_Ext
e vengono aggiornate (con MERGE) dalla SP FullText_Products_Update e dalla SP FullText_Products_Update_Ext (in caso di contesto TP)
Per effettuare una ricerca prodotti utilizzare la FullText_Products_Search oppure FullText_Products_Search_Ext (in caso di contesto TP)
Negozi
[modifica]Qui rimaniamo col vecchio approccio delle viste indicizzate dato che non ci sono self-join al momento.
Script di creazione delle viste:
DROP VIEW IF EXISTS dbo.FT_Shops_IT
GO
DROP VIEW IF EXISTS dbo.FT_Shops_EN
GO
DROP VIEW IF EXISTS dbo.FT_Shops_FR
GO
DROP VIEW IF EXISTS dbo.FT_Shops_DE
GO
-- ITALIA
CREATE VIEW dbo.FT_Shops_IT WITH SCHEMABINDING
AS
SELECT
ShopPoint.ID,
ShopPoint.[Name],
ShopNet.[Name] AS ShopNetName,
LocDescription.[LocaleValue] AS [Description],
ShopPoint.Referente,
ShopPoint.[Address],
ShopPoint.Phone,
ShopPoint.Mail,
ShopPoint.BackofficeNotes,
Region.[Name] AS Region,
Province.[Name] AS Province,
City.[Name] AS City,
ShopPoint.Visible
FROM dbo.ShopPoint
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID
INNER JOIN dbo.Region ON Province.RegionID = Region.ID
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 1 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description'
WHERE ShopPoint.VisibilityTypeID = 0
GO
CREATE UNIQUE CLUSTERED INDEX UQ_ID
ON dbo.FT_Shops_IT(ID)
GO
-- WORLD
CREATE VIEW dbo.FT_Shops_EN WITH SCHEMABINDING
AS
SELECT
ShopPoint.ID,
ShopPoint.[Name],
ShopNet.[Name] AS ShopNetName,
LocDescription.[LocaleValue] AS [Description],
ShopPoint.Referente,
ShopPoint.[Address],
ShopPoint.Phone,
ShopPoint.Mail,
ShopPoint.BackofficeNotes,
Region.[Name] AS Region,
Province.[Name] AS Province,
City.[Name] AS City,
ShopPoint.Visible
FROM dbo.ShopPoint
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID
INNER JOIN dbo.Region ON Province.RegionID = Region.ID
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 2 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description'
WHERE ShopPoint.VisibilityTypeID = 0
GO
CREATE UNIQUE CLUSTERED INDEX UQ_ID
ON dbo.FT_Shops_EN(ID)
GO
-- FRANCIA
CREATE VIEW dbo.FT_Shops_FR WITH SCHEMABINDING
AS
SELECT
ShopPoint.ID,
ShopPoint.[Name],
ShopNet.[Name] AS ShopNetName,
LocDescription.[LocaleValue] AS [Description],
ShopPoint.Referente,
ShopPoint.[Address],
ShopPoint.Phone,
ShopPoint.Mail,
ShopPoint.BackofficeNotes,
Region.[Name] AS Region,
Province.[Name] AS Province,
City.[Name] AS City,
ShopPoint.Visible
FROM dbo.ShopPoint
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID
INNER JOIN dbo.Region ON Province.RegionID = Region.ID
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 3 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description'
WHERE ShopPoint.VisibilityTypeID = 0
GO
CREATE UNIQUE CLUSTERED INDEX UQ_ID
ON dbo.FT_Shops_FR(ID)
GO
-- GERMANIA
CREATE VIEW dbo.FT_Shops_DE WITH SCHEMABINDING
AS
SELECT
ShopPoint.ID,
ShopPoint.[Name],
ShopNet.[Name] AS ShopNetName,
LocDescription.[LocaleValue] AS [Description],
ShopPoint.Referente,
ShopPoint.[Address],
ShopPoint.Phone,
ShopPoint.Mail,
ShopPoint.BackofficeNotes,
Region.[Name] AS Region,
Province.[Name] AS Province,
City.[Name] AS City,
ShopPoint.Visible
FROM dbo.ShopPoint
INNER JOIN dbo.ShopNet ON ShopPoint.ShopNetID = ShopNet.ID
INNER JOIN dbo.City ON ShopPoint.CityID = City.ID
INNER JOIN dbo.Province ON City.ProvinceID = Province.ID
INNER JOIN dbo.Region ON Province.RegionID = Region.ID
INNER JOIN dbo.LocalizedProperty AS LocDescription ON ShopPoint.ID = LocDescription.EntityId AND LocDescription.LanguageId = 4 AND LocDescription.LocaleKeyGroup = 'ShopPoint' AND LocDescription.LocaleKey = 'Description'
WHERE ShopPoint.VisibilityTypeID = 0
GO
CREATE UNIQUE CLUSTERED INDEX UQ_ID
ON dbo.FT_Shops_DE(ID)
GO
Le viste implicate sono
FT_Shops_ITFT_Shops_ENFT_Shops_FRFT_Shops_DE
Per effettuare una ricerca negozi utilizzare la FullText_Shops_Search
Tabelle senza vincoli
[modifica]Qui sotto l'elenco delle tabelle sconnesse dai cascading, che devono essere trattate separatamente in caso di cancellazione dati
LocalizedPropertyLocalizedProperty_ExtPicturePicture_ExtCustomSortingEntityDeepeningNopMapping_ShopNetsNopMapping_ManufsNopMapping_DesignersNopMapping_Categories
Sul Server
[modifica]È necessario installare .NET 8.0, la versione Hosting Bundle da https://dotnet.microsoft.com/en-us/download/dotnet/8.0
Il server non chiede riavvio.
Per verificare che il framework sia presente digitare in una console:
dotnet --list-runtimes
per vedere .NET 8
Progetto VS
[modifica]Program.cs
[modifica]Nel file Program.cs pregenerato aggiungere i seguenti concetti
Sessione
[modifica]In un progetto Net.Core la sessione parte disabilitata.
Per iniettare la sessione è necessario attivare un gestore della cache integrato (IDistributedCache) e la sessione stessa con le sue opzioni di configurazione
come da esempio
builder.Services.AddDistributedMemoryCache(); //L'implementazione IDistributedCache viene usata come archivio di backup per la sessione
builder.Services.AddSession(options => {
//options.IdleTimeout = TimeSpan.FromSeconds(10); // default 20 minuti
//options.Cookie.HttpOnly = true; // default true
options.Cookie.Name = ".Designbest.Session"; // nome del cookie di sessione
options.Cookie.IsEssential = true;
});
Dopodiché è necessario attivarla inserendo app.UseSession():
L' ordine del middleware è importante.
Chiamare UseSession dopo e UseRouting prima di MapRazorPages e MapDefaultControllerRoute
// ...
app.UseAuthorization();
app.UseSession(); // attivazione sessione
app.MapRazorPages();
app.Run();
HttpContextAccessor e HttpClient
[modifica]Abilitare questi servizi
HttpContextAccessorpermette di accedere all' HttpContext da qualsiasi classe.HttpClientpermette di fare request.
builder.Services.AddHttpContextAccessor(); // per avere l'oggetto IHttpContextAccessor nelle classi
builder.Services.AddHttpClient(); // per avere l'oggetto IHttpClientFactory nelle classi
Services/Dipendenze
[modifica]Implementare tutti i servizi bene separati utilizzando il pattern Interfaccia-Implementazione.
Per abilitarli nel progetto, segnalare interfaccia e implementazione come segue:
builder.Services.AddScoped<IDesignbestContext, DesignbestContext>();
builder.Services.AddScoped<IGeolocalization, MaxMindGeolocalization>();
Ci sono 3 diverse durate del servizio
- Temporaneo
builder.Services.AddTransient(..)
istanza normale che muore alla fine dello scope della funzione in cui viene chiamata - Con Ambito
builder.Services.AddScoped(..)
viene utilizzata la stessa istanza per tutta la durata della richiesta utente - Singleton
builder.Services.AddSingleton(...)
viene utilizzata la stessa istanza finche IIS non viene stoppato
Javascript
[modifica]Tutti i moduli, le funzioni e gli eventi del sito sono gestiti dai nostri script che non dipendono da terze parti (tantomeno da jQuery che per i dispositivi mobile è solo un rallentamento).
Sfruttiamo le ultime funzioni standardizzate in ECMASCRIPT 6, con le definizioni di classi, metodi, membri di classe e chiamate asincrone.
Il cosiddetto thread principale prevede un bundle minificato delle nostre risorse principali.
Compilazione a runtime
[modifica]Abilitare la compilazione a runtime solo in ambiente di sviluppo.
- Scaricare il pacchetto nuget
Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation - Aggiungere il seguente codice al Program.cs
if (builder.Environment.IsDevelopment()) {
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
} else {
builder.Services.AddRazorPages();
}
Problema CopyRefAssembliesToPublishDirectory
[modifica]Se dopo aver attivato la compilazione a Runtime il progetto dà un errore che parla di
CopyRefAssembliesToPublishDirectory
il problema potrebbe essere che dalle viste è stata fatta una @inject di un Service del quale non è stata specificata la @using all'interno di /Pages/_ViewImports.cshtml.
Mi è successo con IConfiguration, ho dovuto aggiungere questa riga
@using Microsoft.Extensions.Configuration
Environment - Development, Staging, Production
[modifica]Per decidere con quale environment la web app deve essere pubblicata è necessario editare manualmente il publish profile aggiungendo la seguente
<Project>
<PropertyGroup>
<!-- Altre direttive -->
<EnvironmentName>Staging</EnvironmentName>
</PropertyGroup>
</Project>
I valori consentiti sono
- Production
- Staging
- Development - L'unico che permette la visualizzazione degli errori
Passare a .Net Core 8
[modifica]https://learn.microsoft.com/en-us/aspnet/core/migration/70-80?view=aspnetcore-8.0&tabs=visual-studio