Merge pull request #8 from schebotar/dev

Dev
This commit is contained in:
Serghei Cebotari 2021-12-24 17:43:04 +03:00 committed by GitHub
commit 0525ec1b42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1040 additions and 191 deletions

View File

@ -5,11 +5,11 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Rehau.Sku.Assist")]
[assembly: AssemblyTitle("RehauSku.Assist")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Rehau.Sku.Assist")]
[assembly: AssemblyProduct("RehauSku.Assist")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View File

@ -1,17 +1,23 @@
# REHAU SKU плагин для MS Excel
## Назначение
Служит для помощи в обработке клиентских заявок путем поиска продукции REHAU по запросу, выраженному в свободной форме.
Помощь в обработке клиентских заявок путем поиска продукции REHAU по запросу, выраженному в свободной форме, и в работе с прайс-листом BS REHAU
## Принцип работы
Плагин делает поисковый запрос в интернет-магазине REHAU, выполняет парсинг полученного ответа и выдает результат
Плагин делает поисковый запрос в интернет-магазин REHAU, выполняет парсинг полученного ответа и выдает результат
## Реализованные функции
- Отображение наименования найденного по запросу продукта с помощью формулы `=RAUNAME()`
- Отображение артикула с помощью формулы `=RAUSKU()`
- Отображение цены найденного продукта с помощью формулы `=RAUPRICE()`
- Формулы для поиска информации
- Отображение наименования с помощью `=RAUNAME()`
- Отображение артикула с помощью `=RAUSKU()`
- Отображение цены с помощью формулы `=RAUPRICE()`
- Экспорт массива ячеек вида "Артикул - Количество" в прайс-лист
- Объединение нескольких прайс-листов в один файл
*Для работы функций "Экспорт" и "Объединение" требуется указать путь к файлу пустого прайс-листа REHAU*
## Работа без установки
1. Запустить файл `Rehau.Sku.Assist-AddIn-packed.xll` или `Rehau.Sku.Assist-AddIn64-packed.xll` в зависимости от архитектуры приложения
1. Запустить файл `RehauSku.Assist-AddIn-packed.xll` или `RehauSku.Assist-AddIn64-packed.xll` в зависимости от архитектуры приложения
2. Включить надстройку для данного сеанса в извещении системы безопасности
## Постоянная установка
@ -24,5 +30,9 @@
Файл -> Параметры -> Надстройки ->
Управление: Надстройки Excel -> Перейти... -> Обзор
Выбрать и включить файл плагина.
Выбрать и включить файл плагина
## Использованные библиотеки
- [ExcelDna](https://github.com/Excel-DNA/ExcelDna)
- [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
- [AngleSharp](https://github.com/AngleSharp/AngleSharp)

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<DnaLibrary Name="Rehau.Sku.Assist Add-In" RuntimeVersion="v4.0" xmlns="http://schemas.excel-dna.net/addin/2020/07/dnalibrary">
<ExternalLibrary Path="Rehau.Sku.Assist.dll" ExplicitRegistration="true" LoadFromBytes="true" Pack="true" IncludePdb="false" />
<Reference Path="AngleSharp.dll" Pack="true" />
<Reference Path="ExcelDna.Registration.dll" Pack="true" />
<Reference Path="Newtonsoft.Json.dll" Pack="true" />
<Reference Path="System.Buffers.dll" Pack="true" />
<Reference Path="System.Memory.dll" Pack="true" />
<Reference Path="System.Numerics.Vectors.dll" Pack="true" />
<Reference Path="System.Runtime.CompilerServices.Unsafe.dll" Pack="true" />
<Reference Path="System.Text.Encoding.CodePages.dll" Pack="true" />
<!--
The RuntimeVersion attribute above allows only the following setting:
* RuntimeVersion="v4.0" - for .NET 4.5 or higher
You can have IntelliSense (autocomplete) and validation for this file.
See https://github.com/Excel-DNA/ExcelDna/tree/master/Distribution/XmlSchemas/
Additional referenced assemblies can be specified by adding 'Reference' tags.
These libraries will not be examined and registered with Excel as add-in libraries,
but will be packed into the -packed.xll file and loaded at runtime as needed.
For example:
<Reference Path="Another.Library.dll" Pack="true" />
Excel-DNA also allows the XML for ribbon UI extensions to be specified in the .dna file.
See the main Excel-DNA site at https://excel-dna.net for downloads of the full distribution.
-->
</DnaLibrary>

13
RehauSku.Assist-AddIn.dna Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<DnaLibrary Name="RehauSku.Assist Add-In" RuntimeVersion="v4.0" xmlns="http://schemas.excel-dna.net/addin/2020/07/dnalibrary">
<ExternalLibrary Path="RehauSku.Assist.dll" ExplicitRegistration="true" LoadFromBytes="true" Pack="true" IncludePdb="false" />
<Reference Path="AngleSharp.dll" Pack="true" />
<Reference Path="ExcelDna.Registration.dll" Pack="true" />
<Reference Path="Newtonsoft.Json.dll" Pack="true" />
<Reference Path="System.Buffers.dll" Pack="true" />
<Reference Path="System.Memory.dll" Pack="true" />
<Reference Path="System.Numerics.Vectors.dll" Pack="true" />
<Reference Path="System.Runtime.CompilerServices.Unsafe.dll" Pack="true" />
<Reference Path="System.Text.Encoding.CodePages.dll" Pack="true" />
<Reference Path="ExcelDna.IntelliSense.dll" Pack="true" />
</DnaLibrary>

View File

@ -7,8 +7,8 @@
<ProjectGuid>{18A2FF67-0E46-4A86-B872-29F2B3F23ADF}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Rehau.Sku.Assist</RootNamespace>
<AssemblyName>Rehau.Sku.Assist</AssemblyName>
<RootNamespace>RehauSku.Assist</RootNamespace>
<AssemblyName>RehauSku.Assist</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
@ -42,13 +42,28 @@
<HintPath>packages\ExcelDna.Integration.1.5.0\lib\net452\ExcelDna.Integration.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ExcelDna.IntelliSense, Version=1.4.4.0, Culture=neutral, PublicKeyToken=f225e9659857edbe, processorArchitecture=MSIL">
<HintPath>packages\ExcelDna.IntelliSense.1.5.0\lib\net452\ExcelDna.IntelliSense.dll</HintPath>
</Reference>
<Reference Include="ExcelDna.Registration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=f225e9659857edbe, processorArchitecture=MSIL">
<HintPath>packages\ExcelDna.Registration.1.5.0\lib\net452\ExcelDna.Registration.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Office.Interop.Excel, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
<HintPath>packages\ExcelDna.Interop.14.0.1\lib\Microsoft.Office.Interop.Excel.dll</HintPath>
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="Microsoft.Vbe.Interop, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
<HintPath>packages\ExcelDna.Interop.14.0.1\lib\Microsoft.Vbe.Interop.dll</HintPath>
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Office, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
<HintPath>packages\ExcelDna.Interop.14.0.1\lib\Office.dll</HintPath>
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
@ -56,6 +71,7 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Data.OracleClient" />
<Reference Include="System.Drawing" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
</Reference>
@ -74,6 +90,7 @@
<HintPath>packages\System.Text.Encoding.CodePages.6.0.0\lib\net461\System.Text.Encoding.CodePages.dll</HintPath>
</Reference>
<Reference Include="System.Transactions" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -83,20 +100,38 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Source\Forms\Dialog.cs" />
<Compile Include="Source\AddIn\RegistryUtil.cs" />
<Compile Include="Source\AddIn\MemoryCacheUtil.cs" />
<Compile Include="Source\Assistant\ParseUtil.cs" />
<Compile Include="Source\Assistant\RequestModifier.cs" />
<Compile Include="Source\Assistant\SkuExtensions.cs" />
<Compile Include="Source\PriceListTools\MergeTool.cs" />
<Compile Include="Source\PriceListTools\PriceList.cs" />
<Compile Include="Source\PriceListTools\PriceListUtil.cs" />
<Compile Include="Source\Ribbon\RibbonController.cs" />
<Compile Include="Source\Assistant\HttpClientUtil.cs" />
<Compile Include="Source\Assistant\StoreResponse.cs" />
<Compile Include="Source\ExcelDNA\AddIn.cs" />
<Compile Include="Source\PriceListTools\ExportTool.cs" />
<Compile Include="Source\AddIn\AddIn.cs" />
<Compile Include="Source\Assistant\IProduct.cs" />
<Compile Include="Source\ExcelDNA\Functions.cs" />
<Compile Include="Source\AddIn\Functions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Source\Assistant\SkuAssist.cs" />
<Compile Include="Source\Forms\SettingsForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Source\Forms\SettingsForm.Designer.cs">
<DependentUpon>SettingsForm.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="Rehau.Sku.Assist-AddIn.dna" />
<None Include="RehauSku.Assist-AddIn.dna" />
<None Include="packages.config" />
<None Include="Properties\ExcelDna.Build.props" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="packages\ExcelDna.AddIn.1.5.0\build\ExcelDna.AddIn.targets" Condition="Exists('packages\ExcelDna.AddIn.1.5.0\build\ExcelDna.AddIn.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31829.152
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rehau.Sku.Assist", "Rehau.Sku.Assist.csproj", "{18A2FF67-0E46-4A86-B872-29F2B3F23ADF}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RehauSku.Assist", "RehauSku.Assist.csproj", "{18A2FF67-0E46-4A86-B872-29F2B3F23ADF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -1,21 +1,40 @@
using ExcelDna.Integration;
using ExcelDna.IntelliSense;
using ExcelDna.Registration;
using System.Net.Http;
using System.Runtime.Caching;
namespace Rehau.Sku.Assist
namespace RehauSku
{
public enum ResponseOrder
{
Default,
Relevance,
Name,
Price,
Series
}
public class AddIn : IExcelAddIn
{
public static HttpClient httpClient;
public static MemoryCache memoryCache;
public void AutoOpen()
{
RegisterFunctions();
httpClient = new HttpClient();
memoryCache = new MemoryCache("RehauSku");
RegisterFunctions();
IntelliSenseServer.Install();
RegistryUtil.Initialize();
}
public void AutoClose()
{
IntelliSenseServer.Uninstall();
RegistryUtil.Uninitialize();
memoryCache.Dispose();
}
void RegisterFunctions()

56
Source/AddIn/Functions.cs Normal file
View File

@ -0,0 +1,56 @@
using ExcelDna.Integration;
using RehauSku.Assistant;
namespace RehauSku
{
public class Functions
{
[ExcelFunction(description: "Получение названия первого продукта в поиске")]
public static object RAUNAME([ExcelArgument(Name = "\"Запрос\"", Description = "в свободной форме или ячейка с запросом")] string request)
=> MakeRequest(request, ProductField.Name);
[ExcelFunction(Description = "Получение артикула первого продукта в поиске")]
public static object RAUSKU([ExcelArgument(Name = "\"Запрос\"", Description = "в свободной форме или ячейка с запросом")] string request)
=> MakeRequest(request, ProductField.Id);
[ExcelFunction(Description = "Получение цены первого продукта в поиске")]
public static object RAUPRICE([ExcelArgument(Name = "\"Запрос\"", Description = "в свободной форме или ячейка с запросом")] string request)
=> MakeRequest(request, ProductField.Price);
private static object MakeRequest(string request, ProductField field)
{
object result;
if (request.IsCached())
result = request.GetFromCache();
else
{
result = ExcelAsyncUtil.Run("Request", request, delegate
{
return request.RequestAndCache().GetAwaiter().GetResult();
});
}
if (result == null)
return "Не найдено :(";
if (result.Equals(ExcelError.ExcelErrorNA))
return "Загрузка...";
IProduct product = result as IProduct;
switch (field)
{
case ProductField.Name:
return product.Name;
case ProductField.Id:
return product.Id;
case ProductField.Price:
return double.Parse(product.Price, System.Globalization.CultureInfo.InvariantCulture);
default:
return null;
}
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Runtime.Caching;
using System.Threading.Tasks;
using RehauSku.Assistant;
namespace RehauSku
{
static class MemoryCacheUtil
{
public static bool IsCached(this string request)
{
return AddIn.memoryCache.Contains(request);
}
public static IProduct GetFromCache(this string request)
{
return AddIn.memoryCache[request] as IProduct;
}
public static async Task<IProduct> RequestAndCache(this string request)
{
IProduct product = await SkuAssist.GetProductAsync(request);
if (product == null)
return null;
AddIn.memoryCache.Add(request, product, DateTime.Now.AddMinutes(10));
return product;
}
public static void ClearCache()
{
AddIn.memoryCache.Dispose();
AddIn.memoryCache = new MemoryCache("RehauSku");
}
}
}

View File

@ -0,0 +1,76 @@
using Microsoft.Win32;
using System.IO;
using RehauSku.Forms;
using System.Windows.Forms;
namespace RehauSku
{
static class RegistryUtil
{
private static string _priceListPath;
private static int? _storeResponseOrder;
private static RegistryKey _RootKey { get; set; }
public static void Initialize()
{
_RootKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\REHAU\SkuAssist");
_priceListPath = _RootKey.GetValue("PriceListPath") as string;
_storeResponseOrder = _RootKey.GetValue("StoreResponseOrder") as int?;
}
public static void Uninitialize()
{
_RootKey.Close();
}
public static bool IsPriceListPathEmpty()
{
return string.IsNullOrEmpty(_priceListPath);
}
public static string PriceListPath
{
get
{
if (IsPriceListPathEmpty() || !File.Exists(_priceListPath))
{
MessageBox.Show("Прайс-лист отсутствует или неверный файл прайс-листа", "Укажите файл прайс-листа", MessageBoxButtons.OK, MessageBoxIcon.Warning);
string fileName = Dialog.GetFilePath();
_priceListPath = fileName;
_RootKey.SetValue("PriceListPath", fileName);
return _priceListPath;
}
else
{
return _priceListPath;
}
}
set
{
_priceListPath = value;
_RootKey.SetValue("PriceListPath", value);
}
}
public static ResponseOrder StoreResponseOrder
{
get
{
if (_storeResponseOrder == null)
{
_RootKey.SetValue("StoreResponseOrder", (int)ResponseOrder.Default);
_storeResponseOrder = (int)ResponseOrder.Default;
return (ResponseOrder)_storeResponseOrder.Value;
}
else
{
return (ResponseOrder)_storeResponseOrder.Value;
}
}
}
}
}

View File

@ -1,19 +1,18 @@
using AngleSharp;
using AngleSharp.Dom;
using System;
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text;
namespace Rehau.Sku.Assist
namespace RehauSku.Assistant
{
static class HttpClientUtil
{
private static HttpClient _httpClient = AddIn.httpClient;
public async static Task<string> GetContentByUriAsync(Uri uri)
public async static Task<string> GetContentByRequest(string request)
{
Uri uri = request.ConvertToUri();
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls11 |
@ -22,22 +21,14 @@ namespace Rehau.Sku.Assist
return await _httpClient.GetStringAsync(uri);
}
public async static Task<IDocument> ContentToDocAsync(Task<string> content)
{
IConfiguration config = Configuration.Default;
IBrowsingContext context = BrowsingContext.New(config);
return await context.OpenAsync(req => req.Content(content.Result));
}
public static Uri ConvertToUri(this string request, ResponseOrder order)
private static Uri ConvertToUri(this string request)
{
UriBuilder baseUri = new UriBuilder("https", "shop-rehau.ru");
baseUri.Path = "/catalogsearch/result/index/";
string cleanedRequest = request._CleanRequest();
string cleanedRequest = request.CleanRequest();
switch (order)
switch (RegistryUtil.StoreResponseOrder)
{
case ResponseOrder.Relevance:
baseUri.Query = "dir=asc&order=relevance&q=" + cleanedRequest;
@ -51,25 +42,12 @@ namespace Rehau.Sku.Assist
case ResponseOrder.Series:
baseUri.Query = "dir=asc&order=sch_product_series&q=" + cleanedRequest;
break;
case ResponseOrder.NoSettings:
default:
baseUri.Query = "q=" + cleanedRequest;
break;
default:
throw new ArgumentException();
}
return baseUri.Uri;
}
private static string _CleanRequest(this string input)
{
return new StringBuilder(input)
.Replace("+", " plus ")
.Replace("РХ", "")
.Replace("º", " ")
.Replace(".", " ")
.Replace("Ø", " ")
.ToString();
}
}
}

View File

@ -1,4 +1,4 @@
namespace Rehau.Sku.Assist
namespace RehauSku.Assistant
{
interface IProduct
{

View File

@ -0,0 +1,44 @@
using AngleSharp;
using AngleSharp.Dom;
using Newtonsoft.Json;
using System.Linq;
using System.Threading.Tasks;
namespace RehauSku.Assistant
{
static class ParseUtil
{
public async static Task<IDocument> ContentToDocAsync(string content)
{
IConfiguration config = Configuration.Default;
IBrowsingContext context = BrowsingContext.New(config);
return await context.OpenAsync(req => req.Content(content));
}
public static IProduct GetProduct(IDocument document)
{
string script = document
.Scripts
.Where(s => s.InnerHtml.Contains("dataLayer"))
.FirstOrDefault()
.InnerHtml;
string json = script
.Substring(script.IndexOf("push(") + 5)
.TrimEnd(new[] { ')', ';', '\n', ' ' });
if (!json.Contains("impressions"))
return null;
StoreResponce storeResponse = JsonConvert.DeserializeObject<StoreResponce>(json);
IProduct product = storeResponse
.Ecommerce
.Impressions
.Where(p => p.Id.IsRehauSku())
.FirstOrDefault();
return product;
}
}
}

View File

@ -0,0 +1,67 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace RehauSku.Assistant
{
public static class RequestModifier
{
public static string CleanRequest(this string input)
{
string replace = new StringBuilder(input)
.Replace("+", " plus ")
.Replace("РХ", "")
.Replace("º", " ")
.Replace(".", " ")
.Replace("Ø", " ")
.ToString();
return replace._tPieceNormalize();
}
private static string _tPieceNormalize(this string line)
{
Regex regex = new Regex(@"\d{2}.\d{2}.\d{2}");
if (!regex.IsMatch(line))
return line;
string match = regex.Match(line).Value;
int side = int.Parse($"{match[3]}{match[4]}");
int[] endFaces = new int[]
{
int.Parse($"{match[0]}{match[1]}"),
int.Parse($"{match[6]}{match[7]}")
};
if (new[] { endFaces[0], endFaces[1], side }.Any(x => x == 45 || x == 90 || x == 87))
return line;
List<string> additions = new List<string>();
if (endFaces.All(x => x < side))
additions.Add("увеличенный боковой");
else
{
if (new[] { endFaces[0], endFaces[1], side }.Distinct().Count() == 1)
additions.Add("равнопроходной");
else
additions.Add("уменьшенный");
if (endFaces.Any(x => x > side))
additions.Add("боковой");
if (endFaces[0] != endFaces[1])
additions.Add("торцевой");
}
string piece = $" {endFaces.Max()}-{side}-{endFaces.Min()} ";
string modifiedMatch = string.Join(" ", additions) + piece;
return line.Replace(match, modifiedMatch);
}
}
}

View File

@ -1,24 +1,7 @@
using AngleSharp.Dom;
using ExcelDna.Integration;
using Newtonsoft.Json;
using System;
using System.Globalization;
using System.Linq;
using System.Runtime.Caching;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace Rehau.Sku.Assist
namespace RehauSku.Assistant
{
public enum ResponseOrder
{
NoSettings,
Relevance,
Name,
Price,
Series
}
public enum ProductField
{
Name,
@ -28,79 +11,12 @@ namespace Rehau.Sku.Assist
static class SkuAssist
{
public static async Task<IProduct> GetProduct(string request)
public static async Task<IProduct> GetProductAsync(string request)
{
Uri uri = request.ConvertToUri(ResponseOrder.NoSettings);
var content = await HttpClientUtil.GetContentByRequest(request);
var document = await ParseUtil.ContentToDocAsync(content);
Task<string> contentTask = Task.Run(() => HttpClientUtil.GetContentByUriAsync(uri));
Task<IDocument> documentTask = await contentTask.ContinueWith(content => HttpClientUtil.ContentToDocAsync(content));
return GetProduct(documentTask.Result);
}
public static IProduct GetProduct(IDocument d)
{
string script = d.Scripts
.Where(s => s.InnerHtml.Contains("dataLayer"))
.First()
.InnerHtml;
string json = script
.Substring(script.IndexOf("push(") + 5)
.TrimEnd(new[] { ')', ';', '\n', ' ' });
if (!json.Contains("impressions"))
return null;
StoreResponce storeResponse = JsonConvert.DeserializeObject<StoreResponce>(json);
IProduct product = storeResponse
.Ecommerce
.Impressions
.Where(p => Regex.IsMatch(p.Id, @"\d{11}", RegexOptions.None))
.FirstOrDefault();
return product;
}
public static object GetProduct(string request, ProductField field)
{
IProduct product;
if (MemoryCache.Default.Contains(request))
{
product = MemoryCache.Default[request] as IProduct;
}
else
{
object result = ExcelAsyncUtil.Run("RauName", new[] { request },
delegate
{
Task<IProduct> p = Task.Run(() => GetProduct(request));
return p.Result;
});
if (result == null)
return "Не найдено :(";
if (result.Equals(ExcelError.ExcelErrorNA))
return "Загрузка...";
product = result as IProduct;
MemoryCache.Default.Add(request, product, DateTime.Now.AddMinutes(10));
}
switch (field)
{
case ProductField.Name:
return product.Name;
case ProductField.Id:
return product.Id;
case ProductField.Price:
return double.Parse((string)product.Price, CultureInfo.InvariantCulture);
default:
return ExcelError.ExcelErrorValue;
}
return ParseUtil.GetProduct(document);
}
}
}

View File

@ -0,0 +1,12 @@
using System.Text.RegularExpressions;
namespace RehauSku.Assistant
{
static class SkuExtensions
{
public static bool IsRehauSku(this string line)
{
return Regex.IsMatch(line, @"^[1]\d{6}[1]\d{3}$");
}
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace Rehau.Sku.Assist
namespace RehauSku.Assistant
{
public class StoreResponce
{

View File

@ -1,19 +0,0 @@
using ExcelDna.Integration;
namespace Rehau.Sku.Assist
{
public class Functions
{
[ExcelFunction]
public static object RAUNAME(string request)
=> SkuAssist.GetProduct(request, ProductField.Name);
[ExcelFunction]
public static object RAUSKU(string request)
=> SkuAssist.GetProduct(request, ProductField.Id);
[ExcelFunction]
public static object RAUPRICE(string request)
=> SkuAssist.GetProduct(request, ProductField.Price);
}
}

46
Source/Forms/Dialog.cs Normal file
View File

@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Windows.Forms;
namespace RehauSku.Forms
{
static class Dialog
{
public static string GetFilePath()
{
string filePath = string.Empty;
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "Файлы Excel (*.xls;*.xlsx;*.xlsm)|*.xls;*.xlsx;*.xlsm";
if (dialog.ShowDialog() == DialogResult.OK)
{
filePath = dialog.FileName;
}
}
return filePath;
}
public static string[] GetMultiplyFiles()
{
List<string> fileNames = new List<string>();
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "Файлы Excel (*.xls;*.xlsx;*.xlsm)|*.xls;*.xlsx;*.xlsm";
dialog.Multiselect = true;
if (dialog.ShowDialog() == DialogResult.OK)
{
foreach (string file in dialog.FileNames)
{
fileNames.Add(file);
}
}
}
return fileNames.ToArray();
}
}
}

46
Source/Forms/SettingsForm.Designer.cs generated Normal file
View File

@ -0,0 +1,46 @@
namespace RehauSku.Forms
{
partial class SettingsForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// SettingsForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Name = "SettingsForm";
this.Text = "SettingsForm";
this.ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RehauSku.Forms
{
public partial class SettingsForm : Form
{
public SettingsForm()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,115 @@
using ExcelDna.Integration;
using Microsoft.Office.Interop.Excel;
using RehauSku.Assistant;
using System;
using System.Collections.Generic;
namespace RehauSku.PriceListTools
{
class ExportTool : IDisposable
{
private Application ExcelApp;
private Dictionary<string, double> SkuAmount { get; set; }
private Range Selection { get; set; }
public ExportTool()
{
this.ExcelApp = (Application)ExcelDnaUtil.Application;
Selection = ExcelApp.Selection;
if (IsRangeValid())
_FillSkuAmountDict();
}
public bool IsRangeValid()
{
return Selection != null &&
Selection.Columns.Count == 2;
}
private void _FillSkuAmountDict()
{
object[,] cells = Selection.Value2;
SkuAmount = new Dictionary<string, double>();
int rowsCount = Selection.Rows.Count;
for (int row = 1; row <= rowsCount; row++)
{
if (cells[row, 1] == null || cells[row, 2] == null)
continue;
string sku = null;
double? amount = null;
for (int column = 1; column <= 2; column++)
{
object current = cells[row, column];
if (current.ToString().IsRehauSku())
{
sku = current.ToString();
}
else if (current.GetType() == typeof(string)
&& double.TryParse(current.ToString(), out _))
{
amount = double.Parse((string)current);
}
else if (current.GetType() == typeof(double))
{
amount = (double)current;
}
}
if (sku == null || amount == null)
continue;
if (SkuAmount.ContainsKey(sku))
SkuAmount[sku] += amount.Value;
else
SkuAmount.Add(sku, amount.Value);
}
}
public void ExportToNewFile()
{
if (SkuAmount.Count < 1)
{
return;
}
string exportFile = PriceListUtil.CreateNewExportFile();
Workbook wb = ExcelApp.Workbooks.Open(exportFile);
try
{
PriceList priceList = new PriceList(wb);
priceList.Fill(SkuAmount);
}
catch(Exception ex)
{
System.Windows.Forms.MessageBox.Show
($"{RegistryUtil.PriceListPath} не является файлом прайслиста \n\n {ex.Message}",
"Неверный файл прайс-листа!",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
wb.Close();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
}
}

View File

@ -0,0 +1,88 @@
using ExcelDna.Integration;
using Microsoft.Office.Interop.Excel;
using System;
using System.Collections.Generic;
namespace RehauSku.PriceListTools
{
class MergeTool : IDisposable
{
private Application ExcelApp;
private Dictionary<string, double> SkuAmount { get; set; }
public MergeTool()
{
this.ExcelApp = (Application)ExcelDnaUtil.Application;
this.SkuAmount = new Dictionary<string, double>();
}
public void AddSkuAmountToDict(string[] files)
{
ExcelApp.ScreenUpdating = false;
foreach (string file in files)
{
Workbook wb = ExcelApp.Workbooks.Open(file);
try
{
PriceList priceList = new PriceList(wb);
SkuAmount.AddValues(priceList);
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show
( $"{wb.Name} не является файлом прайслиста \n\n {ex.Message}",
"Неверный файл прайс-листа!",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
}
finally
{
wb.Close();
}
}
ExcelApp.ScreenUpdating = true;
}
public void ExportToNewFile(string exportFile)
{
if (SkuAmount.Count < 1)
{
return;
}
Workbook wb = ExcelApp.Workbooks.Open(exportFile);
PriceList priceList;
try
{
priceList = new PriceList(wb);
priceList.Fill(SkuAmount);
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show
($"{RegistryUtil.PriceListPath} не является файлом прайслиста \n\n {ex.Message}",
"Неверный файл прайс-листа!",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error);
wb.Close();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
}
}

View File

@ -0,0 +1,87 @@
using Microsoft.Office.Interop.Excel;
using System.Collections.Generic;
namespace RehauSku.PriceListTools
{
class PriceList
{
public readonly Workbook Workbook;
public readonly PriceListSheet OfferSheet;
public readonly PriceListSheet ActiveSheet;
private const string _amountHeader = "Кол-во";
private const string _skuHeader = "Актуальный материал";
private const string _offerSheetHeader = "КП";
public PriceList(Workbook workbook)
{
Workbook = workbook;
OfferSheet = new PriceListSheet(workbook.Sheets[_offerSheetHeader]);
Worksheet active = workbook.ActiveSheet;
if (active.Name == _offerSheetHeader)
ActiveSheet = OfferSheet;
else
ActiveSheet = new PriceListSheet(active);
}
public bool IsValid()
{
return OfferSheet.IsValid() &&
ActiveSheet.IsValid();
}
public void Fill(Dictionary<string, double> values)
{
Worksheet ws = OfferSheet.sheet;
ws.Activate();
int amountColumn = OfferSheet.amountColumn.Value;
int skuColumn = OfferSheet.skuColumn.Value;
foreach (KeyValuePair<string, double> kvp in values)
{
Range cell = ws.Columns[skuColumn].Find(kvp.Key);
ws.Cells[cell.Row, amountColumn].Value = kvp.Value;
}
AutoFilter filter = ws.AutoFilter;
int firstFilterColumn = filter.Range.Column;
filter.Range.AutoFilter(amountColumn - firstFilterColumn + 1, "<>");
ws.Range["A1"].Activate();
}
public class PriceListSheet
{
public readonly Worksheet sheet;
public readonly int? headerRow;
public readonly int? amountColumn;
public readonly int? skuColumn;
public object[,] amountCells;
public object[,] skuCells;
public PriceListSheet(Worksheet sheet)
{
this.sheet = sheet;
headerRow = sheet.Cells.Find(_amountHeader).Row;
amountColumn = sheet.Cells.Find(_amountHeader).Column;
skuColumn = sheet.Cells.Find(_skuHeader).Column;
amountCells = sheet.Columns[amountColumn].Value2;
skuCells = sheet.Columns[skuColumn].Value2;
}
public bool IsValid()
{
return sheet != null &&
headerRow != null &&
amountColumn != null &&
skuColumn != null;
}
}
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.IO;
namespace RehauSku.PriceListTools
{
static class PriceListUtil
{
public static string CreateNewExportFile()
{
string fileExtension = Path.GetExtension(RegistryUtil.PriceListPath);
string path = Path.GetTempFileName() + fileExtension;
File.Copy(RegistryUtil.PriceListPath, path);
return path;
}
public static void AddValues(this Dictionary<string, double> SkuAmount, PriceList priceList)
{
object[,] amountCells = priceList.ActiveSheet.amountCells;
object[,] skuCells = priceList.ActiveSheet.skuCells;
for (int row = priceList.ActiveSheet.headerRow.Value + 1; row < amountCells.GetLength(0); row++)
{
object amount = amountCells[row, 1];
object sku = skuCells[row, 1];
if (amount != null && (double)amount != 0)
{
if (SkuAmount.ContainsKey(sku.ToString()))
{
SkuAmount[sku.ToString()] += (double)amount;
}
else
SkuAmount.Add(sku.ToString(), (double)amount);
}
}
}
}
}

View File

@ -0,0 +1,69 @@
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ExcelDna.Integration.CustomUI;
using RehauSku.PriceListTools;
using RehauSku.Forms;
namespace RehauSku.Ribbon
{
[ComVisible(true)]
public class RibbonController : ExcelRibbon
{
public override string GetCustomUI(string RibbonID)
{
return @"
<customUI xmlns='http://schemas.microsoft.com/office/2006/01/customui'>
<ribbon>
<tabs>
<tab id='rau' label='REHAU'>
<group id='priceList' label='Прайс-лист'>
<button id='exportToPrice' label='Экспорт в новый файл' size='normal' imageMso='PivotExportToExcel' onAction='OnExportPressed'/>
<button id='mergeFiles' label='Объединить' size='normal' imageMso='Copy' onAction='OnMergePressed'/>
</group>
<group id='rausettings' label='Настройки'>
<button id='setPriceList' label='Файл прайс-листа' size='normal' imageMso='CurrentViewSettings' onAction='OnSetPricePressed'/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>";
}
public void OnMergePressed(IRibbonControl control)
{
using (MergeTool mergeTool = new MergeTool())
{
string[] files = Dialog.GetMultiplyFiles();
mergeTool.AddSkuAmountToDict(files);
string exportFile = PriceListUtil.CreateNewExportFile();
mergeTool.ExportToNewFile(exportFile);
}
}
public void OnExportPressed(IRibbonControl control)
{
using (ExportTool exportTool = new ExportTool())
{
if (!exportTool.IsRangeValid())
{
MessageBox.Show("Выделен неверный диапазон!",
"Неверный диапазон",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
else
{
exportTool.ExportToNewFile();
}
}
}
public void OnSetPricePressed(IRibbonControl control)
{
string path = Dialog.GetFilePath();
RegistryUtil.PriceListPath = path;
}
}
}

View File

@ -3,6 +3,8 @@
<package id="AngleSharp" version="0.16.1" targetFramework="net48" />
<package id="ExcelDna.AddIn" version="1.5.0" targetFramework="net480" />
<package id="ExcelDna.Integration" version="1.5.0" targetFramework="net480" />
<package id="ExcelDna.IntelliSense" version="1.5.0" targetFramework="net48" />
<package id="ExcelDna.Interop" version="14.0.1" targetFramework="net48" />
<package id="ExcelDna.Registration" version="1.5.0" targetFramework="net480" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />