diff --git a/OD.WebApplication/Context/ApplicationContextHelper.cs b/OD.WebApplication/Context/ApplicationContextHelper.cs new file mode 100644 index 0000000..1ff4dff --- /dev/null +++ b/OD.WebApplication/Context/ApplicationContextHelper.cs @@ -0,0 +1,106 @@ +namespace OD.WebApplication.Context +{ + public static class ApplicationContextHelper + { + public static string[] BusinessApplicationCategories = + { + "Metrónomos, afinadores, atriles, accesorios y complementos en general", + "Contrabajo y arcos", + "Cuerdas de violín, viola, violonchelo y contrabajo", + "Pastillas, afinadores, puentes, cordales, soportes y accesorios de cuerda", + "Viola y arcos de viola", + "Violín y arcos de violín", + "Violonchelo y arcos", + "Amplificadores, cabezales y bafles de guitarra y bajo", + "Bajos eléctricos y acústicos", + "Bandurrias", + "Cuerdas de instrumentos de cuerda punteada", + "Estuches, fundas y soportes para instrumentos de cuerda punteada", + "Guitarras acústicas amplificadas", + "Guitarras acústicas no amplificadas", + "Guitarras eléctricas", + "Guitarras españolas amplificadas", + "Guitarras españolas no amplificadas", + "Laúdes", + "Procesadores de guitarra, pedales, pedaleras, pastillas, mocrófonos de guitara y afinadores", + "Púas, puentes, cejuelas, tornillos, cordales, clavijas y accesorios en general de guitarra e instrumentos de cuerda p", + "Timples, ukeleles y otros instrumentos de cuerda punteada", + "Lámaras, focos, procesadores y mesas iluminación", + "Líquido y máquinas de humo", + "Software secuenciador, editor, procesador y grabador de sonido y sus complementos, aso como programas en general", + "Torres, bolas y complementos de iluminación", + "Armónicas, acordeones e instrumentos de lengüeta y sus complementos", + "Baterías acústicas y digitales", + "Cajones rumberos, claves, sonajas y otros instrumentos no incluidos en otra familia", + "Marimbas, xilófonos, metalófonos e instrumentos con escala afinada", + "Platos", + "Soportes, racks, fundas, banquetas, parches, membranas, baquetas y accesorios de percusión en general", + "Timbales, bombos, tambores, cajas panderetas y otros instrumentos de membrana", + "Estuches, fundas, banquetas y accesorios de piano", + "Pianos de Cola Nuevos con o sin módulo", + "Pianos de Cola Usados", + "Pianos Digitales con ritmos", + "Pianos Digitales sin ritmos", + "Pianos Verticales Nuevos con o sin módulo", + "Pianos Verticales Usados con o sin módulo", + "Libretas, block y papel pautado", + "Libros y partituras de canto", + "Libros y partituras de clarinete", + "Libros y partituras de contrabajo", + "Libros y partituras de fagot", + "Libros y partituras de flauta, dulzaina y gaita", + "Libros y partituras de guitarra y cuerda punteada", + "Libros y partituras de música de cámara", + "Libros y partituras de oboe", + "Libros y partituras de orquesta", + "Libros y partituras de percusión", + "Libros y partituras de piano", + "Libros y partituras de saxo", + "Libros y partituras de solfeo, armonía, Historia de la Música y textos en general", + "Libros y partituras de teclado y acordeón", + "Libros y partituras de trombón, tuba y bombardino", + "Libros y partituras de trompa", + "Libros y partituras de trompeta", + "Libros y partituras de viola", + "Libros y partituras de violín", + "Libros y partituras de violonchelo", + "Alquiler de piano, teclados y otros aparatos", + "Servicios de afinación, reparación y transporte de pianos", + "Servicios de reparación de bafles, etapas y sistemas de amplificación y procesado de sonido", + "Servicios de reparación de teclados e instrumentos digitales", + "Servicios de reparación y ajuste de guitarras", + "Servicios de reparación y ajuste de instrumentos de cuerda", + "Servicios de reparación y ajuste de instrumentos de viento", + "Bafles, altavoces y monitores normales o inalámbricos", + "Cables y conectores", + "Etapas de potencia", + "Grabadores de sonido y sus soportes", + "Mesas de mezcla, cabezales, equipos integrados, amplificadores (combos) multiuso y mezcladores", + "Micrófonos normales e inalámbricos", + "Procesadores de efectos, pachbay, puertas de ruido, divisores, ecualizadores y otros sistemas de procesado de sonido", + "Soportes de micro, columna y torres, racks, auriculares y accesorios en general de amplificación", + "Accesorios de informática en general", + "CD y DVD", + "Librerías de sonidos y midifiles", + "OTRO", + "Tarjetas de sonido e interfaces de ordenador con o sin software", + "Alimentadores, fundas, estuches, pedales, soportes y accesorios de teclado y módulos", + "Cajas de ritmos y módulos de DJ", + "Módulos arregladores con ritmos y secuenciadores", + "Módulos de sonido, sintetizadores y samplers", + "Módulos digitales de percusión", + "Órganos y teclados litúrgicos", + "Teclados con ritmos", + "Teclados Sintetizadores, Workstation y samplers", + "Teclados y mesas controladores", + "Cañas, boquillas, abrazaderas, estuches, fundas, soportes, atriles, accesorios y complementos de viento", + "Clarinete", + "Flauta travesera y de pico", + "Oboe, fliscorno, bombardino, fagot, helicón, tuba y otros instrumentos de viento", + "Saxo", + "Trombón", + "Trompa", + "Trompetas y cornetas" + }; + } +} \ No newline at end of file diff --git a/OD.WebApplication/Controllers/HomeController.cs b/OD.WebApplication/Controllers/HomeController.cs index 499a12b..abef349 100644 --- a/OD.WebApplication/Controllers/HomeController.cs +++ b/OD.WebApplication/Controllers/HomeController.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; +using System.Globalization; +using System.IO; +using AutoMapper; +using CsvHelper; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using OD.WebApplication.Models; @@ -12,30 +14,78 @@ namespace OD.WebApplication.Controllers public class HomeController : Controller { private readonly ILogger _logger; + private readonly IMapper _mapper; - public HomeController(ILogger logger) + public HomeController(ILogger logger, IMapper mapper) { _logger = logger; + _mapper = mapper; } public IActionResult Index() { return View(); } - - public IActionResult Upload() - { - return View(); - } - - public async Task UploadCSV() - { - return Ok(); - } - public IActionResult Privacy() + [HttpPost] + [ValidateAntiForgeryToken] + public IActionResult UploadCSV(HomeViewModel model) { - return View(); + try + { + if (ModelState.IsValid) + { + using var reader = new StreamReader(model.FileAttach.OpenReadStream()); + using var csvReader = new CsvReader(reader, CultureInfo.GetCultureInfo("es-ES")); + csvReader.Configuration.BadDataFound = null; + csvReader.Configuration.HeaderValidated = null; + csvReader.Configuration.MissingFieldFound = null; + csvReader.Configuration.Delimiter = ";"; + var originDto = new List(); + csvReader.Read(); + csvReader.ReadHeader(); + while (csvReader.Read()) + { + var record = new OriginDataModel + { + Id = csvReader.GetField("ID"), + Categories = csvReader.GetField("Categorías"), + Manufacturer = csvReader.GetField("Fabricante"), + PriceListingWithoutVAT = decimal.Parse(csvReader.GetField("Precio tiendas sin IVA")), + PricePublicWithoutVAT = decimal.Parse(csvReader.GetField("PVP sin IVA")), + PriceRecommendedWithoutVAT = decimal.Parse(csvReader.GetField("PVR sin IVA")), + Stock = decimal.Parse(csvReader.GetField("Stock")), + Volume = float.Parse(csvReader.GetField("Volumen")), + Name = csvReader.GetField("Nombre"), + LongDesc = csvReader.GetField("Descripción larga"), + ShortDesc = csvReader.GetField("Descripción breve"), + ImageUrls = csvReader.GetField("Imagen"), + Ean13 = csvReader.GetField("EAN13"), + Sda = csvReader.GetField("SDA") + }; + originDto.Add(record); + } + + var destinationDto = _mapper.Map, List>(originDto); + + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + using var csvWriter = new CsvWriter(sw, CultureInfo.GetCultureInfo("es-ES")); + csvWriter.Configuration.Delimiter = ","; + csvWriter.WriteRecords(destinationDto); + sw.Flush(); + ms.Position = 0; + return File(ms.ToArray(), "text/csv", "result.csv"); + + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Attempted to perform an invalid conversion"); + return new ErrorMessageResult(new ErrorResult { Exception = ex }); + } + + return Ok(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] diff --git a/OD.WebApplication/Models/DestinationDataModel.cs b/OD.WebApplication/Models/DestinationDataModel.cs new file mode 100644 index 0000000..9935241 --- /dev/null +++ b/OD.WebApplication/Models/DestinationDataModel.cs @@ -0,0 +1,28 @@ +using CsvHelper.Configuration.Attributes; + +namespace OD.WebApplication.Models +{ + public class DestinationDataModel + { + [Name("categ_id")] + public string Category { get; set; } + + [Name("brand")] + public string Brand { get; set; } + + [Name("name")] + public string Name { get; set; } + + [Name("alternate_code")] + public string AlternateCode { get; set; } + + [Name("list_price")] + public int StorePriceWithVAT { get; set; } + + [Name("online_price")] + public int OnlinePriceWithVAT { get; set; } + + [Name("online_value_price")] + public int OnlineValueWihtVAT { get; set; } + } +} \ No newline at end of file diff --git a/OD.WebApplication/Models/ErrorMessageResult.cs b/OD.WebApplication/Models/ErrorMessageResult.cs new file mode 100644 index 0000000..12a3f12 --- /dev/null +++ b/OD.WebApplication/Models/ErrorMessageResult.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection.Internal; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace OD.WebApplication.Models +{ + public class ErrorMessageResult : IActionResult + { + private readonly ErrorResult _result; + + public ErrorMessageResult(ErrorResult result) + { + _result = result; + } + + public async Task ExecuteResultAsync(ActionContext context) + { + var objectResult = new ObjectResult(_result.Exception) + { + StatusCode = StatusCodes.Status500InternalServerError + }; + + await objectResult.ExecuteResultAsync(context); + } + } +} \ No newline at end of file diff --git a/OD.WebApplication/Models/ErrorResult.cs b/OD.WebApplication/Models/ErrorResult.cs new file mode 100644 index 0000000..5fefa3a --- /dev/null +++ b/OD.WebApplication/Models/ErrorResult.cs @@ -0,0 +1,9 @@ +using System; + +namespace OD.WebApplication.Models +{ + public class ErrorResult + { + public Exception Exception { get; set; } + } +} \ No newline at end of file diff --git a/OD.WebApplication/Models/HomeViewModel.cs b/OD.WebApplication/Models/HomeViewModel.cs new file mode 100644 index 0000000..7bf6143 --- /dev/null +++ b/OD.WebApplication/Models/HomeViewModel.cs @@ -0,0 +1,11 @@ +using System.IO; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.FileProviders; + +namespace OD.WebApplication.Models +{ + public class HomeViewModel + { + public IFormFile FileAttach { get; set; } + } +} \ No newline at end of file diff --git a/OD.WebApplication/Models/OriginDataModel.cs b/OD.WebApplication/Models/OriginDataModel.cs new file mode 100644 index 0000000..83d7129 --- /dev/null +++ b/OD.WebApplication/Models/OriginDataModel.cs @@ -0,0 +1,49 @@ +using CsvHelper.Configuration.Attributes; + +namespace OD.WebApplication.Models +{ + public class OriginDataModel + { + [Name("ID")] + public string Id { get; set; } + + [Name("Categorías")] + public string Categories { get; set; } + + [Name("Fabricante")] + public string Manufacturer { get; set; } + + [Name("Precio tiendas sin IVA")] + public decimal PriceListingWithoutVAT { get; set; } + + [Name("PVP sin IVA")] + public decimal PricePublicWithoutVAT { get; set; } + + [Name("PVR sin IVA")] + public decimal PriceRecommendedWithoutVAT { get; set; } + + [Name("Stock")] + public decimal Stock { get; set; } + + [Name("Volumen")] + public float Volume { get; set; } + + [Name("Nombre")] + public string Name { get; set; } + + [Name("Descripción larga")] + public string LongDesc { get; set; } + + [Name("Descripción breve")] + public string ShortDesc { get; set; } + + [Name("Imagen")] + public string ImageUrls { get; set; } + + [Name("EAN13")] + public string Ean13 { get; set; } + + [Name("SDA")] + public string Sda { get; set; } + } +} \ No newline at end of file diff --git a/OD.WebApplication/OD.WebApplication.csproj b/OD.WebApplication/OD.WebApplication.csproj index 9b84ef3..263e254 100644 --- a/OD.WebApplication/OD.WebApplication.csproj +++ b/OD.WebApplication/OD.WebApplication.csproj @@ -5,10 +5,9 @@ - - true - PreserveNewest - + + + diff --git a/OD.WebApplication/Profiles/OriginToDestinationProfile.cs b/OD.WebApplication/Profiles/OriginToDestinationProfile.cs new file mode 100644 index 0000000..dbba64e --- /dev/null +++ b/OD.WebApplication/Profiles/OriginToDestinationProfile.cs @@ -0,0 +1,53 @@ +using System.Linq; +using AutoMapper; +using AutoMapper.Internal; +using Microsoft.AspNetCore.Builder; +using OD.WebApplication.Context; +using OD.WebApplication.Models; + +namespace OD.WebApplication.Profiles +{ + public class OriginToDestinationProfile : Profile + { + public OriginToDestinationProfile() + { + CreateMap() + .ForMember(dest => dest.Category, opt => opt.MapFrom(src=> ResolveCategory(src))) + .ForMember(dest => dest.Brand, opt => opt.MapFrom(src => src.Manufacturer)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) + .ForMember(dest => dest.AlternateCode, opt => opt.MapFrom(src => ResolveAlternateCode(src))) + .ForMember(dest => dest.StorePriceWithVAT, opt => opt.MapFrom(src => ResolveAddVAT(src.PriceListingWithoutVAT))) + .ForMember(dest => dest.OnlinePriceWithVAT, opt => opt.MapFrom(src => ResolveAddVAT(src.PricePublicWithoutVAT))) + .ForMember(dest => dest.OnlineValueWihtVAT, opt => opt.MapFrom(src => ResolveAddVAT(src.PriceRecommendedWithoutVAT))); + } + + private static string ResolveCategory(OriginDataModel src) + { + var categories = src.Categories.Split(',').ToList(); + categories = categories.Select(del => del.Trim()).ToList(); + foreach (var category in categories) + { + if (ApplicationContextHelper.BusinessApplicationCategories.Any(x => + x.Contains(category) || category.Contains(x))) + { + return ApplicationContextHelper.BusinessApplicationCategories.First(x => + x.Contains(category) || category.Contains(x)); + } + } + + return "OTRO"; + } + + private static string ResolveAlternateCode(OriginDataModel src) + { + return src.ShortDesc.ToUpper().Replace(" ", ""); + } + + private static int ResolveAddVAT(decimal input) + { + const decimal vat = 0.21m; + var result = decimal.Round(input * vat + input); + return decimal.ToInt32(result); + } + } +} \ No newline at end of file diff --git a/OD.WebApplication/Startup.cs b/OD.WebApplication/Startup.cs index 2714dae..7824ce1 100644 --- a/OD.WebApplication/Startup.cs +++ b/OD.WebApplication/Startup.cs @@ -8,6 +8,9 @@ using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using AutoMapper; +using OD.WebApplication.Context; +using OD.WebApplication.Profiles; namespace OD.WebApplication { @@ -24,6 +27,7 @@ namespace OD.WebApplication public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); + services.AddAutoMapper(typeof(Startup)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/OD.WebApplication/Views/Home/Index.cshtml b/OD.WebApplication/Views/Home/Index.cshtml index 378bcad..08b6ffb 100644 --- a/OD.WebApplication/Views/Home/Index.cshtml +++ b/OD.WebApplication/Views/Home/Index.cshtml @@ -1,8 +1,21 @@ -@{ - ViewData["Title"] = "Home Page"; +@using Microsoft.AspNetCore.Mvc.Rendering +@model HomeViewModel +@{ + ViewData["Title"] = "Upload page"; }
-

Welcome

-

Learn about building Web apps with ASP.NET Core.

+

Escoge el CSV a parsear

+
+ @using (Html.BeginForm("UploadCSV", "Home", FormMethod.Post, new {id = "loginForm", enctype = "multipart/form-data"})) + { + @Html.AntiForgeryToken() +
+ + + +
+ + } +
\ No newline at end of file diff --git a/OD.WebApplication/Views/Home/Privacy.cshtml b/OD.WebApplication/Views/Home/Privacy.cshtml deleted file mode 100644 index e6eef70..0000000 --- a/OD.WebApplication/Views/Home/Privacy.cshtml +++ /dev/null @@ -1,6 +0,0 @@ -@{ - ViewData["Title"] = "Privacy Policy"; -} -

@ViewData["Title"]

- -

Use this page to detail your site's privacy policy.

\ No newline at end of file diff --git a/OD.WebApplication/Views/Home/Upload.cshtml b/OD.WebApplication/Views/Home/Upload.cshtml deleted file mode 100644 index 3891903..0000000 --- a/OD.WebApplication/Views/Home/Upload.cshtml +++ /dev/null @@ -1,18 +0,0 @@ -@using Microsoft.AspNetCore.Mvc.Rendering -@{ - ViewData["Title"] = "Upload page"; -} - -
-

Escoge el CSV a parsear

-
- @using (Html.BeginForm("UploadCSV", "Home", FormMethod.Post, new {id = "loginForm"})) - { - @Html.AntiForgeryToken() -
- Do your magic pablo -
- - } - -
diff --git a/OD.WebApplication/Views/Shared/_Layout.cshtml b/OD.WebApplication/Views/Shared/_Layout.cshtml index 2924b8e..823359a 100644 --- a/OD.WebApplication/Views/Shared/_Layout.cshtml +++ b/OD.WebApplication/Views/Shared/_Layout.cshtml @@ -21,12 +21,6 @@ - - @@ -40,7 +34,7 @@
- © 2020 - OD.WebApplication - Privacy + © 2020 - SI