Nop4.40.3: differenze tra le versioni

Da Webmobili Wiki.
 
(36 versioni intermedie di 3 utenti non mostrate)
Riga 11: Riga 11:
* Aprire di nuovo <code>Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog</code>, nella tab ''General'' inserire come ''Owner'' => '''wmuser'''
* Aprire di nuovo <code>Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog</code>, nella tab ''General'' inserire come ''Owner'' => '''wmuser'''
* Aprire di nuovo <code>Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog</code>, nella tab ''Tables/Views'', pannello ''Eligible columns'' impostare '''italian''' per tutti i campi
* Aprire di nuovo <code>Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog</code>, nella tab ''Tables/Views'', pannello ''Eligible columns'' impostare '''italian''' per tutti i campi
* Creazione dei sinonimi: <syntaxhighlight lang="sql">CREATE SYNONYM [dbo].[Db_NopMapping_Occasioni] FOR [_Designbest].[dbo].[NopMapping_Occasioni]
CREATE SYNONYM [dbo].[TP_Occasioni] FOR [_Trovaprodotti].[dbo].[Occasioni]</syntaxhighlight>


=== Configurazione della Solution ===
=== Configurazione della Solution ===
Riga 27: Riga 29:
   <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
   <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
</environmentVariables></syntaxhighlight>
</environmentVariables></syntaxhighlight>
* Dalle proprietà del progetto ''Nop.Web'' andare sulla tab ''Build'', selezionare la configurazione ''Release'' e aggiungere il simbolo condizionale '''OFFICIAL'''
* Dalle proprietà dei progetti
** ''Nop.Web''
**''Nop.Data''
** ''Not.Services''<br/> andare sulla tab ''Build'', selezionare la configurazione ''Release'' e aggiungere il simbolo condizionale '''OFFICIAL'''
* Configuriamo l'applicazione in modo che cambi file di configurazione a seconda della Solution Configuration selezionata
* Configuriamo l'applicazione in modo che cambi file di configurazione a seconda della Solution Configuration selezionata
** In <code style="color:red;font-weight:bold;">Libraries\Nop.Data\NopDataSettingsDefaults.cs</code> cambiare l'assegnamento di '''FilePath''' con quanto segue <syntaxhighlight lang="c#">        public static string FilePath =>
** In <code style="color:red;font-weight:bold;">Libraries\Nop.Data\NopDataSettingsDefaults.cs</code> cambiare l'assegnamento di '''FilePath''' con quanto segue <syntaxhighlight lang="c#">        public static string FilePath =>
Riga 301: Riga 306:
</div>
</div>
}
}
</syntaxhighlight>
=== Integrazione di 4Dem ===
All'interno della action ''SubscribeNewsletter'' di <code>Presentation\Nop.Web\Controllers\NewsletterController.cs</code> aggiungiamo la chiamata alle API di 4Dem per registrare il cliente nella nostra lista.
<syntaxhighlight lang="c#">#region DESIGNBEST-4DEM
if (subscribe) {
wmcore.WMCore.FourDemAddSubscriber(70904,email,"shop", workContext.GetWorkingLanguageAsync().Result.LanguageCulture.Split('-').First().ToUpper(),"1", Request.HttpContext.Connection.RemoteIpAddress.ToString());
}
#endregion</syntaxhighlight>
dopo l'assegnazione di ''success''.
Ricordarsi di importare <code>using System.Linq;</code>
Aggiungere lo script di tracking di 4dem nella master page in <code>Presentation\Nop.Web\Themes\Pacific\Views\Shared\_Root.Head.cshtml</code>
<syntaxhighlight lang="html">
<!-- 4Dem - Email Automation Tracker - Start -->
<script type="text/javascript" charset="utf-8">var $atmTRK={'settings':{'cid':'RpsXFDFvAu','uid':'25i'}};(function(){var st=document.createElement('script');st.type='text/javascript';st.async=true;st.defer=true;st.src=('https:'==document.location.protocol?'https://trk.mktaut.com':'http://trk.mktaut.com')+'/assets/goaltracking.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(st,s);})();</script>
<!-- 4Dem - Email Automation Tracker - End -->
</syntaxhighlight>
=== SEO delle pagine HP e HP-CATALOGHI ===
Sembra che nella backoffice non ci sia la gestione dei '''meta title''' e '''description''' per le pagine HP e HP-Cataloghi.<br/>
Modificare la vista <code>Presentation\Nop.Web\Themes\Pacific\Views\Shared\_Root.Head.cshtml</code> commentando i meta title e description e sostituendo questo
<syntaxhighlight lang="xml">
  @{
    string currentpage = webhelper.GetThisPageUrl(true);
  }
  @if (currentpage == "https://localhost:44353/" || currentpage == "https://shop.designbest.com/" || currentpage == "https://shop.dbdemo47.com/") {
    <title>Designbest Shop | L'e-commerce del design</title>
    <meta name="description" content="Il meglio del design è in offerta su Shop Designbest: trova l'arredamento per la tua casa e acquista a prezzi scontati e con pronta consegna." />
  }
  else if (currentpage.EndsWith("/manufacturer/all")) {
    <title>Designbest Shop | I cataloghi dei migliori brand</title>
    <meta name="description" content="I prodotti delle migliori marche sono in offerta su Shop Designbest. Sfoglia i cataloghi di arredamento e finiture d’interni e acquista a prezzi scontati." />
  }
  else {
    <title>@Html.NopTitle()</title>
    <meta name="description" content="@(Html.NopMetaDescription())" />
  }
</syntaxhighlight>
</syntaxhighlight>


Riga 478: Riga 524:


=== Update dei MessageTokens ===
=== Update dei MessageTokens ===
Nel <code style="color:red;font-weight:bold;">Libraries\Nop.Services\Messages\MessageTokeProvider.cs</code> aggiungere i riferimenti ai servizi
Nel <code style="color:red;font-weight:bold;">Libraries\Nop.Services\Messages\MessageTokenProvider.cs</code> aggiungere i riferimenti ai servizi
* IVendorService
* IVendorService
* IConfiguration
* IConfiguration
Riga 576: Riga 622:


Nella '''WriteTotalsAsync''' aggiungere il parametro opzionale ''int vendorId = 0'' e una region iniziale <syntaxhighlight lang="c#">protected virtual async Task WriteTotalsAsync(Order order, Language language, StringBuilder sb, int vendorId = 0) {
Nella '''WriteTotalsAsync''' aggiungere il parametro opzionale ''int vendorId = 0'' e una region iniziale <syntaxhighlight lang="c#">protected virtual async Task WriteTotalsAsync(Order order, Language language, StringBuilder sb, int vendorId = 0) {
#region Costi spedizione per negozio
#region Costi spedizione per negozio e sconto sul totale
if (vendorId != 0) {
if (vendorId != 0) {
order.OrderSubtotalInclTax = 0;
order.OrderSubtotalInclTax = 0;
Riga 598: Riga 644:
}
}
}
}
        //Applichiamo eventuale sconto all'ordine
        order.OrderTotal -= order.OrderSubTotalDiscountInclTax;
}
}
#endregion
#endregion
Riga 637: Riga 685:
/// <param name="attachmentFileName">Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.</param>
/// <param name="attachmentFileName">Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.</param>
/// <returns>Queued email identifier</returns>
/// <returns>Queued email identifier</returns>
IList<int> SendOrderPaidDropshipCustomerNotification(Order order, int languageId,
Task<IList<int>> SendOrderPaidDropshipCustomerNotificationAsync(Order order, int languageId,
string attachmentFilePath = null, string attachmentFileName = null);
string attachmentFilePath = null, string attachmentFileName = null);
#endregion</syntaxhighlight>
#endregion</syntaxhighlight>


e l'implementazione in <code style="color:red;font-weight:bold">Libraries\Nop.Services\Messages\WorkflowMessageService.cs</code> <syntaxhighlight lang="c#">#region WEBMOBILI
e l'implementazione in <code style="color:red;font-weight:bold">Libraries\Nop.Services\Messages\WorkflowMessageService.cs</code> <syntaxhighlight lang="c#">
public virtual IList<int> SendOrderPaidDropshipCustomerNotification(Order order, int languageId,
    #region WEBMOBILI
string attachmentFilePath = null, string attachmentFileName = null) {
    public virtual async Task<IList<int>> SendOrderPaidDropshipCustomerNotificationAsync(Order order, int languageId,
if (order == null)
      string attachmentFilePath = null, string attachmentFileName = null) {
throw new ArgumentNullException(nameof(order));
      if (order == null)
        throw new ArgumentNullException(nameof(order));
 
      var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? _storeContext.GetCurrentStore();
      languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);
 
      var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.OrderPaidDropshipCustomerNotification, store.Id);
      if (!messageTemplates.Any())
        return new List<int>();
 
      //tokens
      var commonTokens = new List<Token>();
      await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
      await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);
 
      return await messageTemplates.SelectAwait( async messageTemplate =>
      {
        //email account
        var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);
 
        var tokens = new List<Token>(commonTokens);
        await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);
 
        //event notification
        await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);
 
        var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);
 
        var toEmail = billingAddress.Email;
        var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";
 
        return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName, attachmentFilePath, attachmentFileName);
      }).ToListAsync();
    }
 
    #endregion
</syntaxhighlight>
 
A questo punto è necessario aggiungere via SQL il messaggio (comprensivo di token) alla tabella '''MessageTemplate''' <syntaxhighlight lang="sql">INSERT INTO [MessageTemplate]([Name],[BccEmailAddresses],[Subject],[Body],[IsActive],[DelayBeforeSend],[DelayPeriodId],[AttachedDownloadId],[EmailAccountId],[LimitedToStores])
VALUES ('OrderPaid.DropShipCustomerNotification',NULL,'%Store.Name% | Pagamento effettuato per l''ordine #%Order.OrderNumber%','Compilare il BODY',1,NULL,0,0,1,0)</syntaxhighlight>
 
'''Lato admin''' è necessario modificare il controller <code>Presentation\Nop.Web\Areas\Admin\Controllers\OrderController.cs</code> in modo che chiami la nuova funzione quando necessario.
Aggiungere i seguenti Services:
* ICopyProductService
* IVendorService
* LocalizationSettings
Dopo il metodo(AddProductToOrderDetails), aggiungere:
<syntaxhighlight lang="c#">
#region DROPSHIPPING
 
    public virtual async Task<IActionResult> CopyAndSetProductWMtoVendor(int productId, int vendorId, int orderItemId) {
 
      if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageProducts))
        return AccessDeniedView();
      var orderId = 0;
      //var copyModel = new CopyProductModel { CopyImages = true, Published = false, Name = "prodotto_" + productId }; //model.CopyProductModel;
      try {
        var originalProduct = await _productService.GetProductByIdAsync(productId);
 
        // Clonazione prodotto dell'ordine             
 
        var newProduct = await _copyProductService.CopyProductAsync(originalProduct, originalProduct.Name, false,
#if DEBUG
                  false
#else
                  true
#endif
                  ); // false = publish, true = copyImages
 
        newProduct.Sku = originalProduct.Sku + "-" + newProduct.Id;  // nuovo SKU con presiffo del prodotto originale
        newProduct.Name = originalProduct.Name + " #";
        newProduct.StockQuantity = 0;
        newProduct.VisibleIndividually = true;
        newProduct.VendorId = vendorId; // aggiorniamo il Vendor a quello scelto dalla tendina
        await _productService.UpdateProductAsync(newProduct);
 
        // Recuperiamo i dettagli del OrderItem in cui si trova il prodotto da clonare
        var tmpOrderItem = await _orderService.GetOrderItemByIdAsync(orderItemId);
 
        tmpOrderItem.ProductId = newProduct.Id; // IMPORTANTE!!! aggiornare il productId dell' orderitem con il productId del nuovo prodotto clonato
        await _orderService.UpdateOrderItemAsync(tmpOrderItem);
 
        orderId = tmpOrderItem.OrderId;
 
        // INVIO EMAIL ORDINE AL RIVENDITORE             
        Order currentOrder = await _orderService.GetOrderByOrderItemAsync(orderItemId);
        Vendor newVendor = await _vendorService.GetVendorByIdAsync(vendorId);
 
        // Controllo se ordine è già stato pagato tramite Paypal o Stripe
        // In base a questo decido il template della mail
 
        switch (currentOrder.PaymentMethodSystemName) {
 
          case "Payments.CheckMoneyOrder":
            await _workflowMessageService.SendOrderPlacedVendorNotificationAsync(currentOrder, newVendor, _localizationSettings.DefaultAdminLanguageId);
            break;
 
          case "FoxNetSoft.StripeDirect":
          case "Payments.PayPalSmartPaymentButtons":
            await _workflowMessageService.SendOrderPaidVendorNotificationAsync(currentOrder, newVendor, _localizationSettings.DefaultAdminLanguageId);
            await _workflowMessageService.SendOrderPaidDropshipCustomerNotificationAsync(currentOrder, _localizationSettings.DefaultAdminLanguageId);
            break;
 
        }
 
      }
      catch (Exception exc) {
        _notificationService.ErrorNotification(exc.Message);
      }
 
      return RedirectToAction("Edit", "Order", new { id = orderId });


var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore;
    }
languageId = EnsureLanguageIsActive(languageId, store.Id);


var messageTemplates = GetActiveMessageTemplates(MessageTemplateSystemNames.OrderPaidDropshipCustomerNotification, store.Id);
    #endregion
if (!messageTemplates.Any())
</syntaxhighlight>
return new List<int>();


//tokens
var commonTokens = new List<Token>();
_messageTokenProvider.AddOrderTokens(commonTokens, order, languageId);
_messageTokenProvider.AddCustomerTokens(commonTokens, order.CustomerId);


return messageTemplates.Select(messageTemplate =>
== Installare Plugin ==
{
Installare i plugin necessari che sono nel sorgente del progetto.
//email account
 
var emailAccount = GetEmailAccountOfMessageTemplate(messageTemplate, languageId);
=== Plugin 7Spikes ===
Il nuovo tema è '''Pacific'''<br/>
var tokens = new List<Token>(commonTokens);
Per installare seguire questo ordine updatando il file <code>Presentation\Nop.Web\App_Data\Plugins.json</code>
_messageTokenProvider.AddStoreTokens(tokens, store, emailAccount);
* Installare '''7Spikes Core''' <syntaxhighlight lang="json">"PluginNamesToInstall": [
    {
//event notification
      "Item1": "SevenSpikes.Core",
_eventPublisher.MessageTokensAdded(messageTemplate, tokens);
      "Item2": "277c962d-b286-4311-b2a7-9b2ff68bb7ac"
    }
var billingAddress = _addressService.GetAddressById(order.BillingAddressId);
  ]</syntaxhighlight> (il guid è generato casualmente)
* Avviare l'applicazione e verificare che risulti installato
var toEmail = billingAddress.Email;
* Installare i '''15 plugin necessari al funzionamento del tema''' + '''SmartSEO''' (accodato) <syntaxhighlight lang="json">"PluginNamesToInstall": [
var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";
    {
      "Item1": "SevenSpikes.Nop.Plugins.AjaxCart",
return SendNotification(messageTemplate, emailAccount, languageId, tokens, toEmail, toName, attachmentFilePath, attachmentFileName);
      "Item2": "56c6cd57-2a4d-406f-a7e2-7abf28452553"
}).ToList();
    },
}
    {
      "Item1": "SevenSpikes.Nop.Plugins.AjaxFilters",
      "Item2": "c694ba6e-c4e7-4dca-a67c-b10da14dc45e"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.AnywhereSliders",
      "Item2": "880b31c6-df3e-498a-94e8-5e46131b9483"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.CloudZoom",
      "Item2": "00db0bfd-f675-460d-ae2c-f77b219406b3"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.InstantSearch",
      "Item2": "5daa78be-ee0d-419a-95d0-467e704cf739"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.JCarousel",
      "Item2": "55701ae8-9b19-4005-a1dd-557232191b19"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.MegaMenu",
      "Item2": "59d86a70-949e-430e-8d53-aca34a83939b"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.NopQuickTabs",
      "Item2": "8bb5aa71-9e4f-47c9-b140-0044b80d3e00"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.PrevNextProduct",
      "Item2": "94866039-c2be-49d9-90b1-6f1f1e93c52c"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.ProductRibbons",
      "Item2": "2423c64d-a61b-496e-81d9-44bba585155f"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.QuickView",
      "Item2": "213656fb-be9c-402f-88d9-37810039b78d"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.RichBlog",
      "Item2": "a47252a0-f661-4035-9547-ed1ae335006b"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.SaleOfTheDay",
      "Item2": "11c84c82-56f0-41ad-a02f-7bdb7b62f31f"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.SmartProductCollections",
      "Item2": "d8033245-a44a-46bf-a1c1-1cb4b2cd29d3"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.NewsletterPopup",
      "Item2": "b157dc40-3923-433e-814d-c66f06cf0211"
    },
    {
      "Item1": "SevenSpikes.Nop.Plugins.SmartSEO",
      "Item2": "4d061b34-5c82-4051-ba18-df360a6da072"
    }
  ]</syntaxhighlight>
* Avviare l'applicazione e verificare che risultino installati
* Installare il '''tema''' <syntaxhighlight lang="json">"PluginNamesToInstall": [
    {
      "Item1": "SevenSpikes.Theme.Pacific",
      "Item2": "277c962d-b286-4311-b2a7-9b2ff68bb7ac"
    }
  ]</syntaxhighlight>
 
https://www.nop-templates.com/pacific-responsive-theme-for-nopcommerce-documentation
 
 
=== MEGA MENU Mobile fix ===
Per aggiungere i link a Designbest e al Magazine modificare la vista <code>\Plugins\Nop.Plugin.Compiled\SevenSpikes.Nop.Plugins.MegaMenu\Views\Components\MegaMenu\MegaMenu.cshtml</code> aggiungendo all'elemento <syntaxhighlight  lang="html"><ul class='mega-menu-responsive @menu.CssClass'></syntaxhighlight> gli elementi <code>li</code>:
<syntaxhighlight lang="html">
<li><a href="https://www.designbest.com" target="_blank" rel="noopener"><span>RICERCA</span></a></li>
<li><a href="https://magazine.designbest.com/it" target="_blank" rel="noopener"><span>MAGAZINE</span></a></li>
</syntaxhighlight>
 
 
=== Payments.CheckMoneyOrder ===
'''Payments.CheckMoneyOrder''' (da Configurazione/Plugins Locali) il ''nome amichevole'' del plugin viene utilizzato in message template delle email, si rende necessario modificare il nome con l'esatta stringa<br/><br/>
<code>Bonifico Bancario</code><br/><br/>
[[File:MoneyCheckPlugin.jpg]]


#endregion</syntaxhighlight>
== Sul Server ==
È necessario installare '''.NET 5.0''' (che è la successiva di .NET Core 3.1) , la versione '''Hosting Bundle''' da https://dotnet.microsoft.com/download/dotnet/5.0

Versione attuale delle 12:29, 7 dic 2021

Installare Nop 4.40.3

[modifica]

In questa guida saranno evidenziati in rosso tutti i file del core di Nop che verranno toccati.

Operazioni sul DB

[modifica]
  • Creare un dump del database di produzione (Nop 4.3)
  • Ripristinarlo
  • Per ripristinare l'utente wmuser è necessario disassociarlo dal catalogo fulltext
  • Da SQL Management Aprire Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog, nella tab General impostare come Owner => dbo
  • Eliminare l'utente wmuser
  • Ri-mappare l'utente wmuser sul database nuovo
  • Aprire di nuovo Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog, nella tab General inserire come Owner => wmuser
  • Aprire di nuovo Storage -> Fulltext Catalogs -> nopCommerceFullTextCatalog, nella tab Tables/Views, pannello Eligible columns impostare italian per tutti i campi
  • Creazione dei sinonimi:
    CREATE SYNONYM [dbo].[Db_NopMapping_Occasioni] FOR [_Designbest].[dbo].[NopMapping_Occasioni]
    CREATE SYNONYM [dbo].[TP_Occasioni] FOR [_Trovaprodotti].[dbo].[Occasioni]
    

Configurazione della Solution

[modifica]
  • Aggiornare i file Presentation\Nop.Web\App_Data\dataSettings.json e Presentation\Nop.Web\App_Data\appsettings.json
  • Copiare i plugin di terze parti nella cartella Plugins\Nop.Plugin.Compiled
  • Aggiungere il seguente codice nei build events => post build event progetto Nop.Web
    xcopy /y /E $(SolutionDir)Plugins\Nop.Plugin.Compiled $(SolutionDir)Presentation\Nop.Web\Plugins
    per copiare i plugin compilati (7spikes & co) nel corretto folder durante il build.
  • Creare dal Configuration Manager una nuova Solution Configuration chiamata ReleaseTest
  • Tasto destro sul web.config e cliccare su Add Configs Transform per creare i web.Release.config e web.ReleaseTest.config
    • Nel web.config aggiungere la variabile d'ambiente ASPNETCORE_ENVIRONMENT con valore Development sotto la sezione aspNetCore
      <environmentVariables>
        <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
      </environmentVariables>
      
    • Nel web.ReleaseTest.config mettere il valore Testing
      <environmentVariables>
        <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Testing" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
      </environmentVariables>
      
    • nel web.Release.config mettere il valore Production
      <environmentVariables>
        <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
      </environmentVariables>
      
  • Dalle proprietà dei progetti
    • Nop.Web
    • Nop.Data
    • Not.Services
      andare sulla tab Build, selezionare la configurazione Release e aggiungere il simbolo condizionale OFFICIAL
  • Configuriamo l'applicazione in modo che cambi file di configurazione a seconda della Solution Configuration selezionata
    • In Libraries\Nop.Data\NopDataSettingsDefaults.cs cambiare l'assegnamento di FilePath con quanto segue
              public static string FilePath =>
      #if DEBUG
                  "~/App_Data/dataSettings.development.json";
      
      #elif !OFFICIAL
                  "~/App_Data/dataSettings.testing.json";
      
      #else
                  "~/App_Data/dataSettings.json";
      
      #endif
      
    • In Libraries\Nop.Services\Configuration\NopConfigurationDefaults.cs cambiare l'assegnamento di AppSettingsFilePath con quanto segue
              public static string AppSettingsFilePath =>
      #if DEBUG
                  "App_Data/appsettings.development.json";
      
      #elif !OFFICIAL
                  "App_Data/appsettings.testing.json";
      
      #else
                  "App_Data/appsettings.json";
      
      #endif
      
  • Addare alla Solution (cartella Libraries) i progetti GenericUtilites, WM4Search, WM_Core e USO
  • Runnando la Solution dovrebbe partire con tutti i plugin disabilitati. Una volta entrati in admin è possibile vederli dall'elenco e installarli.
  • Non ci saranno tutti i plugin, in tal caso cliccare su ricaricare la lista di plugin, la soluzione si chiuderà da sola, ma al successivo login la lista dovrebbe essere aggiornata.
  • Aggiornare tutti i pacchetti nuGet della Solution che abbiano un update minor (dalla seconda cifra in poi)
  • Creazione di IWMCoreModelFactory per le chiamate alla WMCore.
    • INTERFACCIA: creare Presentation\Nop.Web\Factories\IWMCoreModelFactory.cs così
      using GPC.WM_Core;
      
      namespace Nop.Web.Factories {
        public interface IWMCoreModelFactory {
          WMRetriever WMCore { get; }
        }
      }
      
    • IMPLEMENTAZIONE: creare Presentation\Nop.Web\Factories\IWMCoreModelFactory.cs così
      using GPC.WM_Core;
      using Microsoft.Extensions.Configuration;
      
      namespace Nop.Web.Factories {
        public class WMCoreModelFactory : IWMCoreModelFactory {
          public WMRetriever WMCore { get; } = null;
      
          public WMCoreModelFactory(IConfiguration configuration) {
            WMCore = new WMRetriever(
              configuration.GetSection("WMConnectionStrings")["Designbest"],
              configuration.GetSection("WMConnectionStrings")["Designbest"],
              configuration.GetSection("WMConnectionStrings")["Trovaprodotti"],
              configuration.GetSection("WMApplicationSettings")["ResourcesPath"],
              "Test_WM47");
          }
        }
      }
      
    • BINDING: comunicare la sua esistenza al framework modificando Presentation\Nop.Web\Startup.cs aggiungendo una riga a ConfigureServices() così
      public void ConfigureServices(IServiceCollection services)
      {
        // aggiunta di IWMCoreModelFactory
        services.AddSingleton<IWMCoreModelFactory, WMCoreModelFactory>();
        services.ConfigureApplicationServices(_configuration, _webHostEnvironment);
      }
      
  • Inserimento di UsoUtilitiesController uguale a quello precedente con la differenza che le chiamate a services sono diventate tutte ASYNC e perciò il trucco è aggiungere al fondo .Result per le variabili o .Wait() per i metodi come ad esempio
    Customer customer = _customerService.GetCustomerByGuidAsync(customerGuid).Result;
    _authenticationService.SignInAsync(customer, true).Wait();
    

Url Rewrite

[modifica]

Cambiare le regole di rewrite, i prodotti sotto /catalogo-prodotti , le categorie sotto /catalogo e i cataloghi sotto /brand

Modificare in Nop.Web\Infrastructure\GenericUrlRouteProvider.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Nop.Data;
using Nop.Web.Framework.Mvc.Routing;

namespace Nop.Web.Infrastructure {
  /// <summary>
  /// Represents provider that provided generic routes
  /// </summary>
  public partial class GenericUrlRouteProvider : BaseRouteProvider, IRouteProvider {
    #region Methods

    /// <summary>
    /// Register routes
    /// </summary>
    /// <param name="endpointRouteBuilder">Route builder</param>
    public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder) {
      var lang = GetLanguageRoutePattern();
      #region WEBMOBILI
      string constProducts = "catalogo-prodotti";
      string constCategories = "catalogo";
      string constManufacturers = "brand";
      #endregion
      //default routes
      //these routes are not generic, they are just default to map requests that don't match other patterns, 
      //but we define them here since this route provider is with the lowest priority, to allow to add additional routes before them
      if (!string.IsNullOrEmpty(lang)) {
        endpointRouteBuilder.MapControllerRoute(name: "DefaultWithLanguageCode",
            pattern: $"{lang}/{{controller=Home}}/{{action=Index}}/{{id?}}");
      }

      endpointRouteBuilder.MapControllerRoute(name: "Default",
          pattern: "{controller=Home}/{action=Index}/{id?}");

      if (!DataSettingsManager.IsDatabaseInstalled())
        return;

      //generic routes
      var genericPattern = $"{lang}/{{SeName}}";
      
      #region WEBMOBILI
      endpointRouteBuilder.MapDynamicControllerRoute<SlugRouteTransformer>(constProducts + "/{SeName}");
      endpointRouteBuilder.MapDynamicControllerRoute<SlugRouteTransformer>(constCategories + "/{SeName}");
      endpointRouteBuilder.MapDynamicControllerRoute<SlugRouteTransformer>(constManufacturers + "/{SeName}");
      #endregion

      endpointRouteBuilder.MapDynamicControllerRoute<SlugRouteTransformer>(genericPattern);

      endpointRouteBuilder.MapControllerRoute(name: "GenericUrl",
          pattern: "{genericSeName}",
          defaults: new { controller = "Common", action = "GenericUrl" });

      endpointRouteBuilder.MapControllerRoute(name: "GenericUrlWithParameter",
          pattern: "{genericSeName}/{genericParameter}",
          defaults: new { controller = "Common", action = "GenericUrl" });

      endpointRouteBuilder.MapControllerRoute(name: "Product",
          pattern: $"{lang}/{constProducts}/{{SeName}}",
          defaults: new { controller = "Product", action = "ProductDetails" });

      endpointRouteBuilder.MapControllerRoute(name: "Category",
          pattern: $"{lang}/{constCategories}/{{SeName}}",
          defaults: new { controller = "Catalog", action = "Category" });

      endpointRouteBuilder.MapControllerRoute(name: "Manufacturer",
          pattern: $"{lang}/{constManufacturers}/{{SeName}}",
          defaults: new { controller = "Catalog", action = "Manufacturer" });

      endpointRouteBuilder.MapControllerRoute(name: "Vendor",
          pattern: genericPattern,
          defaults: new { controller = "Catalog", action = "Vendor" });

      endpointRouteBuilder.MapControllerRoute(name: "NewsItem",
          pattern: genericPattern,
          defaults: new { controller = "News", action = "NewsItem" });

      endpointRouteBuilder.MapControllerRoute(name: "BlogPost",
          pattern: genericPattern,
          defaults: new { controller = "Blog", action = "BlogPost" });

      endpointRouteBuilder.MapControllerRoute(name: "Topic",
          pattern: genericPattern,
          defaults: new { controller = "Topic", action = "TopicDetails" });

      endpointRouteBuilder.MapControllerRoute(name: "ProductsByTag",
          pattern: genericPattern,
          defaults: new { controller = "Catalog", action = "ProductsByTag" });
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets a priority of route provider
    /// </summary>
    /// <remarks>
    /// it should be the last route. we do not set it to -int.MaxValue so it could be overridden (if required)
    /// </remarks>
    public int Priority => -1000000;

    #endregion
  }
}

Prodotto

[modifica]
  • Aggiunta metodi frmt() di utilità nelle viste per restituire decimali in Nop.Web\Extensions\HtmlExtensions.cs:
    /// Restituisce (come stringa) il numero decimale specificato inserendo il punto
    /// ogni tre cifre. I numeri decimali sono limitati a 2
    /// </summary>
    /// <param name="val">Un decimal</param>
    /// <returns>Il numero nella forma mmm.kkk.uuu,dd</returns>
    public static string frmt(this decimal val) {
      var ci = new CultureInfo("it-IT");
      ci.NumberFormat.NumberDecimalDigits = 2;
      return val.ToString("N", ci);
    }

    /// Restituisce (come stringa) il numero intero specificato inserendo il punto
    /// ogni tre cifre.
    /// </summary>
    /// <param name="val">Un decimal</param>
    /// <returns>Il numero nella forma mmm.kkk.uuu,dd</returns>
    public static string frmt(this int val) {
      var ci = new CultureInfo("it-IT");
      ci.NumberFormat.NumberDecimalDigits = 2;
      return val.ToString("N", ci);
    }
  • Nel modello del prodotto in Nop.Web\Models\Catalog\ProductDetailsModel.cs aggiungere:
 #region WEBMOBILI
    /// <summary>
    /// Nella nostra struttura c'è sempre un solo manufacturer per prodotto. Questa property lo restituisce.
    /// In caso di manufacturer non settato (errore di dati) restituisce un manu vuoto per evitare una NullPointerException
    /// </summary>
    public ManufacturerBriefInfoModel Manufacturer {
      get {
        if (ProductManufacturers.Count > 0) {
          return ProductManufacturers[0];
        }
        return new ManufacturerBriefInfoModel { Name = "", SeName = "", IsActive = false };
      }
    }

    public string ShopCity { get; set; }

    public decimal AdditionalShippingCharge { get; set; }

    #endregion
  • ProductController: al momento dichiarata l'istanza dell'interfaccia IWMCoreModelFactory, ma non utilizzata, i campi necessari sono stati aggiunti direttamente nel modello del prodotto, aggiungiamo quindi solo questa istruzione nel metodo ProductDetails(int productId, int updatecartitemid = 0):
#region WEBMOBILI
      model.AdditionalShippingCharge = product.AdditionalShippingCharge; // spese di spedizione da stampare in frontend
#endregion
  • Nella vista del prodotto, copiare la vista del tema base Nop.Web\Views\Product\_DeliveryInfo.cs in Nop.Web\Themes\Brooklyn\Views\Product\_DeliveryInfo.cs e modificarla così:
@model ProductDetailsModel
@if (Model.FreeShippingNotificationEnabled && Model.IsFreeShipping@*|| !string.IsNullOrWhiteSpace(Model.DeliveryDate)*@) {
<div class="delivery">
  <script asp-location="Footer">
    $(document).on("product_attributes_changed", function (data) {
      if (data.changedData.isFreeShipping) {
        $("#free-shipping-" + data.changedData.productId).removeClass("invisible");
      } else {
        $("#free-shipping-" + data.changedData.productId).addClass("invisible");
      }
    });
  </script>
  @if (Model.FreeShippingNotificationEnabled && Model.IsFreeShipping) {
    <div id="free-shipping-@Model.Id" class="free-shipping">@T("Products.FreeShipping")</div>
  }
  @if (!string.IsNullOrWhiteSpace(Model.DeliveryDate)) {
    <div class="delivery-date">
      <span class="label">@T("Products.DeliveryDate"):</span>
      <span class="value">@Model.DeliveryDate</span>
    </div>
  }
</div>
}
else {
<div class="delivery">
  @if (Model.IsShipEnabled) {
    <div class="free-shipping">
      <span class="label">SPEDIZIONE STANDARD ITALIA  @Model.AdditionalShippingCharge.frmt() <span class="testo-minuscolo">(cad.)</span></span>
    </div>
    @if (!string.IsNullOrWhiteSpace(Model.DeliveryDate)) {
      <div class="delivery-date">
        <span class="label">@T("Products.DeliveryDate"):</span>
        <span class="value">@Model.DeliveryDate</span>
      </div>
    }
  }
  else {
    <div class="free-shipping">
      <span class="label">SPEDIZIONE PERSONALIZZATA A PREVENTIVO</span>
    </div>
  }
</div>
}


Integrazione di 4Dem

[modifica]

All'interno della action SubscribeNewsletter di Presentation\Nop.Web\Controllers\NewsletterController.cs aggiungiamo la chiamata alle API di 4Dem per registrare il cliente nella nostra lista.

#region DESIGNBEST-4DEM
if (subscribe) {
	wmcore.WMCore.FourDemAddSubscriber(70904,email,"shop", workContext.GetWorkingLanguageAsync().Result.LanguageCulture.Split('-').First().ToUpper(),"1", Request.HttpContext.Connection.RemoteIpAddress.ToString());
}
#endregion

dopo l'assegnazione di success. Ricordarsi di importare using System.Linq;

Aggiungere lo script di tracking di 4dem nella master page in Presentation\Nop.Web\Themes\Pacific\Views\Shared\_Root.Head.cshtml

<!-- 4Dem - Email Automation Tracker - Start -->
<script type="text/javascript" charset="utf-8">var $atmTRK={'settings':{'cid':'RpsXFDFvAu','uid':'25i'}};(function(){var st=document.createElement('script');st.type='text/javascript';st.async=true;st.defer=true;st.src=('https:'==document.location.protocol?'https://trk.mktaut.com':'http://trk.mktaut.com')+'/assets/goaltracking.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(st,s);})();</script>
<!-- 4Dem - Email Automation Tracker - End -->

SEO delle pagine HP e HP-CATALOGHI

[modifica]

Sembra che nella backoffice non ci sia la gestione dei meta title e description per le pagine HP e HP-Cataloghi.
Modificare la vista Presentation\Nop.Web\Themes\Pacific\Views\Shared\_Root.Head.cshtml commentando i meta title e description e sostituendo questo

  @{
    string currentpage = webhelper.GetThisPageUrl(true);
  }

  @if (currentpage == "https://localhost:44353/" || currentpage == "https://shop.designbest.com/" || currentpage == "https://shop.dbdemo47.com/") {
    <title>Designbest Shop | L'e-commerce del design</title>
    <meta name="description" content="Il meglio del design è in offerta su Shop Designbest: trova l'arredamento per la tua casa e acquista a prezzi scontati e con pronta consegna." />

  }
  else if (currentpage.EndsWith("/manufacturer/all")) {
    <title>Designbest Shop | I cataloghi dei migliori brand</title>
    <meta name="description" content="I prodotti delle migliori marche sono in offerta su Shop Designbest. Sfoglia i cataloghi di arredamento e finiture d’interni e acquista a prezzi scontati." />
  }
  else {
    <title>@Html.NopTitle()</title>
    <meta name="description" content="@(Html.NopMetaDescription())" />
  }

LATO ADMIN

[modifica]

Per cambiare l'immagine in alto a sinistra bisogna sovrascriverla qui P:\DesignbestCommerce3\Presentation\Nop.Web\wwwroot\css\admin\images per 2 versioni del menu(compatto e non) logo.png 250x57 e logo-mini.png

Home - Vendor

[modifica]
  • Nascondere il pulsante Visualizza nei best seller(un Vendor non deve entrare nella pagina di modifica del prodotto lato NOP), bisogna commentare l'ultima ColumnProperty in queste 2 viste:

Nop.Web\Areas\Admin\Views\Home\_BestsellersBriefReportByAmount.cshtml e Nop.Web\Areas\Admin\Views\Home\_BestsellersBriefReportByQuantity.cshtml

Vendite - Ordini del Cliente

[modifica]
  • Nascondere il filtro Magazzino (aggiungere la classe d-none), altrimenti un vendor potrebbe vedere le vendite degli altri vendor, vista da modificare Nop.Web\Areas\Admin\Views\Order\List.cshtml.
<div class="form-group row d-none" @(Model.AvailableWarehouses.SelectionIsNotPossible() ? Html.Raw("style=\"display:none\"") : null)>
   <div class="col-md-4">
     <nop-label asp-for="WarehouseId" />
   </div>
   <div class="col-md-8">
      <nop-select asp-for="WarehouseId" asp-items="Model.AvailableWarehouses" />
   </div>
</div>

Ordine - dettaglio

[modifica]
  • Modificare il modello Nop.Web\Areas\Admin\Models\Orders\OrderItemModel.cs aggiungendo public string ProductSeoName { get; set; }
  • Factory Nop.Web\Areas\Admin\Factories\OrderModelFactory.cs valorizzare il campo ProductSeoName usando la WMCore, nel metodo PrepareOrderItemModelsAsync
ProductSeoName = _wmcore.WMCore.GetNopProductSeoName(orderItem.ProductId),
  • Nel box dove sono elencati i prodotti, il link del prodotti rimanderà alla scheda del prodotto nel frontend, mentre solo se siamo Admin entreremo nell'edit del prodotto di NOP
    modificare in Nop.Web\Areas\Admin\Views\Order\_OrderDetails.Products.cshtml
    • Dobbiamo recuperare il VendorID del dropship di Webmobili:
      @inject Nop.Services.Catalog.IProductService prodService;
      @inject Microsoft.Extensions.Configuration.IConfiguration configuration;
      @{
        int vendorWMDropship = 0;
        int.TryParse(configuration.GetSection("WMApplicationSettings")["DropshipVendorID"], out vendorWMDropship);
      }
      
    • Controllo se l'utente è un Vendor o Admin
      <em>
      @if (!Model.IsLoggedInAsVendor) {
        <a asp-controller="Product" asp-action="Edit" asp-route-id="@item.ProductId">@item.ProductName</a>
      }else {
        <a href="@Url.RouteUrl("Product", new { SeName = item.ProductSeoName })" target="_blank">@item.ProductName</a>
      }</em>
      
    • Se il vendorID è quello di Webmobili-dropship mostro il pulsante CLONA e ASSEGNA a RIVENDITORE. Attenzione il metodo è cambiato da code>prodService.GetProductById(item.ProductId).VendorId a GetProductByIdAsync:
      @{
      	var productById = await prodService.GetProductByIdAsync(item.ProductId);
       }
      @if (productById != null) {
       if (productById.VendorId == vendorWMDropship) {
        <button type="button" id="setProductToVendor" name="setProductToVendor" class="btn bg-olive" onclick="apriModaleAssegnazione(@item.ProductId,@item.Id,'@item.ProductName','@item.PictureThumbnailUrl');" data-toggle="modal" data-target="#setProductToVendor-window"><i class="fa fa-clone"></i>
      CLONA e ASSEGNA a RIVENDITORE</button>
       }
      }
      
    • Il pulsante CLONA e ASSEGNA a RIVENDITORE' apre un modale in cui selezionare il rivenditore, innanzitutto dobbiamo creare la vista/componente del modale ProductToVendorModal.
      Creiamo il componente in Nop.Web\Areas\Admin\Components\ProductToVendorModal.cs così:
      using Microsoft.AspNetCore.Mvc;
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Threading.Tasks;
      using Nop.Web.Framework.Components;
      using Microsoft.Extensions.Configuration;
      using Nop.Services.Vendors;
      
      namespace Nop.Web.Areas.Admin.Components {
        public class ProductToVendorModal : NopViewComponent {
          private readonly IVendorService _vendorService;
          private readonly IConfiguration _configuration;
      
          public ProductToVendorModal(IVendorService vendorService, IConfiguration configuration) {
            _vendorService = vendorService;
            _configuration = configuration;
          }
      
          public IViewComponentResult Invoke() {
            int dropshippingVendorId = 0;
            int.TryParse(_configuration.GetSection("WMApplicationSettings")["DropshipVendorID"], out dropshippingVendorId);     
      
            var vendorList = _vendorService.GetAllVendorsAsync().Result
              .Where(c => c.Id != dropshippingVendorId)
              .OrderBy(o => o.Name).ToList();
      
            return View(vendorList);
          }
        }
      }
      

Creiamo la cartella e la vista del componente in Nop.Web\Areas\Admin\Views\Shared\Components\ProductToVendorModal\Default.cshtml così:

@model List<Nop.Core.Domain.Vendors.Vendor>

<div id="setProductToVendor-window" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="setProductToVendor-window-title">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="copyproduct-window-title">Seleziona il rivenditore a cui assegnare il prodotto venduto</h4>
      </div>
      <form asp-controller="Order" asp-action="CopyAndSetProductWMtoVendor" method="post">
        <div class="form-horizontal">
          <div class="modal-body">
            <input type="hidden" id="hdnProductId" name="productId" value="" />
            <input type="hidden" id="hdnOrderItemId" name="orderItemId" value="" />
            <div class="media">
              <a class="pull-left" href="#">
                <img id="modalProductToAssignThumb" class="media-object" src="">
              </a>
              <div class="media-body">
                <h4 id="modalProductToAssign" class="media-heading">...</h4>
                <p></p>
              </div>
            </div>
            <hr />
            <div class="form-group">
              <div class="col-md-8 mx-auto">
                <label>Seleziona il rivenditore:</label>
                <select name="vendorId" class="form-control">
                  @foreach (var vendor in Model) {
                    <option value="@vendor.Id">@vendor.Name</option>
                  }
                </select>
              </div>

            </div>
            <hr />
            <div>
              <span>Il prodotto verrà:</span>
              <ul>
                <li>clonato;</li>
                <li>assegnato al rivenditore selezionato;</li>
                <li>segnato come non pubblicato, ma sarà cmq visibile dal pannello di gestione del Rivenditore;</li>
                <li>segnato con quantita' di pezzi a 0 (al prodotto clonato, non all'originale).</li>
              </ul>
            </div>
          </div>
          <div class="modal-footer">
            <button type="submit" class="btn btn-primary">
              CLONA E ASSEGNA IL PRODOTTO
            </button>
          </div>
        </div>
      </form>
    </div>
  </div>
</div>

Torniamo nella pagina contenitore di dettaglio dell'ordine in Nop.Web\Areas\Admin\Views\Order\Edit.cshtml e inseriamo al fondo la chiamata al nostro component modale appena creato con questa riga di codice:

@*Modal product to Vendor*@
@await Component.InvokeAsync("ProductToVendorModal")

Aggiungere il seguente script:

function apriModaleAssegnazione(idProduct, idOrderitem, productName, imgThumb) {
  $('#hdnProductId').val(idProduct);
  $('#hdnOrderItemId').val(idOrderitem);
  $('#modalProductToAssign').text(productName);
  $('#modalProductToAssignThumb').attr('src', imgThumb);
}
  • ERRORI

Durante il test di acquisto di prodotto DesignbestShop, in fase di selezione della spedizione non ci sono opzioni e rimane lì e non avanza, probabilmente per assenza plugin spedizione.


Update dei MessageTokens

[modifica]

Nel Libraries\Nop.Services\Messages\MessageTokenProvider.cs aggiungere i riferimenti ai servizi

  • IVendorService
  • IConfiguration

Nella property AllowedTokens aggiungere al fondo %Order.VendorDetail(s)%

 //order tokens
_allowedTokens.Add(TokenGroupNames.OrderTokens, new[]
{
	"%Order.OrderNumber%",
	"%Order.CustomerFullName%",
	"%Order.CustomerEmail%",
	"%Order.BillingFirstName%",
	"%Order.BillingLastName%",
	"%Order.BillingPhoneNumber%",
	"%Order.BillingEmail%",
	"%Order.BillingFaxNumber%",
	"%Order.BillingCompany%",
	"%Order.BillingAddress1%",
	"%Order.BillingAddress2%",
	"%Order.BillingCity%",
	"%Order.BillingCounty%",
	"%Order.BillingStateProvince%",
	"%Order.BillingZipPostalCode%",
	"%Order.BillingCountry%",
	"%Order.BillingCustomAttributes%",
	"%Order.Shippable%",
	"%Order.ShippingMethod%",
	"%Order.ShippingFirstName%",
	"%Order.ShippingLastName%",
	"%Order.ShippingPhoneNumber%",
	"%Order.ShippingEmail%",
	"%Order.ShippingFaxNumber%",
	"%Order.ShippingCompany%",
	"%Order.ShippingAddress1%",
	"%Order.ShippingAddress2%",
	"%Order.ShippingCity%",
	"%Order.ShippingCounty%",
	"%Order.ShippingStateProvince%",
	"%Order.ShippingZipPostalCode%",
	"%Order.ShippingCountry%",
	"%Order.ShippingCustomAttributes%",
	"%Order.PaymentMethod%",
	"%Order.VatNumber%",
	"%Order.CustomValues%",
	"%Order.Product(s)%",
	"%Order.CreatedOn%",
	"%Order.OrderURLForCustomer%",
	"%Order.PickupInStore%",
	"%Order.OrderId%",
	"%Order.VendorDetail(s)%" // custom token by Webmobili
});

Nel metodo AdOrderTokensAsync aggiungere la riga

tokens.Add(new Token("Order.VendorDetail(s)", await VendorDetailsAsync(order, languageId), true)); // custom token by Webmobili

E creare tutto il metodo VendorDetailsAsync

/// <summary>
/// Custom by Webmobili
/// </summary>
/// <param name="order"></param>
/// <param name="languageId"></param>
/// <returns></returns>
protected virtual async Task<string> VendorDetailsAsync(Order order, int languageId) {
	//if (vendorId == 0) return "";

	var sb = new StringBuilder();
	// intestazioni
	sb.AppendLine("<table border=\"0\" style=\"width:100%;\">");
	sb.AppendLine($"<tr style=\"background-color:{_templatesSettings.Color1};text-align:center;\">");
	sb.AppendLine($"<th>Prodotto</th>");
	sb.AppendLine($"<th>Negozio</th>");
	sb.AppendLine("</tr>");

	int vendorWMDropship = 0;
	int.TryParse(_configurationService.GetSection("WMApplicationSettings")["DropshipVendorID"], out vendorWMDropship);

	var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
	foreach (var ord in orderItems) {
		sb.AppendLine($"<tr style=\"background-color: {_templatesSettings.Color2};text-align: center;\">");

		var product = await _productService.GetProductByIdAsync(ord.ProductId);
		var vendor = await _vendorService.GetVendorByIdAsync(product.VendorId);
		var address = await _addressService.GetAddressByIdAsync(vendor.AddressId);
		var stateProvince = await _stateProvinceService.GetStateProvinceByIdAsync(address.StateProvinceId.Value);
		sb.AppendLine($"<td style=\"padding: 0.6em 0.4em;text-align: left;\">{product.Name}</td>");
		if (vendorWMDropship != vendor.Id) {
			sb.AppendLine($"<td style=\"padding: 0.6em 0.4em;text-align: left;\">{vendor.Name} - {address.Address1}, {address.City} ({stateProvince.Abbreviation})<br/>Email: <a href=\"mailto:{vendor.Email}\">{vendor.Email}</a> \t Telefono: {address.PhoneNumber}</td>");
		}
		else {
			sb.AppendLine($"<td style=\"padding: 0.6em 0.4em;text-align: left;\">{vendor.Name}, a breve riceverai l'informazione del negozio di riferimento per il tuo ordine.<br/>Email: <a href=\"mailto:{vendor.Email}\">{vendor.Email}</a> \t Telefono: {address.PhoneNumber}</td>");
		}

		sb.AppendLine("</tr>");
	}
	sb.AppendLine("</table>");
	return sb.ToString();
}

#endregion

Nella WriteTotalsAsync aggiungere il parametro opzionale int vendorId = 0 e una region iniziale

protected virtual async Task WriteTotalsAsync(Order order, Language language, StringBuilder sb, int vendorId = 0) {
	#region Costi spedizione per negozio e sconto sul totale
	if (vendorId != 0) {
		order.OrderSubtotalInclTax = 0;
		order.OrderSubtotalExclTax = 0;
		order.OrderShippingInclTax = 0; // azzero perché lo calcolo parziale per il vendor selezionato
		order.OrderShippingExclTax = 0;
		order.OrderTotal = 0;
		var orderItems = await _orderService.GetOrderItemsAsync(order.Id);
		foreach (var ord in orderItems) {
			var product = await _productService.GetProductByIdAsync(ord.ProductId);
			if (product.VendorId == vendorId) {
				if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) {
					order.OrderSubtotalInclTax += (product.Price * ord.Quantity);
					order.OrderShippingInclTax += product.AdditionalShippingCharge;
				}
				else {
					order.OrderSubtotalExclTax += (product.Price * ord.Quantity);
					order.OrderShippingExclTax += product.AdditionalShippingCharge;
				}
				order.OrderTotal += (product.Price * ord.Quantity) + product.AdditionalShippingCharge;
			}
		}
        //Applichiamo eventuale sconto all'ordine
        order.OrderTotal -= order.OrderSubTotalDiscountInclTax;
	}
	#endregion
	//...

Nella ProductListToHtmlTableAsync(Order order, int languageId, int vendorId) modificare la chiamata alla WriteTotalAsync passando anche il vendorId. Togliere if(vendorId == 0) come di seguito

//if (vendorId == 0)
//{
//we render checkout attributes and totals only for store owners (hide for vendors)

if (!string.IsNullOrEmpty(order.CheckoutAttributeDescription)) {
	sb.AppendLine("<tr><td style=\"text-align:right;\" colspan=\"1\">&nbsp;</td><td colspan=\"3\" style=\"text-align:right\">");
	sb.AppendLine(order.CheckoutAttributeDescription);
	sb.AppendLine("</td></tr>");
}

//totals
await WriteTotalsAsync(order, language, sb, vendorId);
//}
// ....

Aggiunta di un nuovo MessageTemplate

[modifica]

Con il dropshipping si rende necessario aggiungere un nuovo template di messaggi.

In Libraries\Nop.Core\Domain\Messages\MessageTemplateSystemNames.cs aggiungere il nuovo nome (dopo OrderPaidCustomerNotification)

#region Webmobili
/// <summary>
/// Represents system name of notification customer about paid order after a dropshipping sale.
/// </summary>
public const string OrderPaidDropshipCustomerNotification = "OrderPaid.DropShipCustomerNotification";
#endregion

Per creare la logica di spedizione, prima aggiungiamo il metodo nell'interfaccia in Libraries\Nop.Services\Messages\IWorkflowMessageService.cs (dopo SendOrderPaidCustomerNotification)

#region WEBMOBILI
/// <summary>
/// Sends an order paid notification to a customer after a dropshipping sale.
/// </summary>
/// <param name="order">Order instance</param>
/// <param name="languageId">Message language identifier</param>
/// <param name="attachmentFilePath">Attachment file path</param>
/// <param name="attachmentFileName">Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.</param>
/// <returns>Queued email identifier</returns>
Task<IList<int>> SendOrderPaidDropshipCustomerNotificationAsync(Order order, int languageId,
			 string attachmentFilePath = null, string attachmentFileName = null);
#endregion

e l'implementazione in Libraries\Nop.Services\Messages\WorkflowMessageService.cs

    #region WEBMOBILI
    public virtual async Task<IList<int>> SendOrderPaidDropshipCustomerNotificationAsync(Order order, int languageId,
      string attachmentFilePath = null, string attachmentFileName = null) {
      if (order == null)
        throw new ArgumentNullException(nameof(order));

      var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? _storeContext.GetCurrentStore();
      languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

      var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.OrderPaidDropshipCustomerNotification, store.Id);
      if (!messageTemplates.Any())
        return new List<int>();

      //tokens
      var commonTokens = new List<Token>();
      await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
      await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

      return await messageTemplates.SelectAwait( async messageTemplate =>
      {
        //email account
        var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

        var tokens = new List<Token>(commonTokens);
        await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

        //event notification
        await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

        var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

        var toEmail = billingAddress.Email;
        var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

        return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName, attachmentFilePath, attachmentFileName);
      }).ToListAsync();
    }

    #endregion

A questo punto è necessario aggiungere via SQL il messaggio (comprensivo di token) alla tabella MessageTemplate

INSERT INTO [MessageTemplate]([Name],[BccEmailAddresses],[Subject],[Body],[IsActive],[DelayBeforeSend],[DelayPeriodId],[AttachedDownloadId],[EmailAccountId],[LimitedToStores])
VALUES ('OrderPaid.DropShipCustomerNotification',NULL,'%Store.Name% | Pagamento effettuato per l''ordine #%Order.OrderNumber%','Compilare il BODY',1,NULL,0,0,1,0)

Lato admin è necessario modificare il controller Presentation\Nop.Web\Areas\Admin\Controllers\OrderController.cs in modo che chiami la nuova funzione quando necessario. Aggiungere i seguenti Services:

  • ICopyProductService
  • IVendorService
  • LocalizationSettings

Dopo il metodo(AddProductToOrderDetails), aggiungere:

#region DROPSHIPPING

    public virtual async Task<IActionResult> CopyAndSetProductWMtoVendor(int productId, int vendorId, int orderItemId) {

      if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageProducts))
        return AccessDeniedView();
      var orderId = 0;
      //var copyModel = new CopyProductModel { CopyImages = true, Published = false, Name = "prodotto_" + productId }; //model.CopyProductModel;
      try {
        var originalProduct = await _productService.GetProductByIdAsync(productId);

        // Clonazione prodotto dell'ordine              

        var newProduct = await _copyProductService.CopyProductAsync(originalProduct, originalProduct.Name, false,
#if DEBUG
                  false
#else
                  true
#endif
                  ); // false = publish, true = copyImages

        newProduct.Sku = originalProduct.Sku + "-" + newProduct.Id;   // nuovo SKU con presiffo del prodotto originale
        newProduct.Name = originalProduct.Name + " #";
        newProduct.StockQuantity = 0;
        newProduct.VisibleIndividually = true;
        newProduct.VendorId = vendorId; // aggiorniamo il Vendor a quello scelto dalla tendina
        await _productService.UpdateProductAsync(newProduct);

        // Recuperiamo i dettagli del OrderItem in cui si trova il prodotto da clonare
        var tmpOrderItem = await _orderService.GetOrderItemByIdAsync(orderItemId);

        tmpOrderItem.ProductId = newProduct.Id; // IMPORTANTE!!! aggiornare il productId dell' orderitem con il productId del nuovo prodotto clonato
        await _orderService.UpdateOrderItemAsync(tmpOrderItem);

        orderId = tmpOrderItem.OrderId;

        // INVIO EMAIL ORDINE AL RIVENDITORE              
        Order currentOrder = await _orderService.GetOrderByOrderItemAsync(orderItemId);
        Vendor newVendor = await _vendorService.GetVendorByIdAsync(vendorId);

        // Controllo se ordine è già stato pagato tramite Paypal o Stripe
        // In base a questo decido il template della mail

        switch (currentOrder.PaymentMethodSystemName) {

          case "Payments.CheckMoneyOrder":
            await _workflowMessageService.SendOrderPlacedVendorNotificationAsync(currentOrder, newVendor, _localizationSettings.DefaultAdminLanguageId);
            break;

          case "FoxNetSoft.StripeDirect":
          case "Payments.PayPalSmartPaymentButtons":
            await _workflowMessageService.SendOrderPaidVendorNotificationAsync(currentOrder, newVendor, _localizationSettings.DefaultAdminLanguageId);
            await _workflowMessageService.SendOrderPaidDropshipCustomerNotificationAsync(currentOrder, _localizationSettings.DefaultAdminLanguageId);
            break;

        }

      }
      catch (Exception exc) {
        _notificationService.ErrorNotification(exc.Message);
      }

      return RedirectToAction("Edit", "Order", new { id = orderId });

    }

    #endregion


Installare Plugin

[modifica]

Installare i plugin necessari che sono nel sorgente del progetto.

Plugin 7Spikes

[modifica]

Il nuovo tema è Pacific
Per installare seguire questo ordine updatando il file Presentation\Nop.Web\App_Data\Plugins.json

  • Installare 7Spikes Core
    "PluginNamesToInstall": [
        {
          "Item1": "SevenSpikes.Core",
          "Item2": "277c962d-b286-4311-b2a7-9b2ff68bb7ac"
        }
      ]
    
    (il guid è generato casualmente)
  • Avviare l'applicazione e verificare che risulti installato
  • Installare i 15 plugin necessari al funzionamento del tema + SmartSEO (accodato)
    "PluginNamesToInstall": [
        {
          "Item1": "SevenSpikes.Nop.Plugins.AjaxCart",
          "Item2": "56c6cd57-2a4d-406f-a7e2-7abf28452553"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.AjaxFilters",
          "Item2": "c694ba6e-c4e7-4dca-a67c-b10da14dc45e"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.AnywhereSliders",
          "Item2": "880b31c6-df3e-498a-94e8-5e46131b9483"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.CloudZoom",
          "Item2": "00db0bfd-f675-460d-ae2c-f77b219406b3"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.InstantSearch",
          "Item2": "5daa78be-ee0d-419a-95d0-467e704cf739"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.JCarousel",
          "Item2": "55701ae8-9b19-4005-a1dd-557232191b19"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.MegaMenu",
          "Item2": "59d86a70-949e-430e-8d53-aca34a83939b"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.NopQuickTabs",
          "Item2": "8bb5aa71-9e4f-47c9-b140-0044b80d3e00"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.PrevNextProduct",
          "Item2": "94866039-c2be-49d9-90b1-6f1f1e93c52c"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.ProductRibbons",
          "Item2": "2423c64d-a61b-496e-81d9-44bba585155f"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.QuickView",
          "Item2": "213656fb-be9c-402f-88d9-37810039b78d"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.RichBlog",
          "Item2": "a47252a0-f661-4035-9547-ed1ae335006b"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.SaleOfTheDay",
          "Item2": "11c84c82-56f0-41ad-a02f-7bdb7b62f31f"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.SmartProductCollections",
          "Item2": "d8033245-a44a-46bf-a1c1-1cb4b2cd29d3"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.NewsletterPopup",
          "Item2": "b157dc40-3923-433e-814d-c66f06cf0211"
        },
        {
          "Item1": "SevenSpikes.Nop.Plugins.SmartSEO",
          "Item2": "4d061b34-5c82-4051-ba18-df360a6da072"
        }
      ]
    
  • Avviare l'applicazione e verificare che risultino installati
  • Installare il tema
    "PluginNamesToInstall": [
        {
          "Item1": "SevenSpikes.Theme.Pacific",
          "Item2": "277c962d-b286-4311-b2a7-9b2ff68bb7ac"
        }
      ]
    

https://www.nop-templates.com/pacific-responsive-theme-for-nopcommerce-documentation


MEGA MENU Mobile fix

[modifica]

Per aggiungere i link a Designbest e al Magazine modificare la vista \Plugins\Nop.Plugin.Compiled\SevenSpikes.Nop.Plugins.MegaMenu\Views\Components\MegaMenu\MegaMenu.cshtml aggiungendo all'elemento

<ul class='mega-menu-responsive @menu.CssClass'>

gli elementi li:

<li><a href="https://www.designbest.com" target="_blank" rel="noopener"><span>RICERCA</span></a></li>
<li><a href="https://magazine.designbest.com/it" target="_blank" rel="noopener"><span>MAGAZINE</span></a></li>


Payments.CheckMoneyOrder

[modifica]

Payments.CheckMoneyOrder (da Configurazione/Plugins Locali) il nome amichevole del plugin viene utilizzato in message template delle email, si rende necessario modificare il nome con l'esatta stringa

Bonifico Bancario

Sul Server

[modifica]

È necessario installare .NET 5.0 (che è la successiva di .NET Core 3.1) , la versione Hosting Bundle da https://dotnet.microsoft.com/download/dotnet/5.0