Nop4.40.3: differenze tra le versioni

Da Webmobili Wiki.
Riga 352: Riga 352:
</syntaxhighlight>
</syntaxhighlight>


** Se il vendorID è quello di Webmobili-dropship mostro il pulsante '''CLONA e ASSEGNA a RIVENDITORE'''.  
** 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</code> a <code>GetProductByIdAsync</code>: <syntaxhighlight lang="c#">
Attenzione il metodo è cambiato da <code>prodService.GetProductById(item.ProductId).VendorId</code> a <code>GetProductByIdAsync</code>:
<syntaxhighlight lang="c#">
@{
@{
var productById = await prodService.GetProductByIdAsync(item.ProductId);
var productById = await prodService.GetProductByIdAsync(item.ProductId);
Riga 365: Riga 363:
}
}
</syntaxhighlight>
</syntaxhighlight>
** Il pulsante '''CLONA e ASSEGNA a RIVENDITORE''' apre un modale in cui selezionare il rivenditore, innanzitutto dobbiamo creare la vista/componente del modale '''ProductToVendorModal'''.
** Il pulsante '''CLONA e ASSEGNA a RIVENDITORE''' apre un modale in cui selezionare il rivenditore, innanzitutto dobbiamo creare la vista/componente del modale ''ProductToVendorModal'''. <br/>Creiamo il componente in <code>Nop.Web\Areas\Admin\Components\ProductToVendorModal.cs</code> così: <syntaxhighlight lang="c#">
Creiamo il componente in <code>Nop.Web\Areas\Admin\Components\ProductToVendorModal.cs</code> così:
<syntaxhighlight lang="c#">
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
using System;
using System;
Riga 476: Riga 472:
</syntaxhighlight>
</syntaxhighlight>


* '''ERORRI'''
* '''ERORI'''
<blockquote>
<blockquote>
Durante il test di acquisto di prodotto DesignbestShop, in fase di selezione della spedizione  
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.
non ci sono opzioni e rimane lì e non avanza, probabilmente per assenza plugin spedizione.
</blockquote>
</blockquote>

Versione delle 10:09, 7 giu 2021

Installare Nop 4.40.3

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

Operazioni sul DB

  • 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

Configurazione della Solution

  • 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à del progetto Nop.Web 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

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

  • 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>
}

LATO ADMIN

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

  • 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

  • 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

  • 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);
}
  • ERORI

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.