Skip to content

Commit 08c3667

Browse files
committed
Further implementation of import column mapping
1 parent 85e6226 commit 08c3667

9 files changed

Lines changed: 264 additions & 48 deletions

File tree

src/Libraries/SmartStore.Core/Domain/DataExchange/ImportEnums.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace SmartStore.Core.Domain.DataExchange
88
public enum ImportEntityType
99
{
1010
Product = 0,
11+
Category,
1112
Customer,
12-
NewsLetterSubscription,
13-
Category
13+
NewsLetterSubscription
1414
}
1515

1616
public enum ImportFileType

src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,33 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder)
173173
"Trennzeichen und Anführungszeichen können in CSV Dateien nicht gleich sein.");
174174

175175

176+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.BasePriceMeasureUnit", "Base price measure unit", "Grundpreis Maßeinheit");
177+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.ApprovedRatingSum", "Approved rating sum", "Summe genehmigter Bewertungen");
178+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.NotApprovedRatingSum", "Not approved rating sum", "Summe nicht genehmigter Bewertungen");
179+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.ApprovedTotalReviews", "Approved total reviews", "Summe genehmigter Rezensionen");
180+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.NotApprovedTotalReviews", "Not approved total reviews", "Summe nicht genehmigter Rezensionen");
181+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasTierPrices", "Has tier prices", "Hat Staffelpreise");
182+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.LowestAttributeCombinationPrice", "Lowest attribute combination price", "Niedrigster Attributkombinationspreis");
183+
builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasDiscountsApplied", "Has discounts applied", "Hat angewendete Rabatte");
184+
185+
builder.AddOrUpdate("Admin.Catalog.Categories.Fields.ParentCategory", "Parent category", "Übergeordnete Warengruppe");
186+
187+
builder.AddOrUpdate("Admin.Customers.Customers.Fields.CustomerGuid", "Customer GUID", "Kunden GUID");
188+
builder.AddOrUpdate("Admin.Customers.Customers.Fields.PasswordSalt", "Password salt", "Passwort Salt");
189+
builder.AddOrUpdate("Admin.Customers.Customers.Fields.IsSystemAccount", "Is system account", "Ist Systemkonto");
190+
builder.AddOrUpdate("Admin.Customers.Customers.Fields.LastLoginDateUtc", "Last login date", "Letztes Login-Datum");
191+
192+
builder.AddOrUpdate("Admin.Promotions.NewsLetterSubscriptions.Fields.NewsLetterSubscriptionGuid", "Subscription GUID", "Abonnement GUID");
193+
194+
builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.Note",
195+
"For each field of the import file you can optionally set to which entity property whose data is to be imported. Specifying a default value is also possible, which is applied when the import field is empty.",
196+
"Sie können optional für jedes Feld der Importdatei festlegen, zu welcher Entitätseigenschaft dessen Daten importiert werden sollen. Zudem ist die Angabe eines Standardwertes möglich, der angewendet wird, wenn das Importfeld leer ist.");
197+
198+
builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.ImportField", "Import Field", "Importfeld");
199+
builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.ImportFieldIndex", "Index (language code etc.)", "Index (Sprach-Code etc.)");
200+
builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.EntityProperty", "Entity property", "Eigenschaft der Entität");
201+
builder.AddOrUpdate("Admin.DataExchange.ColumnMapping.DefaultValue", "Default Value", "Standard Wert");
202+
176203
builder.Delete(
177204
"Admin.DataExchange.Export.LastExecution",
178205
"Admin.DataExchange.Export.Offset",

src/Libraries/SmartStore.Services/DataExchange/Import/IImportProfileService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System.Collections.Generic;
2+
using System.Linq;
23
using SmartStore.Core.Domain;
34
using SmartStore.Core.Domain.DataExchange;
45

@@ -40,5 +41,7 @@ public interface IImportProfileService
4041
/// <param name="id">Import profile identifier</param>
4142
/// <returns>Import profile</returns>
4243
ImportProfile GetImportProfileById(int id);
44+
45+
Dictionary<string, string> GetImportableEntityProperties(ImportEntityType entityType);
4346
}
4447
}

src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,106 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Data.Entity.Core.Metadata.Edm;
4+
using System.Data.Entity.Infrastructure;
5+
using System.Diagnostics;
26
using System.IO;
37
using System.Linq;
48
using SmartStore.Core.Data;
59
using SmartStore.Core.Domain;
610
using SmartStore.Core.Domain.DataExchange;
711
using SmartStore.Core.Domain.Tasks;
812
using SmartStore.Core.Events;
13+
using SmartStore.Services.Localization;
914
using SmartStore.Services.Tasks;
1015
using SmartStore.Utilities;
1116

1217
namespace SmartStore.Services.DataExchange.Import
1318
{
1419
public partial class ImportProfileService : IImportProfileService
1520
{
21+
private static object _lock = new object();
22+
private static Dictionary<ImportEntityType, Dictionary<string, string>> _entityProperties = null;
23+
1624
private readonly IRepository<ImportProfile> _importProfileRepository;
1725
private readonly IEventPublisher _eventPublisher;
1826
private readonly IScheduleTaskService _scheduleTaskService;
27+
private readonly ILocalizationService _localizationService;
1928
private readonly DataExchangeSettings _dataExchangeSettings;
2029

2130
public ImportProfileService(
2231
IRepository<ImportProfile> importProfileRepository,
2332
IEventPublisher eventPublisher,
2433
IScheduleTaskService scheduleTaskService,
34+
ILocalizationService localizationService,
2535
DataExchangeSettings dataExchangeSettings)
2636
{
2737
_importProfileRepository = importProfileRepository;
2838
_eventPublisher = eventPublisher;
2939
_scheduleTaskService = scheduleTaskService;
40+
_localizationService = localizationService;
3041
_dataExchangeSettings = dataExchangeSettings;
3142
}
3243

44+
private string GetLocalizedPropertyName(ImportEntityType type, string propertyName)
45+
{
46+
if (propertyName.IsEmpty())
47+
return null;
48+
49+
var defaultKey = "";
50+
var keys = new Dictionary<string, string>
51+
{
52+
{ "Id", "Admin.Common.Entity.Fields.Id" },
53+
{ "LimitedToStores", "Admin.Common.Store.LimitedTo" },
54+
{ "DisplayOrder", "Common.DisplayOrder" },
55+
{ "Deleted", "Admin.Common.Deleted" },
56+
{ "CreatedOnUtc", "Common.CreatedOn" },
57+
{ "UpdatedOnUtc", "Common.UpdatedOn" },
58+
{ "HasDiscountsApplied", "Admin.Catalog.Products.Fields.HasDiscountsApplied" },
59+
{ "DefaultViewMode", "Admin.Configuration.Settings.Catalog.DefaultViewMode" },
60+
{ "StoreId", "Admin.Common.Store" }
61+
};
62+
63+
if (type == ImportEntityType.Product)
64+
{
65+
defaultKey = "Admin.Catalog.Products.Fields." + propertyName;
66+
67+
keys.Add("ParentGroupedProductId", "Admin.Catalog.Products.Fields.AssociatedToProductName");
68+
}
69+
else if (type == ImportEntityType.Category)
70+
{
71+
defaultKey = "Admin.Catalog.Categories.Fields." + propertyName;
72+
}
73+
else if (type == ImportEntityType.Customer)
74+
{
75+
defaultKey = "Admin.Customers.Customers.Fields." + propertyName;
76+
77+
keys.Add("PasswordFormatId", "Admin.Configuration.Settings.CustomerUser.DefaultPasswordFormat");
78+
keys.Add("LastIpAddress", "Admin.Customers.Customers.Fields.IPAddress");
79+
}
80+
else if (type == ImportEntityType.NewsLetterSubscription)
81+
{
82+
defaultKey = "Admin.Promotions.NewsLetterSubscriptions.Fields." + propertyName;
83+
}
84+
85+
var result = _localizationService.GetResource(keys.ContainsKey(propertyName) ? keys[propertyName] : defaultKey, 0, false, "", true);
86+
87+
if (result.IsEmpty())
88+
{
89+
if (defaultKey.EndsWith("Id"))
90+
result = _localizationService.GetResource(defaultKey.Substring(0, defaultKey.Length - 2), 0, false, "", true);
91+
else if (defaultKey.EndsWith("Utc"))
92+
result = _localizationService.GetResource(defaultKey.Substring(0, defaultKey.Length - 3), 0, false, "", true);
93+
}
94+
95+
if (result.IsEmpty())
96+
{
97+
Debug.WriteLine("Missing string resource mapping for {0}.{1}".FormatInvariant(type.ToString(), propertyName));
98+
return propertyName.SplitPascalCase();
99+
}
100+
101+
return result;
102+
}
103+
33104
public virtual ImportProfile InsertImportProfile(string fileName, string name, ImportEntityType entityType)
34105
{
35106
Guard.ArgumentNotEmpty(() => fileName);
@@ -136,5 +207,54 @@ public virtual ImportProfile GetImportProfileById(int id)
136207

137208
return profile;
138209
}
210+
211+
public virtual Dictionary<string, string> GetImportableEntityProperties(ImportEntityType entityType)
212+
{
213+
if (_entityProperties == null)
214+
{
215+
lock (_lock)
216+
{
217+
if (_entityProperties == null)
218+
{
219+
_entityProperties = new Dictionary<ImportEntityType, Dictionary<string, string>>();
220+
221+
var context = ((IObjectContextAdapter)_importProfileRepository.Context).ObjectContext;
222+
var container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
223+
224+
foreach (ImportEntityType type in Enum.GetValues(typeof(ImportEntityType)))
225+
{
226+
EntitySet entitySet = null;
227+
var dic = new Dictionary<string, string>();
228+
229+
try
230+
{
231+
if (type == ImportEntityType.Category)
232+
entitySet = container.GetEntitySetByName("Categories", true);
233+
else
234+
entitySet = container.GetEntitySetByName(type.ToString() + "s", true);
235+
}
236+
catch (Exception)
237+
{
238+
throw new SmartException("There is no entity set for ImportEntityType {0}. Note, the enum value must equal the entity name.".FormatInvariant(type.ToString()));
239+
}
240+
241+
foreach (var member in entitySet.ElementType.Members)
242+
{
243+
if (member.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty))
244+
{
245+
var localizedValue = GetLocalizedPropertyName(type, member.Name);
246+
247+
dic.Add(member.Name, localizedValue.NaIfEmpty());
248+
}
249+
}
250+
251+
_entityProperties.Add(type, dic);
252+
}
253+
}
254+
}
255+
}
256+
257+
return (_entityProperties.ContainsKey(entityType) ? _entityProperties[entityType] : null);
258+
}
139259
}
140260
}

src/Presentation/SmartStore.Web/Administration/Content/admin.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,9 @@ tr.adminSeparator hr {
888888
.progress-info {
889889
min-width: 260px;
890890
}
891+
select, input {
892+
margin-bottom: 0;
893+
}
891894
}
892895

893896
/* SERVER CONTROLS */

src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -51,44 +51,65 @@ private List<ColumnMappingItemModel> PrepareColumnMappingModels(ImportProfile pr
5151

5252
try
5353
{
54+
var files = profile.GetImportFiles();
55+
if (!files.Any())
56+
return models;
57+
58+
var filePath = files.First();
5459
var mapConverter = new ColumnMapConverter();
5560
var map = mapConverter.ConvertFrom<ColumnMap>(profile.ColumnMapping);
61+
var hasMappings = (map != null && map.Mappings.Any());
5662

57-
if (map != null && map.Mappings.Any())
58-
{
59-
models = map.Mappings
60-
.Select(x =>
61-
{
62-
var mapModel = new ColumnMappingItemModel
63-
{
64-
SourceColumn = x.Key,
65-
EntityProperty = x.Value.EntityProperty,
66-
DefaultValue = x.Value.DefaultValue
67-
};
68-
return mapModel;
69-
})
70-
.ToList();
71-
}
72-
else
63+
var destProperties = _importService.GetImportableEntityProperties(profile.EntityType);
64+
65+
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
7366
{
74-
var files = profile.GetImportFiles();
75-
if (files.Any())
67+
var dataTable = LightweightDataTable.FromFile(Path.GetFileName(filePath), stream, stream.Length, csvConfiguration, 0, 2);
68+
69+
foreach (var column in dataTable.Columns)
7670
{
77-
var filePath = files.First();
78-
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
71+
var sourceName = column.Name;
72+
if (sourceName.IsEmpty())
73+
continue;
74+
75+
var mapModel = new ColumnMappingItemModel();
76+
77+
var x1 = sourceName.IndexOf('[');
78+
var x2 = sourceName.IndexOf(']');
79+
80+
if (x1 != -1 && x2 != -1 && x2 > x1)
7981
{
80-
var dataTable = LightweightDataTable.FromFile(Path.GetFileName(filePath), stream, stream.Length, csvConfiguration, 0, 2);
82+
mapModel.SourceColumn = sourceName.Substring(0, x1);
83+
mapModel.SourceColumnIndex = sourceName.Substring(x1 + 1, x2 - x1 - 1);
84+
}
85+
else
86+
{
87+
mapModel.SourceColumn = sourceName;
88+
}
8189

82-
foreach (var column in dataTable.Columns)
90+
mapModel.AvailableEntityProperties = destProperties
91+
.Select(x =>
8392
{
84-
models.Add(new ColumnMappingItemModel
93+
return new SelectListItem
8594
{
86-
SourceColumn = column.Name
87-
});
95+
Value = "{0}¶{1}".FormatInvariant(sourceName, x.Key),
96+
Text = (x.Key.IsCaseInsensitiveEqual(x.Value) ? x.Value : "{0} ({1})".FormatInvariant(x.Key, x.Value)),
97+
Selected = (x.Key == sourceName)
98+
};
99+
})
100+
.ToList();
101+
102+
if (hasMappings)
103+
{
104+
var mapping = map.Mappings.FirstOrDefault(x => x.Key == sourceName);
105+
if (mapping.Value != null)
106+
{
107+
mapModel.EntityProperty = mapping.Value.EntityProperty;
108+
mapModel.DefaultValue = mapping.Value.DefaultValue;
88109
}
89-
90-
return models;
91110
}
111+
112+
models.Add(mapModel);
92113
}
93114
}
94115
}
@@ -122,6 +143,7 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile
122143
model.IsTaskEnabled = profile.ScheduleTask.Enabled;
123144
model.LogFileExists = System.IO.File.Exists(profile.GetImportLogPath());
124145
model.EntityTypeName = profile.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext);
146+
model.UnspecifiedString = T("Common.Unspecified");
125147

126148
model.ExistingFileNames = profile.GetImportFiles()
127149
.Select(x => Path.GetFileName(x))
@@ -134,12 +156,12 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile
134156
if (profile.FileType == ImportFileType.CSV)
135157
{
136158
var csvConverter = new CsvConfigurationConverter();
137-
csvConfiguration = csvConverter.ConvertFrom<CsvConfiguration>(profile.FileTypeConfiguration);
159+
csvConfiguration = csvConverter.ConvertFrom<CsvConfiguration>(profile.FileTypeConfiguration) ?? CsvConfiguration.ExcelFriendlyConfiguration;
138160

139-
model.CsvConfiguration = new CsvConfigurationModel(csvConfiguration ?? CsvConfiguration.ExcelFriendlyConfiguration);
161+
model.CsvConfiguration = new CsvConfigurationModel(csvConfiguration);
140162
}
141163

142-
model.ColumnMappings = PrepareColumnMappingModels(profile, csvConfiguration);
164+
model.ColumnMappings = PrepareColumnMappingModels(profile, csvConfiguration ?? CsvConfiguration.ExcelFriendlyConfiguration);
143165
}
144166
}
145167
else

src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public partial class ImportProfileModel : EntityModelBase
4545
public ScheduleTaskModel TaskModel { get; set; }
4646
public bool LogFileExists { get; set; }
4747
public string TempFileName { get; set; }
48+
public string UnspecifiedString { get; set; }
4849

4950
public CsvConfigurationModel CsvConfiguration { get; set; }
5051
public List<ColumnMappingItemModel> ColumnMappings { get; set; }
@@ -53,14 +54,13 @@ public partial class ImportProfileModel : EntityModelBase
5354

5455
public class ColumnMappingItemModel
5556
{
56-
[SmartResourceDisplayName("Admin.DataExchange.ColumnMapping.SourceColumn")]
5757
public string SourceColumn { get; set; }
5858

59-
[SmartResourceDisplayName("Admin.DataExchange.ColumnMapping.EntityProperty")]
59+
public string SourceColumnIndex { get; set; }
60+
6061
public string EntityProperty { get; set; }
6162
public List<SelectListItem> AvailableEntityProperties { get; set; }
6263

63-
[SmartResourceDisplayName("Admin.DataExchange.ColumnMapping.DefaultValue")]
6464
public string DefaultValue { get; set; }
6565
}
6666
}

0 commit comments

Comments
 (0)