3462 lines
162 KiB
C#
3462 lines
162 KiB
C#
using ClosedXML.Excel;
|
|
using Microsoft.AspNetCore.StaticFiles;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Nop.Core;
|
|
using Nop.Core.Domain.Catalog;
|
|
using Nop.Core.Domain.Common;
|
|
using Nop.Core.Domain.Customers;
|
|
using Nop.Core.Domain.Directory;
|
|
using Nop.Core.Domain.Localization;
|
|
using Nop.Core.Domain.Media;
|
|
using Nop.Core.Domain.Messages;
|
|
using Nop.Core.Domain.Orders;
|
|
using Nop.Core.Domain.Payments;
|
|
using Nop.Core.Domain.Security;
|
|
using Nop.Core.Domain.Shipping;
|
|
using Nop.Core.Domain.Stores;
|
|
using Nop.Core.Domain.Tax;
|
|
using Nop.Core.Domain.Vendors;
|
|
using Nop.Core.Http;
|
|
using Nop.Core.Infrastructure;
|
|
using Nop.Data;
|
|
using Nop.Services.Catalog;
|
|
using Nop.Services.Common;
|
|
using Nop.Services.Customers;
|
|
using Nop.Services.Directory;
|
|
using Nop.Services.ExportImport.Help;
|
|
using Nop.Services.Localization;
|
|
using Nop.Services.Logging;
|
|
using Nop.Services.Media;
|
|
using Nop.Services.Messages;
|
|
using Nop.Services.Orders;
|
|
using Nop.Services.Seo;
|
|
using Nop.Services.Shipping;
|
|
using Nop.Services.Shipping.Date;
|
|
using Nop.Services.Stores;
|
|
using Nop.Services.Tax;
|
|
using Nop.Services.Vendors;
|
|
|
|
namespace Nop.Services.ExportImport;
|
|
|
|
/// <summary>
|
|
/// Import manager
|
|
/// </summary>
|
|
public partial class ImportManager : IImportManager
|
|
{
|
|
#region Fields
|
|
|
|
protected readonly CatalogSettings _catalogSettings;
|
|
protected readonly IAddressService _addressService;
|
|
protected readonly IBackInStockSubscriptionService _backInStockSubscriptionService;
|
|
protected readonly ICategoryService _categoryService;
|
|
protected readonly ICountryService _countryService;
|
|
protected readonly ICustomerActivityService _customerActivityService;
|
|
protected readonly ICustomerService _customerService;
|
|
protected readonly ICustomNumberFormatter _customNumberFormatter;
|
|
protected readonly INopDataProvider _dataProvider;
|
|
protected readonly IDateRangeService _dateRangeService;
|
|
protected readonly IGenericAttributeService _genericAttributeService;
|
|
protected readonly IHttpClientFactory _httpClientFactory;
|
|
protected readonly ILanguageService _languageService;
|
|
protected readonly ILocalizationService _localizationService;
|
|
protected readonly ILocalizedEntityService _localizedEntityService;
|
|
protected readonly ILogger _logger;
|
|
protected readonly IManufacturerService _manufacturerService;
|
|
protected readonly IMeasureService _measureService;
|
|
protected readonly INewsLetterSubscriptionService _newsLetterSubscriptionService;
|
|
protected readonly INopFileProvider _fileProvider;
|
|
protected readonly IOrderService _orderService;
|
|
protected readonly IPictureService _pictureService;
|
|
protected readonly IProductAttributeService _productAttributeService;
|
|
protected readonly IProductService _productService;
|
|
protected readonly IProductTagService _productTagService;
|
|
protected readonly IProductTemplateService _productTemplateService;
|
|
protected readonly IServiceScopeFactory _serviceScopeFactory;
|
|
protected readonly IShippingService _shippingService;
|
|
protected readonly ISpecificationAttributeService _specificationAttributeService;
|
|
protected readonly IStateProvinceService _stateProvinceService;
|
|
protected readonly IStoreContext _storeContext;
|
|
protected readonly IStoreMappingService _storeMappingService;
|
|
protected readonly IStoreService _storeService;
|
|
protected readonly ITaxCategoryService _taxCategoryService;
|
|
protected readonly IUrlRecordService _urlRecordService;
|
|
protected readonly IVendorService _vendorService;
|
|
protected readonly IWorkContext _workContext;
|
|
protected readonly MediaSettings _mediaSettings;
|
|
protected readonly SecuritySettings _securitySettings;
|
|
protected readonly TaxSettings _taxSettings;
|
|
protected readonly VendorSettings _vendorSettings;
|
|
private static readonly string[] _separator = [">>"];
|
|
|
|
#endregion
|
|
|
|
#region Ctor
|
|
|
|
public ImportManager(CatalogSettings catalogSettings,
|
|
IAddressService addressService,
|
|
IBackInStockSubscriptionService backInStockSubscriptionService,
|
|
ICategoryService categoryService,
|
|
ICountryService countryService,
|
|
ICustomerActivityService customerActivityService,
|
|
ICustomerService customerService,
|
|
ICustomNumberFormatter customNumberFormatter,
|
|
INopDataProvider dataProvider,
|
|
IDateRangeService dateRangeService,
|
|
IGenericAttributeService genericAttributeService,
|
|
IHttpClientFactory httpClientFactory,
|
|
ILanguageService languageService,
|
|
ILocalizationService localizationService,
|
|
ILocalizedEntityService localizedEntityService,
|
|
ILogger logger,
|
|
IManufacturerService manufacturerService,
|
|
IMeasureService measureService,
|
|
INewsLetterSubscriptionService newsLetterSubscriptionService,
|
|
INopFileProvider fileProvider,
|
|
IOrderService orderService,
|
|
IPictureService pictureService,
|
|
IProductAttributeService productAttributeService,
|
|
IProductService productService,
|
|
IProductTagService productTagService,
|
|
IProductTemplateService productTemplateService,
|
|
IServiceScopeFactory serviceScopeFactory,
|
|
IShippingService shippingService,
|
|
ISpecificationAttributeService specificationAttributeService,
|
|
IStateProvinceService stateProvinceService,
|
|
IStoreContext storeContext,
|
|
IStoreMappingService storeMappingService,
|
|
IStoreService storeService,
|
|
ITaxCategoryService taxCategoryService,
|
|
IUrlRecordService urlRecordService,
|
|
IVendorService vendorService,
|
|
IWorkContext workContext,
|
|
MediaSettings mediaSettings,
|
|
SecuritySettings securitySettings,
|
|
TaxSettings taxSettings,
|
|
VendorSettings vendorSettings)
|
|
{
|
|
_addressService = addressService;
|
|
_backInStockSubscriptionService = backInStockSubscriptionService;
|
|
_catalogSettings = catalogSettings;
|
|
_categoryService = categoryService;
|
|
_countryService = countryService;
|
|
_customerActivityService = customerActivityService;
|
|
_customerService = customerService;
|
|
_customNumberFormatter = customNumberFormatter;
|
|
_dataProvider = dataProvider;
|
|
_dateRangeService = dateRangeService;
|
|
_genericAttributeService = genericAttributeService;
|
|
_httpClientFactory = httpClientFactory;
|
|
_fileProvider = fileProvider;
|
|
_languageService = languageService;
|
|
_localizationService = localizationService;
|
|
_localizedEntityService = localizedEntityService;
|
|
_logger = logger;
|
|
_manufacturerService = manufacturerService;
|
|
_measureService = measureService;
|
|
_newsLetterSubscriptionService = newsLetterSubscriptionService;
|
|
_orderService = orderService;
|
|
_pictureService = pictureService;
|
|
_productAttributeService = productAttributeService;
|
|
_productService = productService;
|
|
_productTagService = productTagService;
|
|
_productTemplateService = productTemplateService;
|
|
_serviceScopeFactory = serviceScopeFactory;
|
|
_shippingService = shippingService;
|
|
_specificationAttributeService = specificationAttributeService;
|
|
_stateProvinceService = stateProvinceService;
|
|
_storeContext = storeContext;
|
|
_storeMappingService = storeMappingService;
|
|
_storeService = storeService;
|
|
_taxCategoryService = taxCategoryService;
|
|
_urlRecordService = urlRecordService;
|
|
_vendorService = vendorService;
|
|
_workContext = workContext;
|
|
_mediaSettings = mediaSettings;
|
|
_securitySettings = securitySettings;
|
|
_taxSettings = taxSettings;
|
|
_vendorSettings = vendorSettings;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Utilities
|
|
|
|
protected virtual ExportedAdditionalProductInfoType GetTypeOfExportedAdditionalProductInfo(IXLWorksheet defaultWorksheet, List<IXLWorksheet> localizedWorksheets, PropertyManager<ExportProductAttribute, Language> productAttributeManager, PropertyManager<ExportSpecificationAttribute, Language> specificationAttributeManager, PropertyManager<ExportTierPrice, Language> tierPriceManager, int iRow)
|
|
{
|
|
productAttributeManager.ReadDefaultFromXlsx(defaultWorksheet, iRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
if (productAttributeManager.IsCaption)
|
|
{
|
|
foreach (var worksheet in localizedWorksheets)
|
|
productAttributeManager.ReadLocalizedFromXlsx(worksheet, iRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
return ExportedAdditionalProductInfoType.ProductAttribute;
|
|
}
|
|
|
|
specificationAttributeManager.ReadDefaultFromXlsx(defaultWorksheet, iRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
if (specificationAttributeManager.IsCaption)
|
|
{
|
|
foreach (var worksheet in localizedWorksheets)
|
|
specificationAttributeManager.ReadLocalizedFromXlsx(worksheet, iRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
return ExportedAdditionalProductInfoType.SpecificationAttribute;
|
|
}
|
|
|
|
tierPriceManager.ReadDefaultFromXlsx(defaultWorksheet, iRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
if (tierPriceManager.IsCaption)
|
|
return ExportedAdditionalProductInfoType.TierPrices;
|
|
|
|
return ExportedAdditionalProductInfoType.NotSpecified;
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task SetOutLineForSpecificationAttributeRowAsync(object cellValue, IXLWorksheet worksheet, int endRow)
|
|
{
|
|
var attributeType = (cellValue ?? string.Empty).ToString();
|
|
|
|
if (attributeType.Equals("AttributeType", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
worksheet.Row(endRow).OutlineLevel = 1;
|
|
}
|
|
else
|
|
{
|
|
if ((await SpecificationAttributeType.Option.ToSelectListAsync(useLocalization: false))
|
|
.Any(p => p.Text.Equals(attributeType, StringComparison.InvariantCultureIgnoreCase)))
|
|
worksheet.Row(endRow).OutlineLevel = 1;
|
|
else if (int.TryParse(attributeType, out var attributeTypeId) && Enum.IsDefined(typeof(SpecificationAttributeType), attributeTypeId))
|
|
worksheet.Row(endRow).OutlineLevel = 1;
|
|
}
|
|
}
|
|
|
|
protected virtual void CopyDataToNewFile(ImportProductMetadata metadata, IXLWorksheet worksheet, string filePath, int startRow, int endRow, int endCell)
|
|
{
|
|
using var workbook = new XLWorkbook();
|
|
|
|
// get handles to the worksheets
|
|
var outWorksheet = workbook.Worksheets.Add(nameof(Product));
|
|
metadata.Manager.WriteDefaultCaption(outWorksheet);
|
|
var outRow = 2;
|
|
for (var row = startRow; row <= endRow; row++)
|
|
{
|
|
outWorksheet.Row(outRow).OutlineLevel = worksheet.Row(row).OutlineLevel;
|
|
|
|
for (var cell = 1; cell <= endCell; cell++)
|
|
outWorksheet.Row(outRow).Cell(cell).Value = worksheet.Row(row).Cell(cell).Value;
|
|
|
|
outRow += 1;
|
|
}
|
|
|
|
workbook.SaveAs(filePath);
|
|
}
|
|
|
|
protected virtual int GetColumnIndex(string[] properties, string columnName)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(properties);
|
|
ArgumentNullException.ThrowIfNull(columnName);
|
|
|
|
for (var i = 0; i < properties.Length; i++)
|
|
if (properties[i].Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
|
|
return i + 1; //excel indexes start from 1
|
|
return 0;
|
|
}
|
|
|
|
protected virtual string GetMimeTypeFromFilePath(string filePath)
|
|
{
|
|
new FileExtensionContentTypeProvider().TryGetContentType(filePath, out var mimeType);
|
|
|
|
//set to jpeg in case mime type cannot be found
|
|
return mimeType ?? _pictureService.GetPictureContentTypeByFileExtension(_fileProvider.GetFileExtension(filePath));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates or loads the image
|
|
/// </summary>
|
|
/// <param name="picturePath">The path to the image file</param>
|
|
/// <param name="name">The name of the object</param>
|
|
/// <param name="picId">Image identifier, may be null</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the image or null if the image has not changed
|
|
/// </returns>
|
|
protected virtual async Task<Picture> LoadPictureAsync(string picturePath, string name, int? picId = null)
|
|
{
|
|
if (string.IsNullOrEmpty(picturePath) || !_fileProvider.FileExists(picturePath))
|
|
return null;
|
|
|
|
var mimeType = GetMimeTypeFromFilePath(picturePath);
|
|
if (string.IsNullOrEmpty(mimeType))
|
|
return null;
|
|
|
|
var newPictureBinary = await _fileProvider.ReadAllBytesAsync(picturePath);
|
|
var pictureAlreadyExists = false;
|
|
if (picId != null)
|
|
{
|
|
//compare with existing product pictures
|
|
var existingPicture = await _pictureService.GetPictureByIdAsync(picId.Value);
|
|
if (existingPicture != null)
|
|
{
|
|
var existingBinary = await _pictureService.LoadPictureBinaryAsync(existingPicture);
|
|
//picture binary after validation (like in database)
|
|
var validatedPictureBinary = await _pictureService.ValidatePictureAsync(newPictureBinary, mimeType, name);
|
|
if (existingBinary.SequenceEqual(validatedPictureBinary) ||
|
|
existingBinary.SequenceEqual(newPictureBinary))
|
|
{
|
|
pictureAlreadyExists = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pictureAlreadyExists)
|
|
return null;
|
|
|
|
var newPicture = await _pictureService.InsertPictureAsync(newPictureBinary, mimeType, await _pictureService.GetPictureSeNameAsync(name));
|
|
return newPicture;
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task LogPictureInsertErrorAsync(string picturePath, Exception ex)
|
|
{
|
|
var extension = _fileProvider.GetFileExtension(picturePath);
|
|
var name = _fileProvider.GetFileNameWithoutExtension(picturePath);
|
|
|
|
var point = string.IsNullOrEmpty(extension) ? string.Empty : ".";
|
|
var fileName = _fileProvider.FileExists(picturePath) ? $"{name}{point}{extension}" : string.Empty;
|
|
|
|
await _logger.ErrorAsync($"Insert picture failed (file name: {fileName})", ex);
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportProductImagesUsingServicesAsync(IList<ProductPictureMetadata> productPictureMetadata)
|
|
{
|
|
foreach (var product in productPictureMetadata)
|
|
{
|
|
foreach (var picturePath in new[] { product.Picture1Path, product.Picture2Path, product.Picture3Path })
|
|
{
|
|
if (string.IsNullOrEmpty(picturePath))
|
|
continue;
|
|
|
|
var mimeType = GetMimeTypeFromFilePath(picturePath);
|
|
if (string.IsNullOrEmpty(mimeType))
|
|
continue;
|
|
|
|
var newPictureBinary = await _fileProvider.ReadAllBytesAsync(picturePath);
|
|
var pictureAlreadyExists = false;
|
|
if (!product.IsNew)
|
|
{
|
|
//compare with existing product pictures
|
|
var existingPictures = await _pictureService.GetPicturesByProductIdAsync(product.ProductItem.Id);
|
|
foreach (var existingPicture in existingPictures)
|
|
{
|
|
var existingBinary = await _pictureService.LoadPictureBinaryAsync(existingPicture);
|
|
//picture binary after validation (like in database)
|
|
var validatedPictureBinary = await _pictureService.ValidatePictureAsync(newPictureBinary, mimeType, picturePath);
|
|
if (!existingBinary.SequenceEqual(validatedPictureBinary) &&
|
|
!existingBinary.SequenceEqual(newPictureBinary))
|
|
continue;
|
|
//the same picture content
|
|
pictureAlreadyExists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pictureAlreadyExists)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
var newPicture = await _pictureService.InsertPictureAsync(newPictureBinary, mimeType, await _pictureService.GetPictureSeNameAsync(product.ProductItem.Name));
|
|
await _productService.InsertProductPictureAsync(new ProductPicture
|
|
{
|
|
//EF has some weird issue if we set "Picture = newPicture" instead of "PictureId = newPicture.Id"
|
|
//pictures are duplicated
|
|
//maybe because entity size is too large
|
|
PictureId = newPicture.Id,
|
|
DisplayOrder = 1,
|
|
ProductId = product.ProductItem.Id
|
|
});
|
|
await _productService.UpdateProductAsync(product.ProductItem);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await LogPictureInsertErrorAsync(picturePath, ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportProductImagesUsingHashAsync(IList<ProductPictureMetadata> productPictureMetadata, IList<Product> allProductsBySku)
|
|
{
|
|
//performance optimization, load all pictures hashes
|
|
//it will only be used if the images are stored in the SQL Server database (not compact)
|
|
var trimByteCount = _dataProvider.SupportedLengthOfBinaryHash - 1;
|
|
var productsImagesIds = await _productService.GetProductsImagesIdsAsync(allProductsBySku.Select(p => p.Id).ToArray());
|
|
|
|
var allProductPictureIds = productsImagesIds.SelectMany(p => p.Value);
|
|
|
|
var allPicturesHashes = allProductPictureIds.Any() ? await _dataProvider.GetFieldHashesAsync<PictureBinary>(p => allProductPictureIds.Contains(p.PictureId),
|
|
p => p.PictureId, p => p.BinaryData) : new Dictionary<int, string>();
|
|
|
|
foreach (var product in productPictureMetadata)
|
|
{
|
|
foreach (var picturePath in new[] { product.Picture1Path, product.Picture2Path, product.Picture3Path })
|
|
{
|
|
if (string.IsNullOrEmpty(picturePath))
|
|
continue;
|
|
try
|
|
{
|
|
var mimeType = GetMimeTypeFromFilePath(picturePath);
|
|
if (string.IsNullOrEmpty(mimeType))
|
|
continue;
|
|
|
|
var newPictureBinary = await _fileProvider.ReadAllBytesAsync(picturePath);
|
|
var pictureAlreadyExists = false;
|
|
var seoFileName = await _pictureService.GetPictureSeNameAsync(product.ProductItem.Name);
|
|
|
|
if (!product.IsNew)
|
|
{
|
|
var newImageHash = HashHelper.CreateHash(
|
|
newPictureBinary,
|
|
ExportImportDefaults.ImageHashAlgorithm,
|
|
trimByteCount);
|
|
|
|
var newValidatedImageHash = HashHelper.CreateHash(
|
|
await _pictureService.ValidatePictureAsync(newPictureBinary, mimeType, seoFileName),
|
|
ExportImportDefaults.ImageHashAlgorithm,
|
|
trimByteCount);
|
|
|
|
var imagesIds = productsImagesIds.TryGetValue(product.ProductItem.Id, out var value) ? value : Array.Empty<int>();
|
|
|
|
pictureAlreadyExists = allPicturesHashes.Where(p => imagesIds.Contains(p.Key))
|
|
.Select(p => p.Value)
|
|
.Any(p =>
|
|
p.Equals(newImageHash, StringComparison.OrdinalIgnoreCase) ||
|
|
p.Equals(newValidatedImageHash, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
if (pictureAlreadyExists)
|
|
continue;
|
|
|
|
var newPicture = await _pictureService.InsertPictureAsync(newPictureBinary, mimeType, seoFileName);
|
|
|
|
await _productService.InsertProductPictureAsync(new ProductPicture
|
|
{
|
|
//EF has some weird issue if we set "Picture = newPicture" instead of "PictureId = newPicture.Id"
|
|
//pictures are duplicated
|
|
//maybe because entity size is too large
|
|
PictureId = newPicture.Id,
|
|
DisplayOrder = 1,
|
|
ProductId = product.ProductItem.Id
|
|
});
|
|
|
|
await _productService.UpdateProductAsync(product.ProductItem);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await LogPictureInsertErrorAsync(picturePath, ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task<(string seName, bool isParentCategoryExists)> UpdateCategoryByXlsxAsync(Category category, PropertyManager<Category, Language> manager, Dictionary<string, ValueTask<Category>> allCategories, bool isNew)
|
|
{
|
|
var seName = string.Empty;
|
|
var isParentCategoryExists = true;
|
|
var isParentCategorySet = false;
|
|
|
|
foreach (var property in manager.GetDefaultProperties)
|
|
{
|
|
switch (property.PropertyName)
|
|
{
|
|
case "Name":
|
|
category.Name = property.StringValue.Split(_separator, StringSplitOptions.RemoveEmptyEntries).Last().Trim();
|
|
break;
|
|
case "Description":
|
|
category.Description = property.StringValue;
|
|
break;
|
|
case "CategoryTemplateId":
|
|
category.CategoryTemplateId = property.IntValue;
|
|
break;
|
|
case "MetaKeywords":
|
|
category.MetaKeywords = property.StringValue;
|
|
break;
|
|
case "MetaDescription":
|
|
category.MetaDescription = property.StringValue;
|
|
break;
|
|
case "MetaTitle":
|
|
category.MetaTitle = property.StringValue;
|
|
break;
|
|
case "ParentCategoryId":
|
|
if (!isParentCategorySet)
|
|
{
|
|
var parentCategory = await await allCategories.Values.FirstOrDefaultAwaitAsync(async c => (await c).Id == property.IntValue);
|
|
isParentCategorySet = parentCategory != null;
|
|
|
|
isParentCategoryExists = isParentCategorySet || property.IntValue == 0;
|
|
|
|
category.ParentCategoryId = parentCategory?.Id ?? property.IntValue;
|
|
}
|
|
|
|
break;
|
|
case "ParentCategoryName":
|
|
if (_catalogSettings.ExportImportCategoriesUsingCategoryName && !isParentCategorySet)
|
|
{
|
|
var categoryName = manager.GetDefaultProperty("ParentCategoryName").StringValue;
|
|
if (!string.IsNullOrEmpty(categoryName))
|
|
{
|
|
var parentCategory = allCategories.TryGetValue(categoryName, out var value)
|
|
//try find category by full name with all parent category names
|
|
? await value
|
|
//try find category by name
|
|
: await await allCategories.Values.FirstOrDefaultAwaitAsync(async c => (await c).Name.Equals(categoryName, StringComparison.InvariantCulture));
|
|
|
|
if (parentCategory != null)
|
|
{
|
|
category.ParentCategoryId = parentCategory.Id;
|
|
isParentCategorySet = true;
|
|
}
|
|
else
|
|
{
|
|
isParentCategoryExists = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
case "Picture":
|
|
var picture = await LoadPictureAsync(manager.GetDefaultProperty("Picture").StringValue, category.Name, isNew ? null : (int?)category.PictureId);
|
|
if (picture != null)
|
|
category.PictureId = picture.Id;
|
|
break;
|
|
case "PageSize":
|
|
category.PageSize = property.IntValue;
|
|
break;
|
|
case "AllowCustomersToSelectPageSize":
|
|
category.AllowCustomersToSelectPageSize = property.BooleanValue;
|
|
break;
|
|
case "PageSizeOptions":
|
|
category.PageSizeOptions = property.StringValue;
|
|
break;
|
|
case "ShowOnHomepage":
|
|
category.ShowOnHomepage = property.BooleanValue;
|
|
break;
|
|
case "PriceRangeFiltering":
|
|
category.PriceRangeFiltering = property.BooleanValue;
|
|
break;
|
|
case "PriceFrom":
|
|
category.PriceFrom = property.DecimalValue;
|
|
break;
|
|
case "PriceTo":
|
|
category.PriceTo = property.DecimalValue;
|
|
break;
|
|
case "AutomaticallyCalculatePriceRange":
|
|
category.ManuallyPriceRange = property.BooleanValue;
|
|
break;
|
|
case "IncludeInTopMenu":
|
|
category.IncludeInTopMenu = property.BooleanValue;
|
|
break;
|
|
case "Published":
|
|
category.Published = property.BooleanValue;
|
|
break;
|
|
case "DisplayOrder":
|
|
category.DisplayOrder = property.IntValue;
|
|
break;
|
|
case "SeName":
|
|
seName = property.StringValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
category.UpdatedOnUtc = DateTime.UtcNow;
|
|
return (seName, isParentCategoryExists);
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task<(Category category, bool isNew, string curentCategoryBreadCrumb)> GetCategoryFromXlsxAsync(PropertyManager<Category, Language> manager, IXLWorksheet worksheet, int iRow, Dictionary<string, ValueTask<Category>> allCategories)
|
|
{
|
|
manager.ReadDefaultFromXlsx(worksheet, iRow);
|
|
|
|
//try get category from database by ID
|
|
var category = await await allCategories.Values.FirstOrDefaultAwaitAsync(async c => (await c).Id == manager.GetDefaultProperty("Id")?.IntValue);
|
|
|
|
if (_catalogSettings.ExportImportCategoriesUsingCategoryName && category == null)
|
|
{
|
|
var categoryName = manager.GetDefaultProperty("Name").StringValue;
|
|
if (!string.IsNullOrEmpty(categoryName))
|
|
{
|
|
category = allCategories.TryGetValue(categoryName, out var value)
|
|
//try find category by full name with all parent category names
|
|
? await value
|
|
//try find category by name
|
|
: await await allCategories.Values.FirstOrDefaultAwaitAsync(async c => (await c).Name.Equals(categoryName, StringComparison.InvariantCulture));
|
|
}
|
|
}
|
|
|
|
var isNew = category == null;
|
|
|
|
category ??= new Category();
|
|
|
|
var curentCategoryBreadCrumb = string.Empty;
|
|
|
|
if (isNew)
|
|
{
|
|
category.CreatedOnUtc = DateTime.UtcNow;
|
|
//default values
|
|
category.PageSize = _catalogSettings.DefaultCategoryPageSize;
|
|
category.PageSizeOptions = _catalogSettings.DefaultCategoryPageSizeOptions;
|
|
category.Published = true;
|
|
category.IncludeInTopMenu = true;
|
|
category.AllowCustomersToSelectPageSize = true;
|
|
}
|
|
else
|
|
curentCategoryBreadCrumb = await _categoryService.GetFormattedBreadCrumbAsync(category);
|
|
|
|
return (category, isNew, curentCategoryBreadCrumb);
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task SaveCategoryAsync(bool isNew, Category category, Dictionary<string, ValueTask<Category>> allCategories, string curentCategoryBreadCrumb, bool setSeName, string seName)
|
|
{
|
|
if (isNew)
|
|
await _categoryService.InsertCategoryAsync(category);
|
|
else
|
|
await _categoryService.UpdateCategoryAsync(category);
|
|
|
|
var categoryBreadCrumb = await _categoryService.GetFormattedBreadCrumbAsync(category);
|
|
if (!allCategories.ContainsKey(categoryBreadCrumb))
|
|
allCategories.Add(categoryBreadCrumb, new ValueTask<Category>(category));
|
|
if (!string.IsNullOrEmpty(curentCategoryBreadCrumb) && allCategories.ContainsKey(curentCategoryBreadCrumb) &&
|
|
categoryBreadCrumb != curentCategoryBreadCrumb)
|
|
allCategories.Remove(curentCategoryBreadCrumb);
|
|
|
|
//search engine name
|
|
if (setSeName)
|
|
await _urlRecordService.SaveSlugAsync(category, await _urlRecordService.ValidateSeNameAsync(category, seName, category.Name, true), 0);
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportCategoryLocalizedAsync(Category category, WorkbookMetadata<Category> metadata, PropertyManager<Category, Language> manager, int iRow, IList<Language> languages)
|
|
{
|
|
if (!metadata.LocalizedWorksheets.Any())
|
|
return;
|
|
|
|
var setSeName = metadata.LocalizedProperties.Any(p => p.PropertyName == "SeName");
|
|
foreach (var language in languages)
|
|
{
|
|
var lWorksheet = metadata.LocalizedWorksheets.FirstOrDefault(ws => ws.Name.Equals(language.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase));
|
|
if (lWorksheet == null)
|
|
continue;
|
|
|
|
manager.CurrentLanguage = language;
|
|
manager.ReadLocalizedFromXlsx(lWorksheet, iRow);
|
|
|
|
foreach (var property in manager.GetLocalizedProperties)
|
|
{
|
|
string localizedName = null;
|
|
|
|
switch (property.PropertyName)
|
|
{
|
|
case "Name":
|
|
localizedName = property.StringValue;
|
|
await _localizedEntityService.SaveLocalizedValueAsync(category, c => c.Name, localizedName, language.Id);
|
|
break;
|
|
case "Description":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(category, c => c.Description, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaKeywords":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(category, c => c.MetaKeywords, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaDescription":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(category, c => c.MetaDescription, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaTitle":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(category, m => m.MetaTitle, property.StringValue, language.Id);
|
|
break;
|
|
case "SeName":
|
|
//search engine name
|
|
if (setSeName)
|
|
{
|
|
var lSeName = await _urlRecordService.ValidateSeNameAsync(category, property.StringValue, localizedName, false);
|
|
await _urlRecordService.SaveSlugAsync(category, lSeName, language.Id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportManufaturerLocalizedAsync(Manufacturer manufacturer, WorkbookMetadata<Manufacturer> metadata, PropertyManager<Manufacturer, Language> manager, int iRow, IList<Language> languages)
|
|
{
|
|
if (!metadata.LocalizedWorksheets.Any())
|
|
return;
|
|
|
|
var setSeName = metadata.LocalizedProperties.Any(p => p.PropertyName == "SeName");
|
|
foreach (var language in languages)
|
|
{
|
|
var lWorksheet = metadata.LocalizedWorksheets.FirstOrDefault(ws => ws.Name.Equals(language.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase));
|
|
if (lWorksheet == null)
|
|
continue;
|
|
|
|
manager.CurrentLanguage = language;
|
|
manager.ReadLocalizedFromXlsx(lWorksheet, iRow);
|
|
|
|
foreach (var property in manager.GetLocalizedProperties)
|
|
{
|
|
string localizedName = null;
|
|
|
|
switch (property.PropertyName)
|
|
{
|
|
case "Name":
|
|
localizedName = property.StringValue;
|
|
await _localizedEntityService.SaveLocalizedValueAsync(manufacturer, m => m.Name, localizedName, language.Id);
|
|
break;
|
|
case "Description":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(manufacturer, m => m.Description, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaKeywords":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(manufacturer, m => m.MetaKeywords, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaDescription":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(manufacturer, m => m.MetaDescription, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaTitle":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(manufacturer, m => m.MetaTitle, property.StringValue, language.Id);
|
|
break;
|
|
case "SeName":
|
|
//search engine name
|
|
if (setSeName)
|
|
{
|
|
var localizedSeName = await _urlRecordService.ValidateSeNameAsync(manufacturer, property.StringValue, localizedName, false);
|
|
await _urlRecordService.SaveSlugAsync(manufacturer, localizedSeName, language.Id);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task SetOutLineForProductAttributeRowAsync(object cellValue, IXLWorksheet worksheet, int endRow)
|
|
{
|
|
try
|
|
{
|
|
var aid = Convert.ToInt32(cellValue ?? -1);
|
|
|
|
var productAttribute = await _productAttributeService.GetProductAttributeByIdAsync(aid);
|
|
|
|
if (productAttribute != null)
|
|
worksheet.Row(endRow).OutlineLevel = 1;
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
if ((cellValue ?? string.Empty).ToString() == "AttributeId")
|
|
worksheet.Row(endRow).OutlineLevel = 1;
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportProductAttributeAsync(ImportProductMetadata metadata, Product lastLoadedProduct, IList<Language> languages, int iRow)
|
|
{
|
|
var productAttributeManager = metadata.ProductAttributeManager;
|
|
if (!_catalogSettings.ExportImportProductAttributes || lastLoadedProduct == null || productAttributeManager.IsCaption)
|
|
return;
|
|
|
|
var productAttributeId = productAttributeManager.GetDefaultProperty("AttributeId").IntValue;
|
|
var attributeControlTypeId = productAttributeManager.GetDefaultProperty("AttributeControlType").IntValue;
|
|
|
|
var productAttributeValueId = productAttributeManager.GetDefaultProperty("ProductAttributeValueId").IntValue;
|
|
var associatedProductId = productAttributeManager.GetDefaultProperty("AssociatedProductId").IntValue;
|
|
var valueName = productAttributeManager.GetDefaultProperty("ValueName").StringValue;
|
|
var attributeValueTypeId = productAttributeManager.GetDefaultProperty("AttributeValueType").IntValue;
|
|
var colorSquaresRgb = productAttributeManager.GetDefaultProperty("ColorSquaresRgb").StringValue;
|
|
var imageSquaresPictureId = productAttributeManager.GetDefaultProperty("ImageSquaresPictureId").IntValue;
|
|
var priceAdjustment = productAttributeManager.GetDefaultProperty("PriceAdjustment").DecimalValue;
|
|
var priceAdjustmentUsePercentage = productAttributeManager.GetDefaultProperty("PriceAdjustmentUsePercentage").BooleanValue;
|
|
var weightAdjustment = productAttributeManager.GetDefaultProperty("WeightAdjustment").DecimalValue;
|
|
var cost = productAttributeManager.GetDefaultProperty("Cost").DecimalValue;
|
|
var customerEntersQty = productAttributeManager.GetDefaultProperty("CustomerEntersQty").BooleanValue;
|
|
var quantity = productAttributeManager.GetDefaultProperty("Quantity").IntValue;
|
|
var isPreSelected = productAttributeManager.GetDefaultProperty("IsPreSelected").BooleanValue;
|
|
var displayOrder = productAttributeManager.GetDefaultProperty("DisplayOrder").IntValue;
|
|
var pictureIdsStr = productAttributeManager.GetDefaultProperty("PictureIds").StringValue;
|
|
var textPrompt = productAttributeManager.GetDefaultProperty("AttributeTextPrompt").StringValue;
|
|
var isRequired = productAttributeManager.GetDefaultProperty("AttributeIsRequired").BooleanValue;
|
|
var attributeDisplayOrder = productAttributeManager.GetDefaultProperty("AttributeDisplayOrder").IntValue;
|
|
|
|
var productAttributeMapping = (await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(lastLoadedProduct.Id))
|
|
.FirstOrDefault(pam => pam.ProductAttributeId == productAttributeId);
|
|
var pictureIds = new List<int>();
|
|
if (!string.IsNullOrWhiteSpace(pictureIdsStr))
|
|
pictureIds = Array.ConvertAll(pictureIdsStr.Split(new[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries), int.Parse).ToList();
|
|
|
|
if (productAttributeMapping == null)
|
|
{
|
|
//insert mapping
|
|
productAttributeMapping = new ProductAttributeMapping
|
|
{
|
|
ProductId = lastLoadedProduct.Id,
|
|
ProductAttributeId = productAttributeId,
|
|
TextPrompt = textPrompt,
|
|
IsRequired = isRequired,
|
|
AttributeControlTypeId = attributeControlTypeId,
|
|
DisplayOrder = attributeDisplayOrder
|
|
};
|
|
await _productAttributeService.InsertProductAttributeMappingAsync(productAttributeMapping);
|
|
}
|
|
else
|
|
{
|
|
productAttributeMapping.AttributeControlTypeId = attributeControlTypeId;
|
|
productAttributeMapping.TextPrompt = textPrompt;
|
|
productAttributeMapping.IsRequired = isRequired;
|
|
productAttributeMapping.DisplayOrder = attributeDisplayOrder;
|
|
await _productAttributeService.UpdateProductAttributeMappingAsync(productAttributeMapping);
|
|
}
|
|
|
|
var pav = (await _productAttributeService.GetProductAttributeValuesAsync(productAttributeMapping.Id))
|
|
.FirstOrDefault(p => p.Id == productAttributeValueId);
|
|
|
|
//var pav = await _productAttributeService.GetProductAttributeValueByIdAsync(productAttributeValueId);
|
|
|
|
var attributeControlType = (AttributeControlType)attributeControlTypeId;
|
|
|
|
async Task saveAttributeValuePicturesAsync(Product product, ProductAttributeValue value)
|
|
{
|
|
var existingValuePictures =
|
|
await _productAttributeService.GetProductAttributeValuePicturesAsync(value.Id);
|
|
var productPictureIds = (await _pictureService.GetPicturesByProductIdAsync(product.Id))
|
|
.Select(p => p.Id).ToList();
|
|
|
|
//delete manufacturers
|
|
foreach (var existingValuePicture in existingValuePictures)
|
|
if (pictureIds.Contains(existingValuePicture.PictureId) ||
|
|
!productPictureIds.Contains(existingValuePicture.PictureId))
|
|
await _productAttributeService.DeleteProductAttributeValuePictureAsync(existingValuePicture);
|
|
|
|
//add manufacturers
|
|
foreach (var pictureId in pictureIds)
|
|
{
|
|
if (!productPictureIds.Contains(pictureId))
|
|
continue;
|
|
|
|
if (_productAttributeService.FindProductAttributeValuePicture(existingValuePictures, value.Id,
|
|
pictureId) == null)
|
|
{
|
|
await _productAttributeService.InsertProductAttributeValuePictureAsync(
|
|
new ProductAttributeValuePicture
|
|
{
|
|
ProductAttributeValueId = value.Id,
|
|
PictureId = pictureId
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!metadata.LocalizedWorksheets.Any())
|
|
return;
|
|
|
|
foreach (var language in languages)
|
|
{
|
|
var lWorksheet = metadata.LocalizedWorksheets.FirstOrDefault(ws =>
|
|
ws.Name.Equals(language.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase));
|
|
if (lWorksheet == null)
|
|
continue;
|
|
|
|
productAttributeManager.CurrentLanguage = language;
|
|
productAttributeManager.ReadLocalizedFromXlsx(lWorksheet, iRow,
|
|
ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
valueName = productAttributeManager.GetLocalizedProperty("ValueName").StringValue;
|
|
textPrompt = productAttributeManager.GetLocalizedProperty("AttributeTextPrompt").StringValue;
|
|
|
|
await _localizedEntityService.SaveLocalizedValueAsync(pav, p => p.Name, valueName, language.Id);
|
|
await _localizedEntityService.SaveLocalizedValueAsync(productAttributeMapping, p => p.TextPrompt,
|
|
textPrompt, language.Id);
|
|
|
|
switch (attributeControlType)
|
|
{
|
|
case AttributeControlType.Datepicker:
|
|
case AttributeControlType.FileUpload:
|
|
case AttributeControlType.MultilineTextbox:
|
|
case AttributeControlType.TextBox:
|
|
if (productAttributeMapping.ValidationRulesAllowed())
|
|
{
|
|
var defaultValue = productAttributeManager.GetLocalizedProperty("DefaultValue")
|
|
?.StringValue;
|
|
await _localizedEntityService.SaveLocalizedValueAsync(productAttributeMapping,
|
|
p => p.DefaultValue, defaultValue, language.Id);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pav == null)
|
|
{
|
|
switch (attributeControlType)
|
|
{
|
|
case AttributeControlType.Datepicker:
|
|
case AttributeControlType.FileUpload:
|
|
case AttributeControlType.MultilineTextbox:
|
|
case AttributeControlType.TextBox:
|
|
if (productAttributeMapping.ValidationRulesAllowed())
|
|
{
|
|
productAttributeMapping.ValidationMinLength = productAttributeManager.GetDefaultProperty("ValidationMinLength")?.IntValueNullable;
|
|
productAttributeMapping.ValidationMaxLength = productAttributeManager.GetDefaultProperty("ValidationMaxLength")?.IntValueNullable;
|
|
productAttributeMapping.ValidationFileMaximumSize = productAttributeManager.GetDefaultProperty("ValidationFileMaximumSize")?.IntValueNullable;
|
|
productAttributeMapping.ValidationFileAllowedExtensions = productAttributeManager.GetDefaultProperty("ValidationFileAllowedExtensions")?.StringValue;
|
|
productAttributeMapping.DefaultValue = productAttributeManager.GetDefaultProperty("DefaultValue")?.StringValue;
|
|
|
|
await _productAttributeService.UpdateProductAttributeMappingAsync(productAttributeMapping);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
pav = new ProductAttributeValue
|
|
{
|
|
ProductAttributeMappingId = productAttributeMapping.Id,
|
|
AttributeValueType = (AttributeValueType)attributeValueTypeId,
|
|
AssociatedProductId = associatedProductId,
|
|
Name = valueName,
|
|
PriceAdjustment = priceAdjustment,
|
|
PriceAdjustmentUsePercentage = priceAdjustmentUsePercentage,
|
|
WeightAdjustment = weightAdjustment,
|
|
Cost = cost,
|
|
IsPreSelected = isPreSelected,
|
|
DisplayOrder = displayOrder,
|
|
ColorSquaresRgb = colorSquaresRgb,
|
|
ImageSquaresPictureId = imageSquaresPictureId,
|
|
CustomerEntersQty = customerEntersQty,
|
|
Quantity = quantity
|
|
};
|
|
|
|
await _productAttributeService.InsertProductAttributeValueAsync(pav);
|
|
await saveAttributeValuePicturesAsync(lastLoadedProduct, pav);
|
|
}
|
|
else
|
|
{
|
|
pav.AttributeValueTypeId = attributeValueTypeId;
|
|
pav.AssociatedProductId = associatedProductId;
|
|
pav.Name = valueName;
|
|
pav.ColorSquaresRgb = colorSquaresRgb;
|
|
pav.ImageSquaresPictureId = imageSquaresPictureId;
|
|
pav.PriceAdjustment = priceAdjustment;
|
|
pav.PriceAdjustmentUsePercentage = priceAdjustmentUsePercentage;
|
|
pav.WeightAdjustment = weightAdjustment;
|
|
pav.Cost = cost;
|
|
pav.CustomerEntersQty = customerEntersQty;
|
|
pav.Quantity = quantity;
|
|
pav.IsPreSelected = isPreSelected;
|
|
pav.DisplayOrder = displayOrder;
|
|
|
|
await _productAttributeService.UpdateProductAttributeValueAsync(pav);
|
|
await saveAttributeValuePicturesAsync(lastLoadedProduct, pav);
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportSpecificationAttributeAsync(ImportProductMetadata metadata, Product lastLoadedProduct, IList<Language> languages, int iRow)
|
|
{
|
|
var specificationAttributeManager = metadata.SpecificationAttributeManager;
|
|
if (!_catalogSettings.ExportImportProductSpecificationAttributes || lastLoadedProduct == null || specificationAttributeManager.IsCaption)
|
|
return;
|
|
|
|
var attributeTypeId = specificationAttributeManager.GetDefaultProperty("AttributeType").IntValue;
|
|
var allowFiltering = specificationAttributeManager.GetDefaultProperty("AllowFiltering").BooleanValue;
|
|
var specificationAttributeOptionId = specificationAttributeManager.GetDefaultProperty("SpecificationAttributeOptionId").IntValue;
|
|
var productId = lastLoadedProduct.Id;
|
|
var customValue = specificationAttributeManager.GetDefaultProperty("CustomValue").StringValue;
|
|
var displayOrder = specificationAttributeManager.GetDefaultProperty("DisplayOrder").IntValue;
|
|
var showOnProductPage = specificationAttributeManager.GetDefaultProperty("ShowOnProductPage").BooleanValue;
|
|
|
|
//if specification attribute option isn't set, try to get first of possible specification attribute option for current specification attribute
|
|
if (specificationAttributeOptionId == 0)
|
|
{
|
|
var specificationAttribute = specificationAttributeManager.GetDefaultProperty("SpecificationAttribute").IntValue;
|
|
specificationAttributeOptionId =
|
|
(await _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttributeAsync(
|
|
specificationAttribute))
|
|
.FirstOrDefault()?.Id ?? specificationAttributeOptionId;
|
|
}
|
|
|
|
var productSpecificationAttribute = specificationAttributeOptionId == 0
|
|
? null
|
|
: (await _specificationAttributeService.GetProductSpecificationAttributesAsync(productId, specificationAttributeOptionId)).FirstOrDefault();
|
|
|
|
var isNew = productSpecificationAttribute == null;
|
|
|
|
if (isNew)
|
|
productSpecificationAttribute = new ProductSpecificationAttribute();
|
|
|
|
if (attributeTypeId != (int)SpecificationAttributeType.Option)
|
|
//we allow filtering only for "Option" attribute type
|
|
allowFiltering = false;
|
|
|
|
//we don't allow CustomValue for "Option" attribute type
|
|
if (attributeTypeId == (int)SpecificationAttributeType.Option)
|
|
customValue = null;
|
|
|
|
productSpecificationAttribute.AttributeTypeId = attributeTypeId;
|
|
productSpecificationAttribute.SpecificationAttributeOptionId = specificationAttributeOptionId;
|
|
productSpecificationAttribute.ProductId = productId;
|
|
productSpecificationAttribute.CustomValue = customValue;
|
|
productSpecificationAttribute.AllowFiltering = allowFiltering;
|
|
productSpecificationAttribute.ShowOnProductPage = showOnProductPage;
|
|
productSpecificationAttribute.DisplayOrder = displayOrder;
|
|
|
|
if (isNew)
|
|
await _specificationAttributeService.InsertProductSpecificationAttributeAsync(productSpecificationAttribute);
|
|
else
|
|
await _specificationAttributeService.UpdateProductSpecificationAttributeAsync(productSpecificationAttribute);
|
|
|
|
if (!metadata.LocalizedWorksheets.Any())
|
|
return;
|
|
|
|
foreach (var language in languages)
|
|
{
|
|
var lWorksheet = metadata.LocalizedWorksheets.FirstOrDefault(ws => ws.Name.Equals(language.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase));
|
|
if (lWorksheet == null)
|
|
continue;
|
|
|
|
specificationAttributeManager.CurrentLanguage = language;
|
|
specificationAttributeManager.ReadLocalizedFromXlsx(lWorksheet, iRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
customValue = specificationAttributeManager.GetLocalizedProperty("CustomValue").StringValue;
|
|
await _localizedEntityService.SaveLocalizedValueAsync(productSpecificationAttribute, p => p.CustomValue, customValue, language.Id);
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportTierPriceAsync(ImportProductMetadata metadata, Product lastLoadedProduct, IList<Language> languages, int iRow)
|
|
{
|
|
var tierPriceManager = metadata.TierPriceManager;
|
|
|
|
if (!_catalogSettings.ExportImportTierPrices || lastLoadedProduct == null || tierPriceManager.IsCaption)
|
|
return;
|
|
|
|
var id = tierPriceManager.GetDefaultProperty("TierPriceId").IntValue;
|
|
var storeId = tierPriceManager.GetDefaultProperty("Store").IntValue;
|
|
var customerRoleId = tierPriceManager.GetDefaultProperty("CustomerRole").IntValue;
|
|
var quantity = tierPriceManager.GetDefaultProperty("Quantity").IntValue;
|
|
var price = tierPriceManager.GetDefaultProperty("Price").DecimalValue;
|
|
var startDateTimeUtc = tierPriceManager.GetDefaultProperty("StartDateTimeUtc").DateTimeNullable;
|
|
var endDateTimeUtc = tierPriceManager.GetDefaultProperty("EndDateTimeUtc").DateTimeNullable;
|
|
|
|
if (id == 0)
|
|
{
|
|
var tierPrice = new TierPrice
|
|
{
|
|
CustomerRoleId = customerRoleId != 0 ? customerRoleId : null,
|
|
EndDateTimeUtc = endDateTimeUtc,
|
|
Price = price,
|
|
ProductId = lastLoadedProduct.Id,
|
|
Quantity = quantity,
|
|
StartDateTimeUtc = startDateTimeUtc,
|
|
StoreId = storeId
|
|
};
|
|
|
|
await _productService.InsertTierPriceAsync(tierPrice);
|
|
}
|
|
else
|
|
{
|
|
var tierPrice = await _productService.GetTierPriceByIdAsync(id);
|
|
|
|
tierPrice.CustomerRoleId = customerRoleId != 0 ? customerRoleId : null;
|
|
tierPrice.EndDateTimeUtc = endDateTimeUtc;
|
|
tierPrice.Price = price;
|
|
tierPrice.ProductId = lastLoadedProduct.Id;
|
|
tierPrice.Quantity = quantity;
|
|
tierPrice.StartDateTimeUtc = startDateTimeUtc;
|
|
tierPrice.StoreId = storeId;
|
|
|
|
await _productService.UpdateTierPriceAsync(tierPrice);
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task<string> DownloadFileAsync(string urlString, IList<string> downloadedFiles)
|
|
{
|
|
if (string.IsNullOrEmpty(urlString))
|
|
return string.Empty;
|
|
|
|
if (!Uri.IsWellFormedUriString(urlString, UriKind.Absolute))
|
|
return urlString;
|
|
|
|
if (!_catalogSettings.ExportImportAllowDownloadImages)
|
|
return string.Empty;
|
|
|
|
//ensure that temp directory is created
|
|
var tempDirectory = _fileProvider.MapPath(ExportImportDefaults.UploadsTempPath);
|
|
_fileProvider.CreateDirectory(tempDirectory);
|
|
|
|
var fileName = _fileProvider.GetFileName(urlString);
|
|
if (string.IsNullOrEmpty(fileName))
|
|
return string.Empty;
|
|
|
|
var filePath = _fileProvider.Combine(tempDirectory, fileName);
|
|
try
|
|
{
|
|
var client = _httpClientFactory.CreateClient(NopHttpDefaults.DefaultHttpClient);
|
|
var fileData = await client.GetByteArrayAsync(urlString);
|
|
await using (var fs = new FileStream(filePath, FileMode.OpenOrCreate))
|
|
fs.Write(fileData, 0, fileData.Length);
|
|
|
|
downloadedFiles?.Add(filePath);
|
|
return filePath;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _logger.ErrorAsync("Download image failed", ex);
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task<ImportProductMetadata> PrepareImportProductDataAsync(IXLWorkbook workbook, IList<Language> languages)
|
|
{
|
|
//the columns
|
|
var metadata = GetWorkbookMetadata<Product>(workbook, languages);
|
|
var defaultWorksheet = metadata.DefaultWorksheet;
|
|
var defaultProperties = metadata.DefaultProperties;
|
|
var localizedProperties = metadata.LocalizedProperties;
|
|
|
|
var manager = new PropertyManager<Product, Language>(defaultProperties, _catalogSettings, localizedProperties, languages);
|
|
|
|
var productAttributeProperties = new[]
|
|
{
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeId"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeName"),
|
|
new PropertyByName<ExportProductAttribute, Language>("DefaultValue"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ValidationMinLength"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ValidationMaxLength"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ValidationFileAllowedExtensions"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ValidationFileMaximumSize"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeTextPrompt"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeIsRequired"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeControlType"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeDisplayOrder"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ProductAttributeValueId"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ValueName"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeValueType"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AssociatedProductId"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ColorSquaresRgb"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ImageSquaresPictureId"),
|
|
new PropertyByName<ExportProductAttribute, Language>("PriceAdjustment"),
|
|
new PropertyByName<ExportProductAttribute, Language>("PriceAdjustmentUsePercentage"),
|
|
new PropertyByName<ExportProductAttribute, Language>("WeightAdjustment"),
|
|
new PropertyByName<ExportProductAttribute, Language>("Cost"),
|
|
new PropertyByName<ExportProductAttribute, Language>("CustomerEntersQty"),
|
|
new PropertyByName<ExportProductAttribute, Language>("Quantity"),
|
|
new PropertyByName<ExportProductAttribute, Language>("IsPreSelected"),
|
|
new PropertyByName<ExportProductAttribute, Language>("DisplayOrder"),
|
|
new PropertyByName<ExportProductAttribute, Language>("PictureIds")
|
|
};
|
|
|
|
var productAttributeLocalizedProperties = new[]
|
|
{
|
|
new PropertyByName<ExportProductAttribute, Language>("DefaultValue"),
|
|
new PropertyByName<ExportProductAttribute, Language>("AttributeTextPrompt"),
|
|
new PropertyByName<ExportProductAttribute, Language>("ValueName")
|
|
};
|
|
|
|
var productAttributeManager = new PropertyManager<ExportProductAttribute, Language>(productAttributeProperties, _catalogSettings, productAttributeLocalizedProperties, languages);
|
|
|
|
var specificationAttributeProperties = new[]
|
|
{
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("AttributeType", (p, l) => p.AttributeTypeId),
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("SpecificationAttribute", (p, l) => p.SpecificationAttributeId),
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("CustomValue", (p, l) => p.CustomValue),
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("SpecificationAttributeOptionId", (p, l) => p.SpecificationAttributeOptionId),
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("AllowFiltering", (p, l) => p.AllowFiltering),
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("ShowOnProductPage", (p, l) => p.ShowOnProductPage),
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("DisplayOrder", (p, l) => p.DisplayOrder)
|
|
};
|
|
|
|
var specificationAttributeLocalizedProperties = new[]
|
|
{
|
|
new PropertyByName<ExportSpecificationAttribute, Language>("CustomValue")
|
|
};
|
|
|
|
var specificationAttributeManager = new PropertyManager<ExportSpecificationAttribute, Language>(specificationAttributeProperties, _catalogSettings, specificationAttributeLocalizedProperties, languages);
|
|
|
|
var tierPriceProperties = new[]
|
|
{
|
|
new PropertyByName<ExportTierPrice, Language>("TierPriceId"),
|
|
new PropertyByName<ExportTierPrice, Language>("Store"),
|
|
new PropertyByName<ExportTierPrice, Language>("CustomerRole"),
|
|
new PropertyByName<ExportTierPrice, Language>("Quantity"),
|
|
new PropertyByName<ExportTierPrice, Language>("Price"),
|
|
new PropertyByName<ExportTierPrice, Language>("StartDateTimeUtc"),
|
|
new PropertyByName<ExportTierPrice, Language>("EndDateTimeUtc")
|
|
};
|
|
|
|
var tierPriceManager = new PropertyManager<ExportTierPrice, Language>(tierPriceProperties, _catalogSettings, languages: languages);
|
|
|
|
var endRow = 2;
|
|
var allCategories = new List<string>();
|
|
var allSku = new List<string>();
|
|
|
|
var tempProperty = manager.GetDefaultProperty("Categories");
|
|
var categoryCellNum = tempProperty?.PropertyOrderPosition ?? -1;
|
|
|
|
tempProperty = manager.GetDefaultProperty("SKU");
|
|
var skuCellNum = tempProperty?.PropertyOrderPosition ?? -1;
|
|
|
|
var allManufacturers = new List<string>();
|
|
tempProperty = manager.GetDefaultProperty("Manufacturers");
|
|
var manufacturerCellNum = tempProperty?.PropertyOrderPosition ?? -1;
|
|
|
|
var allStores = new List<string>();
|
|
tempProperty = manager.GetDefaultProperty("LimitedToStores");
|
|
var limitedToStoresCellNum = tempProperty?.PropertyOrderPosition ?? -1;
|
|
|
|
if (_catalogSettings.ExportImportUseDropdownlistsForAssociatedEntities)
|
|
{
|
|
tierPriceManager.SetSelectList("Store", (await _storeService.GetAllStoresAsync()).ToSelectList(p => (p as Store)?.Name ?? string.Empty));
|
|
tierPriceManager.SetSelectList("CustomerRole", (await _customerService.GetAllCustomerRolesAsync()).ToSelectList(p => (p as CustomerRole)?.Name ?? string.Empty));
|
|
|
|
productAttributeManager.SetSelectList("AttributeControlType", await AttributeControlType.TextBox.ToSelectListAsync(useLocalization: false));
|
|
productAttributeManager.SetSelectList("AttributeValueType", await AttributeValueType.Simple.ToSelectListAsync(useLocalization: false));
|
|
|
|
specificationAttributeManager.SetSelectList("AttributeType", await SpecificationAttributeType.Option.ToSelectListAsync(useLocalization: false));
|
|
specificationAttributeManager.SetSelectList("SpecificationAttribute", (await _specificationAttributeService
|
|
.GetSpecificationAttributesAsync())
|
|
.Select(sa => sa as BaseEntity)
|
|
.ToSelectList(p => (p as SpecificationAttribute)?.Name ?? string.Empty));
|
|
|
|
manager.SetSelectList("ProductType", await ProductType.SimpleProduct.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("GiftCardType", await GiftCardType.Virtual.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("DownloadActivationType",
|
|
await DownloadActivationType.Manually.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("ManageInventoryMethod",
|
|
await ManageInventoryMethod.DontManageStock.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("LowStockActivity",
|
|
await LowStockActivity.Nothing.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("BackorderMode", await BackorderMode.NoBackorders.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("RecurringCyclePeriod",
|
|
await RecurringProductCyclePeriod.Days.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("RentalPricePeriod", await RentalPricePeriod.Days.ToSelectListAsync(useLocalization: false));
|
|
|
|
manager.SetSelectList("Vendor",
|
|
(await _vendorService.GetAllVendorsAsync(showHidden: true)).Select(v => v as BaseEntity)
|
|
.ToSelectList(p => (p as Vendor)?.Name ?? string.Empty));
|
|
manager.SetSelectList("ProductTemplate",
|
|
(await _productTemplateService.GetAllProductTemplatesAsync()).Select(pt => pt as BaseEntity)
|
|
.ToSelectList(p => (p as ProductTemplate)?.Name ?? string.Empty));
|
|
manager.SetSelectList("DeliveryDate",
|
|
(await _dateRangeService.GetAllDeliveryDatesAsync()).Select(dd => dd as BaseEntity)
|
|
.ToSelectList(p => (p as DeliveryDate)?.Name ?? string.Empty));
|
|
manager.SetSelectList("ProductAvailabilityRange",
|
|
(await _dateRangeService.GetAllProductAvailabilityRangesAsync()).Select(range => range as BaseEntity)
|
|
.ToSelectList(p => (p as ProductAvailabilityRange)?.Name ?? string.Empty));
|
|
manager.SetSelectList("TaxCategory",
|
|
(await _taxCategoryService.GetAllTaxCategoriesAsync()).Select(tc => tc as BaseEntity)
|
|
.ToSelectList(p => (p as TaxCategory)?.Name ?? string.Empty));
|
|
manager.SetSelectList("BasepriceUnit",
|
|
(await _measureService.GetAllMeasureWeightsAsync()).Select(mw => mw as BaseEntity)
|
|
.ToSelectList(p => (p as MeasureWeight)?.Name ?? string.Empty));
|
|
manager.SetSelectList("BasepriceBaseUnit",
|
|
(await _measureService.GetAllMeasureWeightsAsync()).Select(mw => mw as BaseEntity)
|
|
.ToSelectList(p => (p as MeasureWeight)?.Name ?? string.Empty));
|
|
}
|
|
|
|
var allAttributeIds = new List<int>();
|
|
var allSpecificationAttributeOptionIds = new List<int>();
|
|
|
|
var attributeIdCellNum = 1 + ExportImportDefaults.ProductAdditionalInfoCellOffset;
|
|
var specificationAttributeOptionIdCellNum =
|
|
specificationAttributeManager.GetIndex("SpecificationAttributeOptionId") +
|
|
ExportImportDefaults.ProductAdditionalInfoCellOffset;
|
|
|
|
var productsInFile = new List<int>();
|
|
|
|
//find end of data
|
|
var typeOfExportedAttribute = ExportedAdditionalProductInfoType.NotSpecified;
|
|
while (true)
|
|
{
|
|
var allColumnsAreEmpty = manager.GetDefaultProperties
|
|
.Select(property => defaultWorksheet.Row(endRow).Cell(property.PropertyOrderPosition))
|
|
.All(cell => string.IsNullOrEmpty(cell?.Value.ToString()));
|
|
|
|
if (allColumnsAreEmpty)
|
|
break;
|
|
|
|
if (new[] { 1, 2 }.Select(cellNum => defaultWorksheet.Row(endRow).Cell(cellNum))
|
|
.All(cell => string.IsNullOrEmpty(cell?.Value.ToString())) &&
|
|
defaultWorksheet.Row(endRow).OutlineLevel == 0)
|
|
{
|
|
var cellValue = defaultWorksheet.Row(endRow).Cell(attributeIdCellNum).Value;
|
|
await SetOutLineForProductAttributeRowAsync(cellValue, defaultWorksheet, endRow);
|
|
await SetOutLineForSpecificationAttributeRowAsync(cellValue, defaultWorksheet, endRow);
|
|
}
|
|
|
|
if (defaultWorksheet.Row(endRow).OutlineLevel != 0)
|
|
{
|
|
var newTypeOfExportedAttribute = GetTypeOfExportedAdditionalProductInfo(defaultWorksheet, metadata.LocalizedWorksheets, productAttributeManager, specificationAttributeManager, tierPriceManager, endRow);
|
|
|
|
//skip caption row
|
|
if (newTypeOfExportedAttribute != ExportedAdditionalProductInfoType.NotSpecified && newTypeOfExportedAttribute != typeOfExportedAttribute)
|
|
{
|
|
typeOfExportedAttribute = newTypeOfExportedAttribute;
|
|
endRow++;
|
|
continue;
|
|
}
|
|
|
|
switch (typeOfExportedAttribute)
|
|
{
|
|
case ExportedAdditionalProductInfoType.ProductAttribute:
|
|
productAttributeManager.ReadDefaultFromXlsx(defaultWorksheet, endRow,
|
|
ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
if (int.TryParse(defaultWorksheet.Row(endRow).Cell(attributeIdCellNum).Value.ToString(), out var aid))
|
|
allAttributeIds.Add(aid);
|
|
|
|
break;
|
|
case ExportedAdditionalProductInfoType.SpecificationAttribute:
|
|
specificationAttributeManager.ReadDefaultFromXlsx(defaultWorksheet, endRow, ExportImportDefaults.ProductAdditionalInfoCellOffset);
|
|
|
|
if (int.TryParse(defaultWorksheet.Row(endRow).Cell(specificationAttributeOptionIdCellNum).Value.ToString(), out var saoid))
|
|
allSpecificationAttributeOptionIds.Add(saoid);
|
|
|
|
break;
|
|
}
|
|
|
|
endRow++;
|
|
continue;
|
|
}
|
|
|
|
if (categoryCellNum > 0)
|
|
{
|
|
var categoryIds = defaultWorksheet.Row(endRow).Cell(categoryCellNum).Value.ToString() ?? string.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(categoryIds))
|
|
allCategories.AddRange(categoryIds
|
|
.Split(new[] { ";", ">>" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())
|
|
.Distinct());
|
|
}
|
|
|
|
if (skuCellNum > 0)
|
|
{
|
|
var sku = defaultWorksheet.Row(endRow).Cell(skuCellNum).Value.ToString() ?? string.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(sku))
|
|
allSku.Add(sku);
|
|
}
|
|
|
|
if (manufacturerCellNum > 0)
|
|
{
|
|
var manufacturerIds = defaultWorksheet.Row(endRow).Cell(manufacturerCellNum).Value.ToString() ??
|
|
string.Empty;
|
|
if (!string.IsNullOrEmpty(manufacturerIds))
|
|
allManufacturers.AddRange(manufacturerIds
|
|
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()));
|
|
}
|
|
|
|
if (limitedToStoresCellNum > 0)
|
|
{
|
|
var storeIds = defaultWorksheet.Row(endRow).Cell(limitedToStoresCellNum).Value.ToString() ??
|
|
string.Empty;
|
|
if (!string.IsNullOrEmpty(storeIds))
|
|
allStores.AddRange(storeIds
|
|
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()));
|
|
}
|
|
|
|
//counting the number of products
|
|
productsInFile.Add(endRow);
|
|
|
|
endRow++;
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the categories in one SQL request
|
|
var notExistingCategories = await _categoryService.GetNotExistingCategoriesAsync(allCategories.ToArray());
|
|
if (notExistingCategories.Any())
|
|
{
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.CategoriesDontExist"), string.Join(", ", notExistingCategories)));
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the manufacturers in one SQL request
|
|
var notExistingManufacturers = await _manufacturerService.GetNotExistingManufacturersAsync(allManufacturers.ToArray());
|
|
if (notExistingManufacturers.Any())
|
|
{
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.ManufacturersDontExist"), string.Join(", ", notExistingManufacturers)));
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the product attributes in one SQL request
|
|
var notExistingProductAttributes = await _productAttributeService.GetNotExistingAttributesAsync(allAttributeIds.ToArray());
|
|
if (notExistingProductAttributes.Any())
|
|
{
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.ProductAttributesDontExist"), string.Join(", ", notExistingProductAttributes)));
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the specification attribute options in one SQL request
|
|
var notExistingSpecificationAttributeOptions = await _specificationAttributeService.GetNotExistingSpecificationAttributeOptionsAsync(allSpecificationAttributeOptionIds.Where(saoId => saoId != 0).ToArray());
|
|
if (notExistingSpecificationAttributeOptions.Any())
|
|
{
|
|
throw new ArgumentException($"The following specification attribute option ID(s) don't exist - {string.Join(", ", notExistingSpecificationAttributeOptions)}");
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the stores in one SQL request
|
|
var notExistingStores = await _storeService.GetNotExistingStoresAsync(allStores.ToArray());
|
|
if (notExistingStores.Any())
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.StoresDontExist"), string.Join(", ", notExistingStores)));
|
|
|
|
return new ImportProductMetadata
|
|
{
|
|
EndRow = endRow,
|
|
Manager = manager,
|
|
Properties = defaultProperties,
|
|
ProductsInFile = productsInFile,
|
|
ProductAttributeManager = productAttributeManager,
|
|
DefaultWorksheet = defaultWorksheet,
|
|
LocalizedWorksheets = metadata.LocalizedWorksheets,
|
|
SpecificationAttributeManager = specificationAttributeManager,
|
|
TierPriceManager = tierPriceManager,
|
|
SkuCellNum = skuCellNum,
|
|
AllSku = allSku
|
|
};
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportProductsFromSplitedXlsxAsync(IXLWorksheet worksheet, ImportProductMetadata metadata)
|
|
{
|
|
foreach (var path in SplitProductFile(worksheet, metadata))
|
|
{
|
|
using var scope = _serviceScopeFactory.CreateScope();
|
|
// Resolve
|
|
var importManager = EngineContext.Current.Resolve<IImportManager>(scope);
|
|
|
|
using var sr = new StreamReader(path);
|
|
await importManager.ImportProductsFromXlsxAsync(sr.BaseStream);
|
|
|
|
try
|
|
{
|
|
_fileProvider.DeleteFile(path);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual IList<string> SplitProductFile(IXLWorksheet worksheet, ImportProductMetadata metadata)
|
|
{
|
|
var fileIndex = 1;
|
|
var fileName = Guid.NewGuid().ToString();
|
|
var endCell = metadata.Properties.Max(p => p.PropertyOrderPosition);
|
|
|
|
var filePaths = new List<string>();
|
|
|
|
while (true)
|
|
{
|
|
var curIndex = fileIndex * _catalogSettings.ExportImportProductsCountInOneFile;
|
|
|
|
var startRow = metadata.ProductsInFile[(fileIndex - 1) * _catalogSettings.ExportImportProductsCountInOneFile];
|
|
|
|
var endRow = metadata.CountProductsInFile > curIndex
|
|
? metadata.ProductsInFile[curIndex - 1]
|
|
: metadata.EndRow;
|
|
|
|
var filePath = $"{_fileProvider.MapPath(ExportImportDefaults.UploadsTempPath)}/{fileName}_part_{fileIndex}.xlsx";
|
|
|
|
CopyDataToNewFile(metadata, worksheet, filePath, startRow, endRow, endCell);
|
|
|
|
filePaths.Add(filePath);
|
|
fileIndex += 1;
|
|
|
|
if (endRow == metadata.EndRow)
|
|
break;
|
|
}
|
|
|
|
return filePaths;
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task<(ImportOrderMetadata, IXLWorksheet)> PrepareImportOrderDataAsync(IXLWorkbook workbook)
|
|
{
|
|
var languages = await _languageService.GetAllLanguagesAsync(showHidden: true);
|
|
|
|
//the columns
|
|
var metadata = GetWorkbookMetadata<Order>(workbook, languages);
|
|
var worksheet = metadata.DefaultWorksheet;
|
|
var defaultProperties = metadata.DefaultProperties;
|
|
|
|
var manager = new PropertyManager<Order, Language>(defaultProperties, _catalogSettings);
|
|
|
|
var orderItemProperties = new[]
|
|
{
|
|
new PropertyByName<OrderItem, Language>("OrderItemGuid"),
|
|
new PropertyByName<OrderItem, Language>("Name"),
|
|
new PropertyByName<OrderItem, Language>("Sku"),
|
|
new PropertyByName<OrderItem, Language>("PriceExclTax"),
|
|
new PropertyByName<OrderItem, Language>("PriceInclTax"),
|
|
new PropertyByName<OrderItem, Language>("Quantity"),
|
|
new PropertyByName<OrderItem, Language>("DiscountExclTax"),
|
|
new PropertyByName<OrderItem, Language>("DiscountInclTax"),
|
|
new PropertyByName<OrderItem, Language>("TotalExclTax"),
|
|
new PropertyByName<OrderItem, Language>("TotalInclTax")
|
|
};
|
|
|
|
var orderItemManager = new PropertyManager<OrderItem, Language>(orderItemProperties, _catalogSettings);
|
|
|
|
var endRow = 2;
|
|
var allOrderGuids = new List<Guid>();
|
|
|
|
var tempProperty = manager.GetDefaultProperty("OrderGuid");
|
|
var orderGuidCellNum = tempProperty?.PropertyOrderPosition ?? -1;
|
|
|
|
tempProperty = manager.GetDefaultProperty("CustomerGuid");
|
|
var customerGuidCellNum = tempProperty?.PropertyOrderPosition ?? -1;
|
|
|
|
manager.SetSelectList("OrderStatus", await OrderStatus.Cancelled.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("ShippingStatus", await ShippingStatus.Delivered.ToSelectListAsync(useLocalization: false));
|
|
manager.SetSelectList("PaymentStatus", await PaymentStatus.Authorized.ToSelectListAsync(useLocalization: false));
|
|
|
|
var allCustomerGuids = new List<Guid>();
|
|
|
|
var allOrderItemSkus = new List<string>();
|
|
|
|
var countOrdersInFile = 0;
|
|
|
|
//find end of data
|
|
while (true)
|
|
{
|
|
var allColumnsAreEmpty = manager.GetDefaultProperties
|
|
.Select(property => worksheet.Row(endRow).Cell(property.PropertyOrderPosition))
|
|
.All(cell => string.IsNullOrEmpty(cell?.Value.ToString()));
|
|
|
|
if (allColumnsAreEmpty)
|
|
break;
|
|
|
|
if (worksheet.Row(endRow).OutlineLevel != 0)
|
|
{
|
|
orderItemManager.ReadDefaultFromXlsx(worksheet, endRow, 2);
|
|
|
|
//skip caption row
|
|
if (!orderItemManager.IsCaption)
|
|
{
|
|
allOrderItemSkus.Add(orderItemManager.GetDefaultProperty("Sku").StringValue);
|
|
}
|
|
|
|
endRow++;
|
|
continue;
|
|
}
|
|
|
|
if (orderGuidCellNum > 0)
|
|
{
|
|
var orderGuidString = worksheet.Row(endRow).Cell(orderGuidCellNum).Value.ToString() ?? string.Empty;
|
|
if (!string.IsNullOrEmpty(orderGuidString) && Guid.TryParse(orderGuidString, out var orderGuid))
|
|
allOrderGuids.Add(orderGuid);
|
|
}
|
|
|
|
if (customerGuidCellNum > 0)
|
|
{
|
|
var customerGuidString = worksheet.Row(endRow).Cell(customerGuidCellNum).Value.ToString() ?? string.Empty;
|
|
if (!string.IsNullOrEmpty(customerGuidString) && Guid.TryParse(customerGuidString, out var customerGuid))
|
|
allCustomerGuids.Add(customerGuid);
|
|
}
|
|
|
|
//counting the number of orders
|
|
countOrdersInFile++;
|
|
|
|
endRow++;
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the customers in one SQL request
|
|
var notExistingCustomerGuids = await _customerService.GetNotExistingCustomersAsync(allCustomerGuids.ToArray());
|
|
if (notExistingCustomerGuids.Any())
|
|
{
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Orders.Import.CustomersDontExist"), string.Join(", ", notExistingCustomerGuids)));
|
|
}
|
|
|
|
//performance optimization, the check for the existence of the order items in one SQL request
|
|
var notExistingProductSkus = await _productService.GetNotExistingProductsAsync(allOrderItemSkus.ToArray());
|
|
if (notExistingProductSkus.Any())
|
|
{
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Orders.Import.ProductsDontExist"), string.Join(", ", notExistingProductSkus)));
|
|
}
|
|
|
|
return (new ImportOrderMetadata
|
|
{
|
|
EndRow = endRow,
|
|
Manager = manager,
|
|
Properties = defaultProperties,
|
|
CountOrdersInFile = countOrdersInFile,
|
|
OrderItemManager = orderItemManager,
|
|
AllOrderGuids = allOrderGuids,
|
|
AllCustomerGuids = allCustomerGuids
|
|
}, worksheet);
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportOrderItemAsync(PropertyManager<OrderItem, Language> orderItemManager, Order lastLoadedOrder)
|
|
{
|
|
if (lastLoadedOrder == null || orderItemManager.IsCaption)
|
|
return;
|
|
|
|
var orderItemGuid = Guid.TryParse(orderItemManager.GetDefaultProperty("OrderItemGuid").StringValue, out var guidValue) ? guidValue : Guid.NewGuid();
|
|
var sku = orderItemManager.GetDefaultProperty("Sku").StringValue;
|
|
var priceExclTax = orderItemManager.GetDefaultProperty("PriceExclTax").DecimalValue;
|
|
var priceInclTax = orderItemManager.GetDefaultProperty("PriceInclTax").DecimalValue;
|
|
var quantity = orderItemManager.GetDefaultProperty("Quantity").IntValue;
|
|
var discountExclTax = orderItemManager.GetDefaultProperty("DiscountExclTax").DecimalValue;
|
|
var discountInclTax = orderItemManager.GetDefaultProperty("DiscountInclTax").DecimalValue;
|
|
var totalExclTax = orderItemManager.GetDefaultProperty("TotalExclTax").DecimalValue;
|
|
var totalInclTax = orderItemManager.GetDefaultProperty("TotalInclTax").DecimalValue;
|
|
|
|
var orderItemProduct = await _productService.GetProductBySkuAsync(sku);
|
|
var orderItem = (await _orderService.GetOrderItemsAsync(lastLoadedOrder.Id)).FirstOrDefault(f => f.OrderItemGuid == orderItemGuid);
|
|
|
|
if (orderItem == null)
|
|
{
|
|
//insert order item
|
|
orderItem = new OrderItem
|
|
{
|
|
DiscountAmountExclTax = discountExclTax,
|
|
DiscountAmountInclTax = discountInclTax,
|
|
OrderId = lastLoadedOrder.Id,
|
|
OrderItemGuid = orderItemGuid,
|
|
PriceExclTax = totalExclTax,
|
|
PriceInclTax = totalInclTax,
|
|
ProductId = orderItemProduct.Id,
|
|
Quantity = quantity,
|
|
OriginalProductCost = orderItemProduct.ProductCost,
|
|
UnitPriceExclTax = priceExclTax,
|
|
UnitPriceInclTax = priceInclTax
|
|
};
|
|
await _orderService.InsertOrderItemAsync(orderItem);
|
|
}
|
|
else
|
|
{
|
|
//update order item
|
|
orderItem.DiscountAmountExclTax = discountExclTax;
|
|
orderItem.DiscountAmountInclTax = discountInclTax;
|
|
orderItem.OrderId = lastLoadedOrder.Id;
|
|
orderItem.PriceExclTax = totalExclTax;
|
|
orderItem.PriceInclTax = totalInclTax;
|
|
orderItem.Quantity = quantity;
|
|
orderItem.UnitPriceExclTax = priceExclTax;
|
|
orderItem.UnitPriceInclTax = priceInclTax;
|
|
await _orderService.UpdateOrderItemAsync(orderItem);
|
|
}
|
|
}
|
|
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
protected virtual async Task ImportProductLocalizedAsync(Product product, ImportProductMetadata metadata, int iRow, IList<Language> languages)
|
|
{
|
|
if (metadata.LocalizedWorksheets.Any())
|
|
{
|
|
var manager = metadata.Manager;
|
|
foreach (var language in languages)
|
|
{
|
|
var lWorksheet = metadata.LocalizedWorksheets.FirstOrDefault(ws => ws.Name.Equals(language.UniqueSeoCode, StringComparison.InvariantCultureIgnoreCase));
|
|
if (lWorksheet == null)
|
|
continue;
|
|
|
|
manager.CurrentLanguage = language;
|
|
manager.ReadLocalizedFromXlsx(lWorksheet, iRow);
|
|
|
|
foreach (var property in manager.GetLocalizedProperties)
|
|
{
|
|
string localizedName = null;
|
|
|
|
switch (property.PropertyName)
|
|
{
|
|
case "Name":
|
|
localizedName = property.StringValue;
|
|
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.Name, localizedName, language.Id);
|
|
break;
|
|
case "ShortDescription":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.ShortDescription, property.StringValue, language.Id);
|
|
break;
|
|
case "FullDescription":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.FullDescription, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaKeywords":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.MetaKeywords, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaDescription":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.MetaDescription, property.StringValue, language.Id);
|
|
break;
|
|
case "MetaTitle":
|
|
await _localizedEntityService.SaveLocalizedValueAsync(product, p => p.MetaTitle, property.StringValue, language.Id);
|
|
break;
|
|
case "SeName":
|
|
//search engine name
|
|
var localizedSeName = await _urlRecordService.ValidateSeNameAsync(product, property.StringValue, localizedName, false);
|
|
await _urlRecordService.SaveSlugAsync(product, localizedSeName, language.Id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Get excel workbook metadata
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of object</typeparam>
|
|
/// <param name="workbook">Excel workbook</param>
|
|
/// <param name="languages">Languages</param>
|
|
/// <returns>Workbook metadata</returns>
|
|
public static WorkbookMetadata<T> GetWorkbookMetadata<T>(IXLWorkbook workbook, IList<Language> languages)
|
|
{
|
|
// get the first worksheet in the workbook
|
|
var worksheet = workbook.Worksheets.FirstOrDefault()
|
|
?? throw new NopException("No worksheet found");
|
|
|
|
var properties = new List<PropertyByName<T, Language>>();
|
|
var localizedProperties = new List<PropertyByName<T, Language>>();
|
|
var localizedWorksheets = new List<IXLWorksheet>();
|
|
|
|
var poz = 1;
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
var cell = worksheet.Row(1).Cell(poz);
|
|
|
|
if (string.IsNullOrEmpty(cell?.Value.ToString()))
|
|
break;
|
|
|
|
poz += 1;
|
|
properties.Add(new PropertyByName<T, Language>(cell.Value.ToString()));
|
|
}
|
|
catch
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (var ws in workbook.Worksheets.Skip(1))
|
|
if (languages.Any(l => l.UniqueSeoCode.Equals(ws.Name, StringComparison.InvariantCultureIgnoreCase)))
|
|
localizedWorksheets.Add(ws);
|
|
|
|
if (localizedWorksheets.Any())
|
|
{
|
|
// get the first worksheet in the workbook
|
|
var localizedWorksheet = localizedWorksheets.First();
|
|
|
|
poz = 1;
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
var cell = localizedWorksheet.Row(1).Cell(poz);
|
|
|
|
if (string.IsNullOrEmpty(cell?.Value.ToString()))
|
|
break;
|
|
|
|
poz += 1;
|
|
localizedProperties.Add(new PropertyByName<T, Language>(cell.Value.ToString()));
|
|
}
|
|
catch
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return new WorkbookMetadata<T>
|
|
{
|
|
DefaultProperties = properties,
|
|
LocalizedProperties = localizedProperties,
|
|
DefaultWorksheet = worksheet,
|
|
LocalizedWorksheets = localizedWorksheets
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import customers from XLSX file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task ImportCustomersFromXlsxAsync(Stream stream)
|
|
{
|
|
using var workbook = new XLWorkbook(stream);
|
|
|
|
var languages = await _languageService.GetAllLanguagesAsync(showHidden: true);
|
|
|
|
//the columns
|
|
var metadata = GetWorkbookMetadata<Customer>(workbook, languages);
|
|
var defaultWorksheet = metadata.DefaultWorksheet;
|
|
var defaultProperties = metadata.DefaultProperties;
|
|
|
|
var manager = new PropertyManager<Customer, Language>(defaultProperties, _catalogSettings);
|
|
|
|
if (_catalogSettings.ExportImportUseDropdownlistsForAssociatedEntities)
|
|
{
|
|
manager.SetSelectList("VatNumberStatus",
|
|
await VatNumberStatus.Unknown.ToSelectListAsync(useLocalization: false));
|
|
}
|
|
|
|
var iRow = 2;
|
|
var allRoles = await _customerService.GetAllCustomerRolesAsync();
|
|
var countries = await _countryService.GetAllCountriesAsync();
|
|
var states = await _stateProvinceService.GetStateProvincesAsync();
|
|
|
|
while (true)
|
|
{
|
|
var allColumnsAreEmpty = manager.GetDefaultProperties
|
|
.Select(property => defaultWorksheet.Row(iRow).Cell(property.PropertyOrderPosition))
|
|
.All(cell => cell?.Value == null || string.IsNullOrEmpty(cell.Value.ToString()));
|
|
|
|
if (allColumnsAreEmpty)
|
|
break;
|
|
|
|
manager.ReadDefaultFromXlsx(defaultWorksheet, iRow);
|
|
|
|
var customerGuid = manager.GetDefaultProperty("CustomerGuid").GuidValue;
|
|
var customer = await _customerService.GetCustomerByGuidAsync(customerGuid) ??
|
|
await _customerService.GetCustomerByEmailAsync(manager.GetDefaultProperty("Email").StringValue);
|
|
|
|
int? avatarPictureId = null;
|
|
string signature = null;
|
|
string password = null;
|
|
string passwordSalt = null;
|
|
|
|
var isNew = customer == null;
|
|
|
|
if (isNew)
|
|
customer = new Customer
|
|
{
|
|
CustomerGuid = Guid.Empty.Equals(customerGuid) ? Guid.NewGuid() : customerGuid,
|
|
CreatedOnUtc = DateTime.UtcNow
|
|
};
|
|
|
|
var rolesToSave = new List<int>();
|
|
|
|
foreach (var property in manager.GetDefaultProperties)
|
|
{
|
|
switch (property.PropertyName)
|
|
{
|
|
case "Email":
|
|
customer.Email = property.StringValue;
|
|
break;
|
|
case "Username":
|
|
customer.Username = property.StringValue;
|
|
break;
|
|
case "IsTaxExempt":
|
|
customer.IsTaxExempt = property.BooleanValue;
|
|
break;
|
|
case "AffiliateId":
|
|
customer.AffiliateId = property.IntValue;
|
|
break;
|
|
case "Vendor":
|
|
if (!string.IsNullOrEmpty(property.StringValue))
|
|
if (int.TryParse(property.StringValue, out var vendorId))
|
|
customer.VendorId = vendorId;
|
|
else
|
|
{
|
|
var vendors = await _vendorService.GetAllVendorsAsync(property.StringValue);
|
|
customer.VendorId = vendors.FirstOrDefault()?.Id ?? 0;
|
|
}
|
|
else
|
|
customer.VendorId = 0;
|
|
break;
|
|
case "Active":
|
|
customer.Active = property.BooleanValue;
|
|
break;
|
|
case "CustomerRoles":
|
|
var roles = property.StringValue.Split(", ");
|
|
|
|
foreach (var role in roles)
|
|
if (int.TryParse(role, out var roleId))
|
|
rolesToSave.Add(roleId);
|
|
else
|
|
{
|
|
var currentRole = allRoles.FirstOrDefault(r =>
|
|
r.Name.Equals(role, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
if (currentRole != null)
|
|
rolesToSave.Add(currentRole.Id);
|
|
}
|
|
break;
|
|
case "CreatedOnUtc":
|
|
if (DateTime.TryParse(property.StringValue, out var date))
|
|
customer.CreatedOnUtc = date;
|
|
break;
|
|
case "FirstName":
|
|
customer.FirstName = property.StringValue;
|
|
break;
|
|
case "LastName":
|
|
customer.LastName = property.StringValue;
|
|
break;
|
|
case "Gender":
|
|
customer.Gender = property.StringValue;
|
|
break;
|
|
case "Company":
|
|
customer.Company = property.StringValue;
|
|
break;
|
|
case "StreetAddress":
|
|
customer.StreetAddress = property.StringValue;
|
|
break;
|
|
case "StreetAddress2":
|
|
customer.StreetAddress2 = property.StringValue;
|
|
break;
|
|
case "ZipPostalCode":
|
|
customer.ZipPostalCode = property.StringValue;
|
|
break;
|
|
case "City":
|
|
customer.City = property.StringValue;
|
|
break;
|
|
case "County":
|
|
customer.County = property.StringValue;
|
|
break;
|
|
case "Country":
|
|
if (int.TryParse(property.StringValue, out var countryId))
|
|
customer.CountryId = countryId;
|
|
else
|
|
{
|
|
var country = countries.FirstOrDefault(c =>
|
|
c.Name.Equals(property.StringValue, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
if (country != null)
|
|
customer.CountryId = country.Id;
|
|
}
|
|
break;
|
|
case "StateProvince":
|
|
if (int.TryParse(property.StringValue, out var stateId))
|
|
customer.StateProvinceId = stateId;
|
|
else
|
|
{
|
|
var state = states.FirstOrDefault(s =>
|
|
s.Name.Equals(property.StringValue, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
if (state != null)
|
|
customer.StateProvinceId = state.Id;
|
|
}
|
|
break;
|
|
case "Phone":
|
|
customer.Phone = property.StringValue;
|
|
break;
|
|
case "Fax":
|
|
customer.Fax = property.StringValue;
|
|
break;
|
|
case "VatNumber":
|
|
customer.VatNumber = property.StringValue;
|
|
break;
|
|
case "VatNumberStatus":
|
|
customer.VatNumberStatusId = property.IntValue;
|
|
break;
|
|
case "TimeZone":
|
|
customer.TimeZoneId = property.StringValue;
|
|
break;
|
|
case "AvatarPictureId":
|
|
avatarPictureId = property.IntValueNullable;
|
|
break;
|
|
case "Signature":
|
|
signature = property.StringValue;
|
|
break;
|
|
case "CustomCustomerAttributesXML":
|
|
customer.CustomCustomerAttributesXML = property.StringValue;
|
|
break;
|
|
case "Password":
|
|
password = property.StringValue;
|
|
break;
|
|
case "PasswordSalt":
|
|
passwordSalt = property.StringValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isNew)
|
|
await _customerService.InsertCustomerAsync(customer);
|
|
else
|
|
await _customerService.UpdateCustomerAsync(customer);
|
|
|
|
var customerRoles = await _customerService.GetCustomerRolesAsync(customer);
|
|
|
|
foreach (var roleId in rolesToSave)
|
|
{
|
|
var role = allRoles.FirstOrDefault(r => r.Id == roleId);
|
|
|
|
if (role == null || customerRoles.Any(cr => cr.Id == roleId))
|
|
continue;
|
|
|
|
await _customerService.AddCustomerRoleMappingAsync(
|
|
new CustomerCustomerRoleMapping { CustomerId = customer.Id, CustomerRoleId = roleId });
|
|
}
|
|
|
|
if (!isNew && rolesToSave.Any())
|
|
foreach (var customerRole in customerRoles.Where(cr=>!rolesToSave.Contains(cr.Id)).ToList())
|
|
await _customerService.RemoveCustomerRoleMappingAsync(customer, customerRole);
|
|
|
|
if (avatarPictureId.HasValue)
|
|
await _genericAttributeService.SaveAttributeAsync(customer,
|
|
NopCustomerDefaults.AvatarPictureIdAttribute, avatarPictureId.Value);
|
|
|
|
if (!string.IsNullOrEmpty(signature))
|
|
await _genericAttributeService.SaveAttributeAsync(customer, NopCustomerDefaults.SignatureAttribute,
|
|
signature);
|
|
|
|
if (_securitySettings.AllowStoreOwnerExportImportCustomersWithHashedPassword &&
|
|
!string.IsNullOrEmpty(password) && !string.IsNullOrEmpty(passwordSalt))
|
|
{
|
|
var lastPassword = isNew ? null : await _customerService.GetCurrentPasswordAsync(customer.Id);
|
|
|
|
if (lastPassword == null || !(lastPassword.Password.Equals(password) && lastPassword.PasswordSalt.Equals(passwordSalt)))
|
|
await _customerService.InsertCustomerPasswordAsync(new CustomerPassword
|
|
{
|
|
CustomerId = customer.Id,
|
|
Password = password,
|
|
PasswordSalt = passwordSalt,
|
|
PasswordFormat = PasswordFormat.Hashed,
|
|
CreatedOnUtc = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
iRow++;
|
|
}
|
|
|
|
//activity log
|
|
await _customerActivityService.InsertActivityAsync("ImportCustomers",
|
|
string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportCustomers"), iRow - 2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import products from XLSX file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task ImportProductsFromXlsxAsync(Stream stream)
|
|
{
|
|
var languages = await _languageService.GetAllLanguagesAsync(showHidden: true);
|
|
|
|
using var workbook = new XLWorkbook(stream);
|
|
var downloadedFiles = new List<string>();
|
|
|
|
var metadata = await PrepareImportProductDataAsync(workbook, languages);
|
|
var defaultWorksheet = metadata.DefaultWorksheet;
|
|
|
|
if (_catalogSettings.ExportImportSplitProductsFile && metadata.CountProductsInFile > _catalogSettings.ExportImportProductsCountInOneFile)
|
|
{
|
|
await ImportProductsFromSplitedXlsxAsync(defaultWorksheet, metadata);
|
|
return;
|
|
}
|
|
|
|
//performance optimization, load all products by SKU in one SQL request
|
|
var currentVendor = await _workContext.GetCurrentVendorAsync();
|
|
var allProductsBySku = await _productService.GetProductsBySkuAsync(metadata.AllSku.ToArray(), currentVendor?.Id ?? 0);
|
|
|
|
//validate maximum number of products per vendor
|
|
if (_vendorSettings.MaximumProductNumber > 0 &&
|
|
currentVendor != null)
|
|
{
|
|
var newProductsCount = metadata.CountProductsInFile - allProductsBySku.Count;
|
|
if (await _productService.GetNumberOfProductsByVendorIdAsync(currentVendor.Id) + newProductsCount > _vendorSettings.MaximumProductNumber)
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.ExceededMaximumNumber"), _vendorSettings.MaximumProductNumber));
|
|
}
|
|
|
|
//performance optimization, load all categories IDs for products in one SQL request
|
|
var allProductsCategoryIds = await _categoryService.GetProductCategoryIdsAsync(allProductsBySku.Select(p => p.Id).ToArray());
|
|
|
|
//performance optimization, load all categories in one SQL request
|
|
Dictionary<CategoryKey, Category> allCategories = new();
|
|
try
|
|
{
|
|
var allCategoryList = await _categoryService.GetAllCategoriesAsync(showHidden: true);
|
|
|
|
allCategories = await allCategoryList
|
|
.WhereAwait(async c => await _categoryService.CanVendorAddProductsAsync(c, allCategoryList))
|
|
.ToDictionaryAwaitAsync(async c => {
|
|
var keyName = await _categoryService.GetFormattedBreadCrumbAsync(c, allCategoryList);
|
|
return new CategoryKey(keyName, c, c.LimitedToStores ? (await _storeMappingService.GetStoresIdsWithAccessAsync(c)).ToList() : new List<int>());
|
|
});
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
//categories with the same name are not supported in the same category level
|
|
throw new ArgumentException(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.CategoriesWithSameNameNotSupported"));
|
|
}
|
|
|
|
//performance optimization, load all manufacturers IDs for products in one SQL request
|
|
var allProductsManufacturerIds = await _manufacturerService.GetProductManufacturerIdsAsync(allProductsBySku.Select(p => p.Id).ToArray());
|
|
|
|
//performance optimization, load all manufacturers in one SQL request
|
|
var allManufacturers = await _manufacturerService.GetAllManufacturersAsync(showHidden: true);
|
|
|
|
//performance optimization, load all stores in one SQL request
|
|
var allStores = await _storeService.GetAllStoresAsync();
|
|
|
|
//product to import images
|
|
var productPictureMetadata = new List<ProductPictureMetadata>();
|
|
|
|
Product lastLoadedProduct = null;
|
|
var typeOfExportedAttribute = ExportedAdditionalProductInfoType.NotSpecified;
|
|
|
|
for (var iRow = 2; iRow < metadata.EndRow; iRow++)
|
|
{
|
|
if (defaultWorksheet.Row(iRow).OutlineLevel != 0)
|
|
{
|
|
if (lastLoadedProduct == null)
|
|
continue;
|
|
|
|
var newTypeOfExportedAttribute = GetTypeOfExportedAdditionalProductInfo(defaultWorksheet, metadata.LocalizedWorksheets, metadata.ProductAttributeManager, metadata.SpecificationAttributeManager, metadata.TierPriceManager, iRow);
|
|
|
|
//skip caption row
|
|
if (newTypeOfExportedAttribute != ExportedAdditionalProductInfoType.NotSpecified &&
|
|
newTypeOfExportedAttribute != typeOfExportedAttribute)
|
|
{
|
|
typeOfExportedAttribute = newTypeOfExportedAttribute;
|
|
continue;
|
|
}
|
|
|
|
switch (typeOfExportedAttribute)
|
|
{
|
|
case ExportedAdditionalProductInfoType.ProductAttribute:
|
|
await ImportProductAttributeAsync(metadata, lastLoadedProduct, languages, iRow);
|
|
break;
|
|
case ExportedAdditionalProductInfoType.SpecificationAttribute:
|
|
await ImportSpecificationAttributeAsync(metadata, lastLoadedProduct, languages, iRow);
|
|
break;
|
|
case ExportedAdditionalProductInfoType.TierPrices:
|
|
await ImportTierPriceAsync(metadata, lastLoadedProduct, languages, iRow);
|
|
break;
|
|
case ExportedAdditionalProductInfoType.NotSpecified:
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
metadata.Manager.ReadDefaultFromXlsx(defaultWorksheet, iRow);
|
|
|
|
var product = metadata.SkuCellNum > 0 ? allProductsBySku.FirstOrDefault(p => p.Sku == metadata.Manager.GetDefaultProperty("SKU").StringValue) : null;
|
|
|
|
var isNew = product == null;
|
|
|
|
product ??= new Product();
|
|
|
|
//some of previous values
|
|
var previousStockQuantity = product.StockQuantity;
|
|
var previousWarehouseId = product.WarehouseId;
|
|
var prevTotalStockQuantity = await _productService.GetTotalStockQuantityAsync(product);
|
|
|
|
if (isNew)
|
|
product.CreatedOnUtc = DateTime.UtcNow;
|
|
|
|
foreach (var property in metadata.Manager.GetDefaultProperties)
|
|
{
|
|
switch (property.PropertyName)
|
|
{
|
|
case "ProductType":
|
|
product.ProductTypeId = property.IntValue;
|
|
break;
|
|
case "ParentGroupedProductId":
|
|
product.ParentGroupedProductId = property.IntValue;
|
|
break;
|
|
case "VisibleIndividually":
|
|
product.VisibleIndividually = property.BooleanValue;
|
|
break;
|
|
case "Name":
|
|
product.Name = property.StringValue;
|
|
break;
|
|
case "ShortDescription":
|
|
product.ShortDescription = property.StringValue;
|
|
break;
|
|
case "FullDescription":
|
|
product.FullDescription = property.StringValue;
|
|
break;
|
|
case "Vendor":
|
|
//vendor can't change this field
|
|
if (currentVendor == null)
|
|
product.VendorId = property.IntValue;
|
|
break;
|
|
case "ProductTemplate":
|
|
product.ProductTemplateId = property.IntValue;
|
|
break;
|
|
case "ShowOnHomepage":
|
|
//vendor can't change this field
|
|
if (currentVendor == null)
|
|
product.ShowOnHomepage = property.BooleanValue;
|
|
break;
|
|
case "DisplayOrder":
|
|
//vendor can't change this field
|
|
if (currentVendor == null)
|
|
product.DisplayOrder = property.IntValue;
|
|
break;
|
|
case "MetaKeywords":
|
|
product.MetaKeywords = property.StringValue;
|
|
break;
|
|
case "MetaDescription":
|
|
product.MetaDescription = property.StringValue;
|
|
break;
|
|
case "MetaTitle":
|
|
product.MetaTitle = property.StringValue;
|
|
break;
|
|
case "AllowCustomerReviews":
|
|
product.AllowCustomerReviews = property.BooleanValue;
|
|
break;
|
|
case "Published":
|
|
product.Published = property.BooleanValue;
|
|
break;
|
|
case "SKU":
|
|
product.Sku = property.StringValue;
|
|
break;
|
|
case "ManufacturerPartNumber":
|
|
product.ManufacturerPartNumber = property.StringValue;
|
|
break;
|
|
case "Gtin":
|
|
product.Gtin = property.StringValue;
|
|
break;
|
|
case "IsGiftCard":
|
|
product.IsGiftCard = property.BooleanValue;
|
|
break;
|
|
case "GiftCardType":
|
|
product.GiftCardTypeId = property.IntValue;
|
|
break;
|
|
case "OverriddenGiftCardAmount":
|
|
product.OverriddenGiftCardAmount = property.DecimalValue;
|
|
break;
|
|
case "RequireOtherProducts":
|
|
product.RequireOtherProducts = property.BooleanValue;
|
|
break;
|
|
case "RequiredProductIds":
|
|
product.RequiredProductIds = property.StringValue;
|
|
break;
|
|
case "AutomaticallyAddRequiredProducts":
|
|
product.AutomaticallyAddRequiredProducts = property.BooleanValue;
|
|
break;
|
|
case "IsDownload":
|
|
product.IsDownload = property.BooleanValue;
|
|
break;
|
|
case "DownloadId":
|
|
product.DownloadId = property.IntValue;
|
|
break;
|
|
case "UnlimitedDownloads":
|
|
product.UnlimitedDownloads = property.BooleanValue;
|
|
break;
|
|
case "MaxNumberOfDownloads":
|
|
product.MaxNumberOfDownloads = property.IntValue;
|
|
break;
|
|
case "DownloadActivationType":
|
|
product.DownloadActivationTypeId = property.IntValue;
|
|
break;
|
|
case "HasSampleDownload":
|
|
product.HasSampleDownload = property.BooleanValue;
|
|
break;
|
|
case "SampleDownloadId":
|
|
product.SampleDownloadId = property.IntValue;
|
|
break;
|
|
case "HasUserAgreement":
|
|
product.HasUserAgreement = property.BooleanValue;
|
|
break;
|
|
case "UserAgreementText":
|
|
product.UserAgreementText = property.StringValue;
|
|
break;
|
|
case "IsRecurring":
|
|
product.IsRecurring = property.BooleanValue;
|
|
break;
|
|
case "RecurringCycleLength":
|
|
product.RecurringCycleLength = property.IntValue;
|
|
break;
|
|
case "RecurringCyclePeriod":
|
|
product.RecurringCyclePeriodId = property.IntValue;
|
|
break;
|
|
case "RecurringTotalCycles":
|
|
product.RecurringTotalCycles = property.IntValue;
|
|
break;
|
|
case "IsRental":
|
|
product.IsRental = property.BooleanValue;
|
|
break;
|
|
case "RentalPriceLength":
|
|
product.RentalPriceLength = property.IntValue;
|
|
break;
|
|
case "RentalPricePeriod":
|
|
product.RentalPricePeriodId = property.IntValue;
|
|
break;
|
|
case "IsShipEnabled":
|
|
product.IsShipEnabled = property.BooleanValue;
|
|
break;
|
|
case "IsFreeShipping":
|
|
product.IsFreeShipping = property.BooleanValue;
|
|
break;
|
|
case "ShipSeparately":
|
|
product.ShipSeparately = property.BooleanValue;
|
|
break;
|
|
case "AdditionalShippingCharge":
|
|
product.AdditionalShippingCharge = property.DecimalValue;
|
|
break;
|
|
case "DeliveryDate":
|
|
product.DeliveryDateId = property.IntValue;
|
|
break;
|
|
case "IsTaxExempt":
|
|
product.IsTaxExempt = property.BooleanValue;
|
|
break;
|
|
case "TaxCategory":
|
|
product.TaxCategoryId = property.IntValue;
|
|
break;
|
|
case "ManageInventoryMethod":
|
|
product.ManageInventoryMethodId = property.IntValue;
|
|
break;
|
|
case "ProductAvailabilityRange":
|
|
product.ProductAvailabilityRangeId = property.IntValue;
|
|
break;
|
|
case "UseMultipleWarehouses":
|
|
product.UseMultipleWarehouses = property.BooleanValue;
|
|
break;
|
|
case "WarehouseId":
|
|
product.WarehouseId = property.IntValue;
|
|
break;
|
|
case "StockQuantity":
|
|
product.StockQuantity = property.IntValue;
|
|
break;
|
|
case "DisplayStockAvailability":
|
|
product.DisplayStockAvailability = property.BooleanValue;
|
|
break;
|
|
case "DisplayStockQuantity":
|
|
product.DisplayStockQuantity = property.BooleanValue;
|
|
break;
|
|
case "MinStockQuantity":
|
|
product.MinStockQuantity = property.IntValue;
|
|
break;
|
|
case "LowStockActivity":
|
|
product.LowStockActivityId = property.IntValue;
|
|
break;
|
|
case "NotifyAdminForQuantityBelow":
|
|
product.NotifyAdminForQuantityBelow = property.IntValue;
|
|
break;
|
|
case "BackorderMode":
|
|
product.BackorderModeId = property.IntValue;
|
|
break;
|
|
case "AllowBackInStockSubscriptions":
|
|
product.AllowBackInStockSubscriptions = property.BooleanValue;
|
|
break;
|
|
case "OrderMinimumQuantity":
|
|
product.OrderMinimumQuantity = property.IntValue;
|
|
break;
|
|
case "OrderMaximumQuantity":
|
|
product.OrderMaximumQuantity = property.IntValue;
|
|
break;
|
|
case "AllowedQuantities":
|
|
product.AllowedQuantities = property.StringValue;
|
|
break;
|
|
case "AllowAddingOnlyExistingAttributeCombinations":
|
|
product.AllowAddingOnlyExistingAttributeCombinations = property.BooleanValue;
|
|
break;
|
|
case "NotReturnable":
|
|
product.NotReturnable = property.BooleanValue;
|
|
break;
|
|
case "DisableBuyButton":
|
|
product.DisableBuyButton = property.BooleanValue;
|
|
break;
|
|
case "DisableWishlistButton":
|
|
product.DisableWishlistButton = property.BooleanValue;
|
|
break;
|
|
case "AvailableForPreOrder":
|
|
product.AvailableForPreOrder = property.BooleanValue;
|
|
break;
|
|
case "PreOrderAvailabilityStartDateTimeUtc":
|
|
product.PreOrderAvailabilityStartDateTimeUtc = property.DateTimeNullable;
|
|
break;
|
|
case "CallForPrice":
|
|
product.CallForPrice = property.BooleanValue;
|
|
break;
|
|
case "Price":
|
|
product.Price = property.DecimalValue;
|
|
break;
|
|
case "OldPrice":
|
|
product.OldPrice = property.DecimalValue;
|
|
break;
|
|
case "ProductCost":
|
|
product.ProductCost = property.DecimalValue;
|
|
break;
|
|
case "CustomerEntersPrice":
|
|
product.CustomerEntersPrice = property.BooleanValue;
|
|
break;
|
|
case "MinimumCustomerEnteredPrice":
|
|
product.MinimumCustomerEnteredPrice = property.DecimalValue;
|
|
break;
|
|
case "MaximumCustomerEnteredPrice":
|
|
product.MaximumCustomerEnteredPrice = property.DecimalValue;
|
|
break;
|
|
case "BasepriceEnabled":
|
|
product.BasepriceEnabled = property.BooleanValue;
|
|
break;
|
|
case "BasepriceAmount":
|
|
product.BasepriceAmount = property.DecimalValue;
|
|
break;
|
|
case "BasepriceUnit":
|
|
product.BasepriceUnitId = property.IntValue;
|
|
break;
|
|
case "BasepriceBaseAmount":
|
|
product.BasepriceBaseAmount = property.DecimalValue;
|
|
break;
|
|
case "BasepriceBaseUnit":
|
|
product.BasepriceBaseUnitId = property.IntValue;
|
|
break;
|
|
case "MarkAsNew":
|
|
product.MarkAsNew = property.BooleanValue;
|
|
break;
|
|
case "MarkAsNewStartDateTimeUtc":
|
|
product.MarkAsNewStartDateTimeUtc = property.DateTimeNullable;
|
|
break;
|
|
case "MarkAsNewEndDateTimeUtc":
|
|
product.MarkAsNewEndDateTimeUtc = property.DateTimeNullable;
|
|
break;
|
|
case "Weight":
|
|
product.Weight = property.DecimalValue;
|
|
break;
|
|
case "Length":
|
|
product.Length = property.DecimalValue;
|
|
break;
|
|
case "Width":
|
|
product.Width = property.DecimalValue;
|
|
break;
|
|
case "Height":
|
|
product.Height = property.DecimalValue;
|
|
break;
|
|
case "IsLimitedToStores":
|
|
product.LimitedToStores = property.BooleanValue;
|
|
break;
|
|
case "DisplayAttributeCombinationImagesOnly":
|
|
product.DisplayAttributeCombinationImagesOnly = property.BooleanValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//set some default values if not specified
|
|
if (isNew && metadata.Properties.All(p => p.PropertyName != "ProductType"))
|
|
product.ProductType = ProductType.SimpleProduct;
|
|
if (isNew && metadata.Properties.All(p => p.PropertyName != "VisibleIndividually"))
|
|
product.VisibleIndividually = true;
|
|
if (isNew && metadata.Properties.All(p => p.PropertyName != "Published"))
|
|
product.Published = true;
|
|
|
|
//sets the current vendor for the new product
|
|
if (isNew && currentVendor != null)
|
|
product.VendorId = currentVendor.Id;
|
|
|
|
product.UpdatedOnUtc = DateTime.UtcNow;
|
|
|
|
if (isNew)
|
|
await _productService.InsertProductAsync(product);
|
|
else
|
|
await _productService.UpdateProductAsync(product);
|
|
|
|
//quantity change history
|
|
if (isNew || previousWarehouseId == product.WarehouseId)
|
|
{
|
|
await _productService.AddStockQuantityHistoryEntryAsync(product, product.StockQuantity - previousStockQuantity, product.StockQuantity,
|
|
product.WarehouseId, await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.ImportProduct.Edit"));
|
|
}
|
|
//warehouse is changed
|
|
else
|
|
{
|
|
//compose a message
|
|
var oldWarehouseMessage = string.Empty;
|
|
if (previousWarehouseId > 0)
|
|
{
|
|
var oldWarehouse = await _shippingService.GetWarehouseByIdAsync(previousWarehouseId);
|
|
if (oldWarehouse != null)
|
|
oldWarehouseMessage = string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.EditWarehouse.Old"), oldWarehouse.Name);
|
|
}
|
|
|
|
var newWarehouseMessage = string.Empty;
|
|
if (product.WarehouseId > 0)
|
|
{
|
|
var newWarehouse = await _shippingService.GetWarehouseByIdAsync(product.WarehouseId);
|
|
if (newWarehouse != null)
|
|
newWarehouseMessage = string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.EditWarehouse.New"), newWarehouse.Name);
|
|
}
|
|
|
|
var message = string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.ImportProduct.EditWarehouse"), oldWarehouseMessage, newWarehouseMessage);
|
|
|
|
//record history
|
|
await _productService.AddStockQuantityHistoryEntryAsync(product, -previousStockQuantity, 0, previousWarehouseId, message);
|
|
await _productService.AddStockQuantityHistoryEntryAsync(product, product.StockQuantity, product.StockQuantity, product.WarehouseId, message);
|
|
}
|
|
|
|
if (!isNew &&
|
|
product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
|
|
product.BackorderMode == BackorderMode.NoBackorders &&
|
|
product.AllowBackInStockSubscriptions &&
|
|
await _productService.GetTotalStockQuantityAsync(product) > 0 &&
|
|
prevTotalStockQuantity <= 0 &&
|
|
product.Published &&
|
|
!product.Deleted)
|
|
{
|
|
await _backInStockSubscriptionService.SendNotificationsToSubscribersAsync(product);
|
|
}
|
|
|
|
var tempProperty = metadata.Manager.GetDefaultProperty("SeName");
|
|
|
|
//search engine name
|
|
var seName = tempProperty?.StringValue ?? (isNew ? string.Empty : await _urlRecordService.GetSeNameAsync(product, 0));
|
|
await _urlRecordService.SaveSlugAsync(product, await _urlRecordService.ValidateSeNameAsync(product, seName, product.Name, true), 0);
|
|
|
|
//save product localized data
|
|
await ImportProductLocalizedAsync(product, metadata, iRow, languages);
|
|
|
|
tempProperty = metadata.Manager.GetDefaultProperty("Categories");
|
|
|
|
if (tempProperty != null)
|
|
{
|
|
var categoryList = tempProperty.StringValue;
|
|
|
|
//category mappings
|
|
var categories = isNew || !allProductsCategoryIds.ContainsKey(product.Id) ? Array.Empty<int>() : allProductsCategoryIds[product.Id];
|
|
|
|
var storesIds = product.LimitedToStores
|
|
? (await _storeMappingService.GetStoresIdsWithAccessAsync(product)).ToList()
|
|
: new List<int>();
|
|
|
|
var importedCategories = await categoryList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(categoryName => new CategoryKey(categoryName, storesIds: storesIds))
|
|
.SelectAwait(async categoryKey =>
|
|
{
|
|
var rez = (allCategories.TryGetValue(categoryKey, out var value) ? value.Id : allCategories.Values.FirstOrDefault(c => c.Name == categoryKey.Key)?.Id) ??
|
|
allCategories.FirstOrDefault(p =>
|
|
p.Key.Key.Equals(categoryKey.Key, StringComparison.InvariantCultureIgnoreCase))
|
|
.Value?.Id;
|
|
|
|
if (!rez.HasValue && int.TryParse(categoryKey.Key, out var id))
|
|
rez = id;
|
|
|
|
if (!rez.HasValue)
|
|
//database doesn't contain the imported category
|
|
//this can happen if the category was deleted during the import process
|
|
await _logger.WarningAsync(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.DatabaseNotContainCategory"), product.Name, categoryKey.Key));
|
|
|
|
return rez;
|
|
}).Where(id => id != null).ToListAsync();
|
|
|
|
foreach (var categoryId in importedCategories)
|
|
{
|
|
if (categories.Any(c => c == categoryId))
|
|
continue;
|
|
|
|
var productCategory = new ProductCategory
|
|
{
|
|
ProductId = product.Id,
|
|
CategoryId = categoryId.Value,
|
|
IsFeaturedProduct = false,
|
|
DisplayOrder = 1
|
|
};
|
|
await _categoryService.InsertProductCategoryAsync(productCategory);
|
|
}
|
|
|
|
//delete product categories
|
|
var deletedProductCategories = await categories.Where(categoryId => !importedCategories.Contains(categoryId))
|
|
.SelectAwait(async categoryId => (await _categoryService.GetProductCategoriesByProductIdAsync(product.Id, true)).FirstOrDefault(pc => pc.CategoryId == categoryId)).Where(pc => pc != null).ToListAsync();
|
|
|
|
foreach (var deletedProductCategory in deletedProductCategories)
|
|
await _categoryService.DeleteProductCategoryAsync(deletedProductCategory);
|
|
}
|
|
|
|
tempProperty = metadata.Manager.GetDefaultProperty("Manufacturers");
|
|
if (tempProperty != null)
|
|
{
|
|
var manufacturerList = tempProperty.StringValue;
|
|
|
|
//manufacturer mappings
|
|
var manufacturers = isNew || !allProductsManufacturerIds.ContainsKey(product.Id) ? Array.Empty<int>() : allProductsManufacturerIds[product.Id];
|
|
|
|
var importedManufacturers = await manufacturerList
|
|
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
|
|
.SelectAwait(async x =>
|
|
{
|
|
var id = allManufacturers.FirstOrDefault(m => m.Name == x.Trim())?.Id;
|
|
|
|
if (id != null)
|
|
return id;
|
|
|
|
id = int.TryParse(x, out var parsedId) ? parsedId : null;
|
|
|
|
if (!id.HasValue)
|
|
//database doesn't contain the imported manufacturer
|
|
//this can happen if the manufacturer was deleted during the import process
|
|
await _logger.WarningAsync(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Products.Import.DatabaseNotContainManufacturer"), product.Name, x));
|
|
|
|
return id;
|
|
}).Where(id => id.HasValue).ToListAsync();
|
|
|
|
foreach (var manufacturerId in importedManufacturers)
|
|
{
|
|
if (manufacturers.Any(c => c == manufacturerId))
|
|
continue;
|
|
|
|
var productManufacturer = new ProductManufacturer
|
|
{
|
|
ProductId = product.Id,
|
|
ManufacturerId = manufacturerId.Value,
|
|
IsFeaturedProduct = false,
|
|
DisplayOrder = 1
|
|
};
|
|
await _manufacturerService.InsertProductManufacturerAsync(productManufacturer);
|
|
}
|
|
|
|
//delete product manufacturers
|
|
var deletedProductsManufacturers = await manufacturers.Where(manufacturerId => !importedManufacturers.Contains(manufacturerId))
|
|
.SelectAwait(async manufacturerId => (await _manufacturerService.GetProductManufacturersByProductIdAsync(product.Id)).FirstOrDefault(pc => pc.ManufacturerId == manufacturerId)).ToListAsync();
|
|
foreach (var deletedProductManufacturer in deletedProductsManufacturers.Where(m => m != null))
|
|
await _manufacturerService.DeleteProductManufacturerAsync(deletedProductManufacturer);
|
|
}
|
|
|
|
tempProperty = metadata.Manager.GetDefaultProperty("ProductTags");
|
|
if (tempProperty != null)
|
|
{
|
|
var productTags = tempProperty.StringValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToList();
|
|
|
|
//searching existing product tags by their id
|
|
var productTagIds = productTags.Where(pt => int.TryParse(pt, out var _)).Select(int.Parse);
|
|
|
|
var productTagsByIds = (await _productTagService.GetAllProductTagsByProductIdAsync(product.Id)).Where(pt => productTagIds.Contains(pt.Id)).ToList();
|
|
|
|
productTags.AddRange(productTagsByIds.Select(pt => pt.Name));
|
|
var filter = productTagsByIds.Select(pt => pt.Id.ToString()).ToList();
|
|
|
|
//product tag mappings
|
|
await _productTagService.UpdateProductTagsAsync(product, productTags.Where(pt => !filter.Contains(pt)).ToArray());
|
|
}
|
|
|
|
tempProperty = metadata.Manager.GetDefaultProperty("LimitedToStores");
|
|
if (tempProperty != null)
|
|
{
|
|
var limitedToStoresList = tempProperty.StringValue;
|
|
|
|
var importedStores = product.LimitedToStores ? limitedToStoresList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(x => allStores.FirstOrDefault(store => store.Name == x.Trim())?.Id ?? int.Parse(x.Trim())).ToList() : new List<int>();
|
|
|
|
await _productService.UpdateProductStoreMappingsAsync(product, importedStores);
|
|
}
|
|
|
|
var picture1 = await DownloadFileAsync(metadata.Manager.GetDefaultProperty("Picture1")?.StringValue, downloadedFiles);
|
|
var picture2 = await DownloadFileAsync(metadata.Manager.GetDefaultProperty("Picture2")?.StringValue, downloadedFiles);
|
|
var picture3 = await DownloadFileAsync(metadata.Manager.GetDefaultProperty("Picture3")?.StringValue, downloadedFiles);
|
|
|
|
productPictureMetadata.Add(new ProductPictureMetadata
|
|
{
|
|
ProductItem = product,
|
|
Picture1Path = picture1,
|
|
Picture2Path = picture2,
|
|
Picture3Path = picture3,
|
|
IsNew = isNew
|
|
});
|
|
|
|
lastLoadedProduct = product;
|
|
}
|
|
|
|
if (_mediaSettings.ImportProductImagesUsingHash && await _pictureService.IsStoreInDbAsync())
|
|
await ImportProductImagesUsingHashAsync(productPictureMetadata, allProductsBySku);
|
|
else
|
|
await ImportProductImagesUsingServicesAsync(productPictureMetadata);
|
|
|
|
foreach (var downloadedFile in downloadedFiles)
|
|
{
|
|
if (!_fileProvider.FileExists(downloadedFile))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
_fileProvider.DeleteFile(downloadedFile);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
//activity log
|
|
await _customerActivityService.InsertActivityAsync("ImportProducts", string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportProducts"), metadata.CountProductsInFile));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import newsletter subscribers from TXT file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the number of imported subscribers
|
|
/// </returns>
|
|
public virtual async Task<int> ImportNewsletterSubscribersFromTxtAsync(Stream stream)
|
|
{
|
|
var count = 0;
|
|
using (var reader = new StreamReader(stream))
|
|
while (!reader.EndOfStream)
|
|
{
|
|
var line = await reader.ReadLineAsync();
|
|
if (string.IsNullOrWhiteSpace(line))
|
|
continue;
|
|
var tmp = line.Split(',');
|
|
|
|
if (tmp.Length > 3)
|
|
throw new NopException("Wrong file format");
|
|
|
|
var isActive = true;
|
|
|
|
var store = await _storeContext.GetCurrentStoreAsync();
|
|
var storeId = store.Id;
|
|
|
|
//"email" field specified
|
|
var email = tmp[0].Trim();
|
|
|
|
if (!CommonHelper.IsValidEmail(email))
|
|
continue;
|
|
|
|
//"active" field specified
|
|
if (tmp.Length >= 2)
|
|
isActive = bool.Parse(tmp[1].Trim());
|
|
|
|
//"storeId" field specified
|
|
if (tmp.Length == 3)
|
|
storeId = int.Parse(tmp[2].Trim());
|
|
|
|
//import
|
|
var subscription = await _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreIdAsync(email, storeId);
|
|
if (subscription != null)
|
|
{
|
|
subscription.Email = email;
|
|
subscription.Active = isActive;
|
|
await _newsLetterSubscriptionService.UpdateNewsLetterSubscriptionAsync(subscription);
|
|
}
|
|
else
|
|
{
|
|
var customer = await _customerService.GetCustomerByEmailAsync(email);
|
|
subscription = new NewsLetterSubscription
|
|
{
|
|
Active = isActive,
|
|
CreatedOnUtc = DateTime.UtcNow,
|
|
Email = email,
|
|
StoreId = storeId,
|
|
LanguageId = customer?.LanguageId ?? store.DefaultLanguageId,
|
|
NewsLetterSubscriptionGuid = Guid.NewGuid()
|
|
};
|
|
await _newsLetterSubscriptionService.InsertNewsLetterSubscriptionAsync(subscription);
|
|
}
|
|
|
|
count++;
|
|
}
|
|
|
|
await _customerActivityService.InsertActivityAsync("ImportNewsLetterSubscriptions",
|
|
string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportNewsLetterSubscriptions"), count));
|
|
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import states from TXT file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <param name="writeLog">Indicates whether to add logging</param>
|
|
/// <returns>
|
|
/// A task that represents the asynchronous operation
|
|
/// The task result contains the number of imported states
|
|
/// </returns>
|
|
public virtual async Task<int> ImportStatesFromTxtAsync(Stream stream, bool writeLog = true)
|
|
{
|
|
var count = 0;
|
|
using (var reader = new StreamReader(stream))
|
|
{
|
|
while (!reader.EndOfStream)
|
|
{
|
|
var line = await reader.ReadLineAsync();
|
|
if (string.IsNullOrWhiteSpace(line))
|
|
continue;
|
|
var tmp = line.Split(',');
|
|
|
|
if (tmp.Length != 5)
|
|
throw new NopException("Wrong file format");
|
|
|
|
//parse
|
|
var countryTwoLetterIsoCode = tmp[0].Trim();
|
|
var name = tmp[1].Trim();
|
|
var abbreviation = tmp[2].Trim();
|
|
var published = bool.Parse(tmp[3].Trim());
|
|
var displayOrder = int.Parse(tmp[4].Trim());
|
|
|
|
var country = await _countryService.GetCountryByTwoLetterIsoCodeAsync(countryTwoLetterIsoCode);
|
|
if (country == null)
|
|
{
|
|
//country cannot be loaded. skip
|
|
continue;
|
|
}
|
|
|
|
//import
|
|
var states = await _stateProvinceService.GetStateProvincesByCountryIdAsync(country.Id, showHidden: true);
|
|
var state = states.FirstOrDefault(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
if (state != null)
|
|
{
|
|
state.Abbreviation = abbreviation;
|
|
state.Published = published;
|
|
state.DisplayOrder = displayOrder;
|
|
await _stateProvinceService.UpdateStateProvinceAsync(state);
|
|
}
|
|
else
|
|
{
|
|
state = new StateProvince
|
|
{
|
|
CountryId = country.Id,
|
|
Name = name,
|
|
Abbreviation = abbreviation,
|
|
Published = published,
|
|
DisplayOrder = displayOrder
|
|
};
|
|
await _stateProvinceService.InsertStateProvinceAsync(state);
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
//activity log
|
|
if (writeLog)
|
|
{
|
|
await _customerActivityService.InsertActivityAsync("ImportStates",
|
|
string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportStates"), count));
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import manufacturers from XLSX file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task ImportManufacturersFromXlsxAsync(Stream stream)
|
|
{
|
|
using var workbook = new XLWorkbook(stream);
|
|
|
|
var languages = await _languageService.GetAllLanguagesAsync(showHidden: true);
|
|
|
|
//the columns
|
|
var metadata = GetWorkbookMetadata<Manufacturer>(workbook, languages);
|
|
var defaultWorksheet = metadata.DefaultWorksheet;
|
|
var defaultProperties = metadata.DefaultProperties;
|
|
var localizedProperties = metadata.LocalizedProperties;
|
|
|
|
var manager = new PropertyManager<Manufacturer, Language>(defaultProperties, _catalogSettings, localizedProperties, languages);
|
|
|
|
var iRow = 2;
|
|
var setSeName = defaultProperties.Any(p => p.PropertyName == "SeName");
|
|
|
|
while (true)
|
|
{
|
|
var allColumnsAreEmpty = manager.GetDefaultProperties
|
|
.Select(property => defaultWorksheet.Row(iRow).Cell(property.PropertyOrderPosition))
|
|
.All(cell => cell?.Value == null || string.IsNullOrEmpty(cell.Value.ToString()));
|
|
|
|
if (allColumnsAreEmpty)
|
|
break;
|
|
|
|
manager.ReadDefaultFromXlsx(defaultWorksheet, iRow);
|
|
|
|
var manufacturer = await _manufacturerService.GetManufacturerByIdAsync(manager.GetDefaultProperty("Id").IntValue);
|
|
|
|
var isNew = manufacturer == null;
|
|
|
|
manufacturer ??= new Manufacturer();
|
|
|
|
if (isNew)
|
|
{
|
|
manufacturer.CreatedOnUtc = DateTime.UtcNow;
|
|
|
|
//default values
|
|
manufacturer.PageSize = _catalogSettings.DefaultManufacturerPageSize;
|
|
manufacturer.PageSizeOptions = _catalogSettings.DefaultManufacturerPageSizeOptions;
|
|
manufacturer.Published = true;
|
|
manufacturer.AllowCustomersToSelectPageSize = true;
|
|
}
|
|
|
|
var seName = string.Empty;
|
|
|
|
foreach (var property in manager.GetDefaultProperties)
|
|
{
|
|
switch (property.PropertyName)
|
|
{
|
|
case "Name":
|
|
manufacturer.Name = property.StringValue;
|
|
break;
|
|
case "Description":
|
|
manufacturer.Description = property.StringValue;
|
|
break;
|
|
case "ManufacturerTemplateId":
|
|
manufacturer.ManufacturerTemplateId = property.IntValue;
|
|
break;
|
|
case "MetaKeywords":
|
|
manufacturer.MetaKeywords = property.StringValue;
|
|
break;
|
|
case "MetaDescription":
|
|
manufacturer.MetaDescription = property.StringValue;
|
|
break;
|
|
case "MetaTitle":
|
|
manufacturer.MetaTitle = property.StringValue;
|
|
break;
|
|
case "Picture":
|
|
var picture = await LoadPictureAsync(manager.GetDefaultProperty("Picture").StringValue, manufacturer.Name, isNew ? null : (int?)manufacturer.PictureId);
|
|
|
|
if (picture != null)
|
|
manufacturer.PictureId = picture.Id;
|
|
|
|
break;
|
|
case "PageSize":
|
|
manufacturer.PageSize = property.IntValue;
|
|
break;
|
|
case "AllowCustomersToSelectPageSize":
|
|
manufacturer.AllowCustomersToSelectPageSize = property.BooleanValue;
|
|
break;
|
|
case "PageSizeOptions":
|
|
manufacturer.PageSizeOptions = property.StringValue;
|
|
break;
|
|
case "PriceRangeFiltering":
|
|
manufacturer.PriceRangeFiltering = property.BooleanValue;
|
|
break;
|
|
case "PriceFrom":
|
|
manufacturer.PriceFrom = property.DecimalValue;
|
|
break;
|
|
case "PriceTo":
|
|
manufacturer.PriceTo = property.DecimalValue;
|
|
break;
|
|
case "AutomaticallyCalculatePriceRange":
|
|
manufacturer.ManuallyPriceRange = property.BooleanValue;
|
|
break;
|
|
case "Published":
|
|
manufacturer.Published = property.BooleanValue;
|
|
break;
|
|
case "DisplayOrder":
|
|
manufacturer.DisplayOrder = property.IntValue;
|
|
break;
|
|
case "SeName":
|
|
seName = property.StringValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
manufacturer.UpdatedOnUtc = DateTime.UtcNow;
|
|
|
|
if (isNew)
|
|
await _manufacturerService.InsertManufacturerAsync(manufacturer);
|
|
else
|
|
await _manufacturerService.UpdateManufacturerAsync(manufacturer);
|
|
|
|
//search engine name
|
|
if (setSeName)
|
|
await _urlRecordService.SaveSlugAsync(manufacturer, await _urlRecordService.ValidateSeNameAsync(manufacturer, seName, manufacturer.Name, true), 0);
|
|
|
|
//save manufacturer localized data
|
|
await ImportManufaturerLocalizedAsync(manufacturer, metadata, manager, iRow, languages);
|
|
|
|
iRow++;
|
|
}
|
|
|
|
//activity log
|
|
await _customerActivityService.InsertActivityAsync("ImportManufacturers",
|
|
string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportManufacturers"), iRow - 2));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import categories from XLSX file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task ImportCategoriesFromXlsxAsync(Stream stream)
|
|
{
|
|
using var workbook = new XLWorkbook(stream);
|
|
|
|
var languages = await _languageService.GetAllLanguagesAsync(showHidden: true);
|
|
|
|
//the columns
|
|
var metadata = GetWorkbookMetadata<Category>(workbook, languages);
|
|
var defaultWorksheet = metadata.DefaultWorksheet;
|
|
var defaultProperties = metadata.DefaultProperties;
|
|
var localizedProperties = metadata.LocalizedProperties;
|
|
|
|
var manager = new PropertyManager<Category, Language>(defaultProperties, _catalogSettings, localizedProperties, languages);
|
|
|
|
var iRow = 2;
|
|
var setSeName = defaultProperties.Any(p => p.PropertyName == "SeName");
|
|
|
|
//performance optimization, load all categories in one SQL request
|
|
var allCategories = await (await _categoryService
|
|
.GetAllCategoriesAsync(showHidden: true))
|
|
.GroupByAwait(async c => await _categoryService.GetFormattedBreadCrumbAsync(c))
|
|
.ToDictionaryAsync(c => c.Key, c => c.FirstAsync());
|
|
|
|
var saveNextTime = new List<int>();
|
|
|
|
while (true)
|
|
{
|
|
var allColumnsAreEmpty = manager.GetDefaultProperties
|
|
.Select(property => defaultWorksheet.Row(iRow).Cell(property.PropertyOrderPosition))
|
|
.All(cell => string.IsNullOrEmpty(cell?.Value.ToString()));
|
|
|
|
if (allColumnsAreEmpty)
|
|
break;
|
|
|
|
//get category by data in xlsx file if it possible, or create new category
|
|
var (category, isNew, currentCategoryBreadCrumb) = await GetCategoryFromXlsxAsync(manager, defaultWorksheet, iRow, allCategories);
|
|
|
|
//update category by data in xlsx file
|
|
var (seName, isParentCategoryExists) = await UpdateCategoryByXlsxAsync(category, manager, allCategories, isNew);
|
|
|
|
if (isParentCategoryExists)
|
|
{
|
|
//if parent category exists in database then save category into database
|
|
await SaveCategoryAsync(isNew, category, allCategories, currentCategoryBreadCrumb, setSeName, seName);
|
|
|
|
//save category localized data
|
|
await ImportCategoryLocalizedAsync(category, metadata, manager, iRow, languages);
|
|
}
|
|
else
|
|
{
|
|
//if parent category doesn't exists in database then try save category into database next time
|
|
saveNextTime.Add(iRow);
|
|
}
|
|
|
|
iRow++;
|
|
}
|
|
|
|
var needSave = saveNextTime.Any();
|
|
|
|
while (needSave)
|
|
{
|
|
var remove = new List<int>();
|
|
|
|
//try to save unsaved categories
|
|
foreach (var rowId in saveNextTime)
|
|
{
|
|
//get category by data in xlsx file if it possible, or create new category
|
|
var (category, isNew, currentCategoryBreadCrumb) = await GetCategoryFromXlsxAsync(manager, defaultWorksheet, rowId, allCategories);
|
|
//update category by data in xlsx file
|
|
var (seName, isParentCategoryExists) = await UpdateCategoryByXlsxAsync(category, manager, allCategories, isNew);
|
|
|
|
if (!isParentCategoryExists)
|
|
continue;
|
|
|
|
//if parent category exists in database then save category into database
|
|
await SaveCategoryAsync(isNew, category, allCategories, currentCategoryBreadCrumb, setSeName, seName);
|
|
|
|
//save category localized data
|
|
await ImportCategoryLocalizedAsync(category, metadata, manager, rowId, languages);
|
|
|
|
remove.Add(rowId);
|
|
}
|
|
|
|
saveNextTime.RemoveAll(remove.Contains);
|
|
|
|
needSave = remove.Any() && saveNextTime.Any();
|
|
}
|
|
|
|
//activity log
|
|
await _customerActivityService.InsertActivityAsync("ImportCategories",
|
|
string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportCategories"), iRow - 2 - saveNextTime.Count));
|
|
|
|
if (!saveNextTime.Any())
|
|
return;
|
|
|
|
var categoriesName = new List<string>();
|
|
|
|
foreach (var rowId in saveNextTime)
|
|
{
|
|
manager.ReadDefaultFromXlsx(defaultWorksheet, rowId);
|
|
categoriesName.Add(manager.GetDefaultProperty("Name").StringValue);
|
|
}
|
|
|
|
throw new ArgumentException(string.Format(await _localizationService.GetResourceAsync("Admin.Catalog.Categories.Import.CategoriesArentImported"), string.Join(", ", categoriesName)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import orders from XLSX file
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <returns>A task that represents the asynchronous operation</returns>
|
|
public virtual async Task ImportOrdersFromXlsxAsync(Stream stream)
|
|
{
|
|
using var workbook = new XLWorkbook(stream);
|
|
|
|
var (metadata, worksheet) = await PrepareImportOrderDataAsync(workbook);
|
|
|
|
//performance optimization, load all orders by guid in one SQL request
|
|
var allOrdersByGuids = await _orderService.GetOrdersByGuidsAsync(metadata.AllOrderGuids.ToArray());
|
|
|
|
//performance optimization, load all customers by guid in one SQL request
|
|
var allCustomersByGuids = await _customerService.GetCustomersByGuidsAsync(metadata.AllCustomerGuids.ToArray());
|
|
|
|
Order lastLoadedOrder = null;
|
|
|
|
for (var iRow = 2; iRow < metadata.EndRow; iRow++)
|
|
{
|
|
//imports product attributes
|
|
if (worksheet.Row(iRow).OutlineLevel != 0)
|
|
{
|
|
if (lastLoadedOrder == null)
|
|
continue;
|
|
|
|
metadata.OrderItemManager.ReadDefaultFromXlsx(worksheet, iRow, 2);
|
|
|
|
//skip caption row
|
|
if (!metadata.OrderItemManager.IsCaption)
|
|
await ImportOrderItemAsync(metadata.OrderItemManager, lastLoadedOrder);
|
|
|
|
continue;
|
|
}
|
|
|
|
metadata.Manager.ReadDefaultFromXlsx(worksheet, iRow);
|
|
|
|
var order = allOrdersByGuids.FirstOrDefault(p => p.OrderGuid == metadata.Manager.GetDefaultProperty("OrderGuid").GuidValue);
|
|
|
|
var isNew = order == null;
|
|
|
|
order ??= new Order();
|
|
|
|
Address orderBillingAddress = null;
|
|
Address orderAddress = null;
|
|
|
|
if (isNew)
|
|
order.CreatedOnUtc = DateTime.UtcNow;
|
|
else
|
|
{
|
|
orderBillingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);
|
|
orderAddress = await _addressService.GetAddressByIdAsync((order.PickupInStore ? order.PickupAddressId : order.ShippingAddressId) ?? 0);
|
|
}
|
|
|
|
orderBillingAddress ??= new Address();
|
|
orderAddress ??= new Address();
|
|
|
|
var customer = allCustomersByGuids.FirstOrDefault(p => p.CustomerGuid.ToString() == metadata.Manager.GetDefaultProperty("CustomerGuid").StringValue);
|
|
|
|
var billingStateProvinceAbbreviation = string.Empty;
|
|
var shippingStateProvinceAbbreviation = string.Empty;
|
|
|
|
foreach (var property in metadata.Manager.GetDefaultProperties)
|
|
switch (property.PropertyName)
|
|
{
|
|
case "StoreId":
|
|
if (await _storeService.GetStoreByIdAsync(property.IntValue) is { })
|
|
order.StoreId = property.IntValue;
|
|
else
|
|
order.StoreId = (await _storeContext.GetCurrentStoreAsync())?.Id ?? 0;
|
|
break;
|
|
case "OrderGuid":
|
|
order.OrderGuid = property.GuidValue;
|
|
break;
|
|
case "CustomerId":
|
|
order.CustomerId = customer?.Id ?? 0;
|
|
break;
|
|
case "OrderStatus":
|
|
order.OrderStatus = (OrderStatus)property.PropertyValue;
|
|
break;
|
|
case "PaymentStatus":
|
|
order.PaymentStatus = (PaymentStatus)property.PropertyValue;
|
|
break;
|
|
case "ShippingStatus":
|
|
order.ShippingStatus = (ShippingStatus)property.PropertyValue;
|
|
break;
|
|
case "OrderSubtotalInclTax":
|
|
order.OrderSubtotalInclTax = property.DecimalValue;
|
|
break;
|
|
case "OrderSubtotalExclTax":
|
|
order.OrderSubtotalExclTax = property.DecimalValue;
|
|
break;
|
|
case "OrderSubTotalDiscountInclTax":
|
|
order.OrderSubTotalDiscountInclTax = property.DecimalValue;
|
|
break;
|
|
case "OrderSubTotalDiscountExclTax":
|
|
order.OrderSubTotalDiscountExclTax = property.DecimalValue;
|
|
break;
|
|
case "OrderShippingInclTax":
|
|
order.OrderShippingInclTax = property.DecimalValue;
|
|
break;
|
|
case "OrderShippingExclTax":
|
|
order.OrderShippingExclTax = property.DecimalValue;
|
|
break;
|
|
case "PaymentMethodAdditionalFeeInclTax":
|
|
order.PaymentMethodAdditionalFeeInclTax = property.DecimalValue;
|
|
break;
|
|
case "PaymentMethodAdditionalFeeExclTax":
|
|
order.PaymentMethodAdditionalFeeExclTax = property.DecimalValue;
|
|
break;
|
|
case "TaxRates":
|
|
order.TaxRates = property.StringValue;
|
|
break;
|
|
case "OrderTax":
|
|
order.OrderTax = property.DecimalValue;
|
|
break;
|
|
case "OrderTotal":
|
|
order.OrderTotal = property.DecimalValue;
|
|
break;
|
|
case "RefundedAmount":
|
|
order.RefundedAmount = property.DecimalValue;
|
|
break;
|
|
case "OrderDiscount":
|
|
order.OrderDiscount = property.DecimalValue;
|
|
break;
|
|
case "CurrencyRate":
|
|
order.CurrencyRate = property.DecimalValue;
|
|
break;
|
|
case "CustomerCurrencyCode":
|
|
order.CustomerCurrencyCode = property.StringValue;
|
|
break;
|
|
case "AffiliateId":
|
|
order.AffiliateId = property.IntValue;
|
|
break;
|
|
case "PaymentMethodSystemName":
|
|
order.PaymentMethodSystemName = property.StringValue;
|
|
break;
|
|
case "ShippingPickupInStore":
|
|
order.PickupInStore = property.BooleanValue;
|
|
break;
|
|
case "ShippingMethod":
|
|
order.ShippingMethod = property.StringValue;
|
|
break;
|
|
case "ShippingRateComputationMethodSystemName":
|
|
order.ShippingRateComputationMethodSystemName = property.StringValue;
|
|
break;
|
|
case "CustomValuesXml":
|
|
order.CustomValuesXml = property.StringValue;
|
|
break;
|
|
case "VatNumber":
|
|
order.VatNumber = property.StringValue;
|
|
break;
|
|
case "CreatedOnUtc":
|
|
order.CreatedOnUtc = DateTime.TryParse(property.StringValue, out var createdOnUtc) ? createdOnUtc : DateTime.UtcNow;
|
|
break;
|
|
case "BillingFirstName":
|
|
orderBillingAddress.FirstName = property.StringValue;
|
|
break;
|
|
case "BillingLastName":
|
|
orderBillingAddress.LastName = property.StringValue;
|
|
break;
|
|
case "BillingPhoneNumber":
|
|
orderBillingAddress.PhoneNumber = property.StringValue;
|
|
break;
|
|
case "BillingEmail":
|
|
orderBillingAddress.Email = property.StringValue;
|
|
break;
|
|
case "BillingFaxNumber":
|
|
orderBillingAddress.FaxNumber = property.StringValue;
|
|
break;
|
|
case "BillingCompany":
|
|
orderBillingAddress.Company = property.StringValue;
|
|
break;
|
|
case "BillingAddress1":
|
|
orderBillingAddress.Address1 = property.StringValue;
|
|
break;
|
|
case "BillingAddress2":
|
|
orderBillingAddress.Address2 = property.StringValue;
|
|
break;
|
|
case "BillingCity":
|
|
orderBillingAddress.City = property.StringValue;
|
|
break;
|
|
case "BillingCounty":
|
|
orderBillingAddress.County = property.StringValue;
|
|
break;
|
|
case "BillingStateProvinceAbbreviation":
|
|
billingStateProvinceAbbreviation = property.StringValue;
|
|
|
|
break;
|
|
case "BillingZipPostalCode":
|
|
orderBillingAddress.ZipPostalCode = property.StringValue;
|
|
break;
|
|
case "BillingCountryCode":
|
|
if (await _countryService.GetCountryByTwoLetterIsoCodeAsync(property.StringValue) is { } billingCountry)
|
|
orderBillingAddress.CountryId = billingCountry.Id;
|
|
break;
|
|
case "ShippingFirstName":
|
|
orderAddress.FirstName = property.StringValue;
|
|
break;
|
|
case "ShippingLastName":
|
|
orderAddress.LastName = property.StringValue;
|
|
break;
|
|
case "ShippingPhoneNumber":
|
|
orderAddress.PhoneNumber = property.StringValue;
|
|
break;
|
|
case "ShippingEmail":
|
|
orderAddress.Email = property.StringValue;
|
|
break;
|
|
case "ShippingFaxNumber":
|
|
orderAddress.FaxNumber = property.StringValue;
|
|
break;
|
|
case "ShippingCompany":
|
|
orderAddress.Company = property.StringValue;
|
|
break;
|
|
case "ShippingAddress1":
|
|
orderAddress.Address1 = property.StringValue;
|
|
break;
|
|
case "ShippingAddress2":
|
|
orderAddress.Address2 = property.StringValue;
|
|
break;
|
|
case "ShippingCity":
|
|
orderAddress.City = property.StringValue;
|
|
break;
|
|
case "ShippingCounty":
|
|
orderAddress.County = property.StringValue;
|
|
break;
|
|
case "ShippingStateProvinceAbbreviation":
|
|
shippingStateProvinceAbbreviation = property.StringValue;
|
|
break;
|
|
case "ShippingZipPostalCode":
|
|
orderAddress.ZipPostalCode = property.StringValue;
|
|
break;
|
|
case "ShippingCountryCode":
|
|
if (await _countryService.GetCountryByTwoLetterIsoCodeAsync(property.StringValue) is { } shippingCountry)
|
|
orderAddress.CountryId = shippingCountry.Id;
|
|
break;
|
|
}
|
|
|
|
if (await _stateProvinceService.GetStateProvinceByAbbreviationAsync(billingStateProvinceAbbreviation, orderBillingAddress.CountryId) is { } billingState)
|
|
orderBillingAddress.StateProvinceId = billingState.Id;
|
|
|
|
if (await _stateProvinceService.GetStateProvinceByAbbreviationAsync(shippingStateProvinceAbbreviation, orderAddress.CountryId) is { } shippingState)
|
|
orderAddress.StateProvinceId = shippingState.Id;
|
|
|
|
//check order address field values from excel
|
|
if (string.IsNullOrWhiteSpace(orderAddress.FirstName) && string.IsNullOrWhiteSpace(orderAddress.LastName) && string.IsNullOrWhiteSpace(orderAddress.Email))
|
|
orderAddress = null;
|
|
|
|
//insert or update billing address
|
|
if (orderBillingAddress.Id == 0)
|
|
{
|
|
await _addressService.InsertAddressAsync(orderBillingAddress);
|
|
order.BillingAddressId = orderBillingAddress.Id; //WTF :)
|
|
}
|
|
else
|
|
await _addressService.UpdateAddressAsync(orderBillingAddress);
|
|
|
|
//insert or update shipping/pickup address
|
|
if (orderAddress != null)
|
|
{
|
|
if (orderAddress.Id == 0)
|
|
{
|
|
await _addressService.InsertAddressAsync(orderAddress);
|
|
|
|
if (order.PickupInStore)
|
|
order.PickupAddressId = orderAddress.Id;
|
|
else
|
|
order.ShippingAddressId = orderAddress.Id;
|
|
}
|
|
else
|
|
await _addressService.UpdateAddressAsync(orderAddress);
|
|
}
|
|
else
|
|
order.ShippingAddressId = null;
|
|
|
|
//set some default values if not specified
|
|
if (isNew)
|
|
{
|
|
//customer language
|
|
var customerLanguage = await _languageService.GetLanguageByIdAsync(customer?.LanguageId ?? 0);
|
|
if (customerLanguage == null || !customerLanguage.Published)
|
|
customerLanguage = await _workContext.GetWorkingLanguageAsync();
|
|
order.CustomerLanguageId = customerLanguage.Id;
|
|
|
|
//tax display type
|
|
order.CustomerTaxDisplayType = _taxSettings.TaxDisplayType;
|
|
|
|
//set other default values
|
|
order.AllowStoringCreditCardNumber = false;
|
|
order.AuthorizationTransactionCode = string.Empty;
|
|
order.AuthorizationTransactionId = string.Empty;
|
|
order.AuthorizationTransactionResult = string.Empty;
|
|
order.CaptureTransactionId = string.Empty;
|
|
order.CaptureTransactionResult = string.Empty;
|
|
order.CardCvv2 = string.Empty;
|
|
order.CardExpirationMonth = string.Empty;
|
|
order.CardExpirationYear = string.Empty;
|
|
order.CardName = string.Empty;
|
|
order.CardNumber = string.Empty;
|
|
order.CardType = string.Empty;
|
|
order.CustomerIp = string.Empty;
|
|
order.CustomOrderNumber = string.Empty;
|
|
order.MaskedCreditCardNumber = string.Empty;
|
|
order.RefundedAmount = decimal.Zero;
|
|
order.SubscriptionTransactionId = string.Empty;
|
|
|
|
await _orderService.InsertOrderAsync(order);
|
|
|
|
//generate and set custom order number
|
|
order.CustomOrderNumber = _customNumberFormatter.GenerateOrderCustomNumber(order);
|
|
await _orderService.UpdateOrderAsync(order);
|
|
}
|
|
else
|
|
await _orderService.UpdateOrderAsync(order);
|
|
|
|
lastLoadedOrder = order;
|
|
}
|
|
|
|
//activity log
|
|
await _customerActivityService.InsertActivityAsync("ImportOrders", string.Format(await _localizationService.GetResourceAsync("ActivityLog.ImportOrders"), metadata.CountOrdersInFile));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Nested classes
|
|
|
|
protected partial class ProductPictureMetadata
|
|
{
|
|
public Product ProductItem { get; set; }
|
|
|
|
public string Picture1Path { get; set; }
|
|
|
|
public string Picture2Path { get; set; }
|
|
|
|
public string Picture3Path { get; set; }
|
|
|
|
public bool IsNew { get; set; }
|
|
}
|
|
|
|
protected partial class CategoryKey
|
|
{
|
|
public CategoryKey(string key, Category category = null, List<int> storesIds = null)
|
|
{
|
|
Key = key.Trim();
|
|
StoresIds = storesIds ?? new List<int>();
|
|
Category = category;
|
|
}
|
|
|
|
public List<int> StoresIds { get; }
|
|
|
|
public Category Category { get; }
|
|
|
|
public string Key { get; }
|
|
|
|
public bool Equals(CategoryKey y)
|
|
{
|
|
if (y == null)
|
|
return false;
|
|
|
|
if (Category != null && y.Category != null)
|
|
return Category.Id == y.Category.Id;
|
|
|
|
if ((StoresIds.Any() || y.StoresIds.Any())
|
|
&& (StoresIds.All(id => !y.StoresIds.Contains(id)) || y.StoresIds.All(id => !StoresIds.Contains(id))))
|
|
return false;
|
|
|
|
return Key.Equals(y.Key);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
if (!StoresIds.Any())
|
|
return Key.GetHashCode();
|
|
|
|
var storesIds = StoresIds.Select(id => id.ToString())
|
|
.Aggregate(string.Empty, (all, current) => all + current);
|
|
|
|
return $"{storesIds}_{Key}".GetHashCode();
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
return obj is CategoryKey other && other.Equals(this);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
} |