Compare commits

..

No commits in common. "master" and "v1.9.0.0" have entirely different histories.

30 changed files with 435 additions and 404 deletions

27
.vscode/launch.json vendored
View File

@ -1,27 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE",
"args": [
"${workspaceFolder}\\RhSolutions.AddIn\\bin\\Debug\\net6.0-windows\\RhSolutions-AddIn64.xll"
],
"cwd": "${workspaceFolder}",
"console": "internalConsole",
"stopAtEntry": false,
"requireExactSource": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

12
.vscode/tasks.json vendored
View File

@ -1,12 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "dotnet",
"task": "build ${workspaceFolder}\\RhSolutions.AddIn\\RhSolutions.AddIn.csproj",
"group": "build",
"problemMatcher": [],
"label": "build"
},
]
}

14
Install.ps1 Normal file
View File

@ -0,0 +1,14 @@
$object = Invoke-WebRequest -Uri "https://gitea.cebotari.ru/api/v1/repos/chebser/RhSolutions-AddIn/releases/latest" | ConvertFrom-Json
$download_link = $object.assets[1].browser_download_url
Invoke-WebRequest -Uri $download_link -OutFile "$env:appdata\Microsoft\AddIns\RhSolutions-AddIn-packed.xll"
$excel = New-Object -ComObject Excel.Application
foreach ($addin in $excel.AddIns2)
{
$title = $addin.Title
if ($title -eq 'RhSolutions Add-In')
{
$addin.Installed = $true
}
}
$excel.Quit()

View File

@ -8,13 +8,12 @@
- Объединение нескольких прайс-листов в один файл - Объединение нескольких прайс-листов в один файл
- Поиск пар артикул-количество в любой сторонней таблице и экспорт в таблицу заказов - Поиск пар артикул-количество в любой сторонней таблице и экспорт в таблицу заказов
- Подбор монтажных гильз для фитингов RAUTITAN - Подбор монтажных гильз для фитингов RAUTITAN
- Подбор ремонтных муфт по количеству трубы
- Экспорт таблицы заказов в девятиграфку по ГОСТ - Экспорт таблицы заказов в девятиграфку по ГОСТ
*Для работы функций "Экспорт", "Актуализация" и "Объединение" требуется указать путь к файлу пустого прайс-листа РЕХАУ* *Для работы функций "Экспорт", "Актуализация" и "Объединение" требуется указать путь к файлу пустого прайс-листа РЕХАУ*
## Реализованные формулы для работы с артикулами ## Реализованные формулы для работы с артикулами
- ```=РЕХАУ()``` - поиск артикула РЕХАУ по произвольному запросу в сервисе [RhSolutions-Api](https://gitea.cebotari.ru/chebser/RhSolutions-Api) - ```=RHSOLUTIONS()``` и ```=РЕХАУ()``` - поиск в удаленной базе данных
- ```=РЕХАУАРТИКУЛ()``` - экстракция артикула РЕХАУ из любой строки по регулярному выражению - ```=РЕХАУАРТИКУЛ()``` - экстракция артикула РЕХАУ из любой строки по регулярному выражению
- ```=РЕХАУИМЯ()``` - поиск названия артикула по номеру - ```=РЕХАУИМЯ()``` - поиск названия артикула по номеру
- ```=РЕХАУЦЕНА()``` - поиск цены в евро по номеру артикула - ```=РЕХАУЦЕНА()``` - поиск цены в евро по номеру артикула

View File

@ -1,27 +0,0 @@
namespace RhSolutions.AddIn;
public static class CurrencyFunctions
{
private static readonly ICurrencyClient currencyClient =
RhSolutionsAddIn.ServiceProvider.GetRequiredService<ICurrencyClient>();
[ExcelFunction(Name = "КУРСЕВРО")]
public static object GetEuroCurrencyRate(double dateField)
{
DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField);
var functionName = nameof(GetEuroCurrencyRate);
var parameters = new object[] { date };
var exchangeRate = ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
var requestResult = await currencyClient.GetExchangeRate(date); return requestResult ?? -1m;
});
if (exchangeRate is not decimal)
{
return "Загрузка...";
}
return (decimal)exchangeRate < 0 ? ExcelError.ExcelErrorNA : exchangeRate;
}
}

View File

@ -1,7 +1,14 @@
using System.Net; using System.Net;
using ExcelDna.IntelliSense;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.AddIn; namespace RhSolutions.AddIn;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public sealed class RhSolutionsAddIn : IExcelAddIn public sealed class RhSolutionsAddIn : IExcelAddIn
{ {
public static Application Excel { get; private set; } public static Application Excel { get; private set; }
@ -48,13 +55,22 @@ public sealed class RhSolutionsAddIn : IExcelAddIn
EventsUtil.Initialize(); EventsUtil.Initialize();
if (Environment.GetEnvironmentVariable("ISTESTING") == "false")
{
IntelliSenseServer.Install();
}
ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12; SecurityProtocolType.Tls12;
ServicePointManager.DefaultConnectionLimit = 50;
} }
public void AutoClose() public void AutoClose()
{ {
EventsUtil.Uninitialize(); EventsUtil.Uninitialize();
if (Environment.GetEnvironmentVariable("ISTESTING") == "false")
{
IntelliSenseServer.Uninstall();
}
} }
} }

View File

@ -1,156 +1,245 @@
namespace RhSolutions.AddIn; #if !NET472
using System.Runtime.Versioning;
#endif
public static class RhSolutionsFunctions using Microsoft.Extensions.Caching.Memory;
namespace RhSolutions.AddIn;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public class RhSolutionsFunctions
{ {
private static readonly IDatabaseClient databaseClient = [ExcelFunction(Description = "Поиск артикула в базе данных")]
RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>(); public static object RHSOLUTIONS([ExcelArgument(Name = "СТРОКА", Description = "Ячейка с артикулом РЕХАУ или поисковый запрос в свободной форме")] string line)
private static readonly ICurrencyClient currencyClient = {
RhSolutionsAddIn.ServiceProvider.GetRequiredService<ICurrencyClient>(); IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
[ExcelFunction(Name = "РЕХАУ")] ProductSku.TryParse(line, out var skus);
public static object ProductSearch(object[,] values)
{
List<string> strings = new();
int rows = values.GetLength(0);
int columns = values.GetLength(1);
for (int row = 0; row < rows; row++)
{
for (int column = 0; column < columns; column++)
{
object value = values[row, column];
strings.Add(value.ToString());
}
}
string query = string.Join(" ", strings.ToArray());
var functionName = nameof(ProductSearch);
var parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
return await databaseClient.GetProducts(query);
}) is not IEnumerable<Product> products)
{
return "Загрузка...";
}
else if (!products.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var product = products.First();
return $"{product.Id} {product.Name}";
}
}
[ExcelFunction(Name = "РЕХАУАРТИКУЛ")] if (ExcelAsyncUtil.Run(nameof(RHSOLUTIONS), line, delegate
public static object SkuSearch(string query) {
{ return databaseClient.GetProducts(line)
if (ProductSku.TryParse(query, out var skus)) .GetAwaiter()
{ .GetResult();
return skus.First().Id; }) is not IEnumerable<Product> requestResult)
} {
return ExcelError.ExcelErrorNA; if (skus.Any())
} {
return $"{skus.First()} ...";
}
else
{
return "Загрузка...";
}
}
[ExcelFunction(Name = "РЕХАУИМЯ")] else
public static object GetProductName(string query) {
{ if (!requestResult.Any() && !skus.Any())
if (!ProductSku.TryParse(query, out var skus)) {
{ return ExcelError.ExcelErrorNA;
return ExcelError.ExcelErrorNA; }
} else if (!requestResult.Any())
var article = skus.First().Id; {
var functionName = nameof(GetProductName); return $"{skus.First()}";
var parameters = new object[] { query }; }
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () => else
{ {
return await databaseClient.GetProducts(article); var firstProduct = requestResult.First();
}) is not IEnumerable<Product> requestResult) return $"{firstProduct.ProductSku} {firstProduct.Name}";
{ }
return "Загрузка..."; }
} }
else if (!requestResult.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var firstProduct = requestResult.First();
return firstProduct.Name;
}
}
[ExcelFunction(Name = "РЕХАУЦЕНА")] [ExcelFunction(Description = "Поиск артикула в базе данных")]
public static object GetProductPrice(string query) public static object РЕХАУ([ExcelArgument(Name = "СТРОКА", Description = "Ячейка с артикулом РЕХАУ или поисковый запрос в свободной форме")] string line)
{ => RHSOLUTIONS(line);
if (!ProductSku.TryParse(query, out var skus))
{
return ExcelError.ExcelErrorNA;
}
var article = skus.First().Id;
var functionName = nameof(GetProductPrice);
var parameters = new object[] { article };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () => [ExcelFunction(Description = "Выделить артикул РЕХАУ из ячейки")]
{ public static object РЕХАУАРТИКУЛ([ExcelArgument(Name = "СТРОКА", Description = "Ячейка содержащая артикул РЕХАУ")] string line)
return await databaseClient.GetProducts(article); {
}) is not IEnumerable<Product> requestResult) if (ProductSku.TryParse(line, out var skus))
{ {
return "Загрузка..."; return skus.First().Id;
} }
else if (!requestResult.Any()) else
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
else }
{
var firstProduct = requestResult.First();
return Math.Round(firstProduct.Price * 1.2m, 2);
}
}
[ExcelFunction(Name = "РЕХАУЦЕНАРУБ")] [ExcelFunction(Description = "Поиск названия по артикулу РЕХАУ")]
public static object GetProductPriceRub(string query, double dateField) public static object РЕХАУИМЯ([ExcelArgument(Name = "АРТИКУЛ", Description = "Ячейка содержащая артикул РЕХАУ")] string line)
{ {
if (!ProductSku.TryParse(query, out var skus)) if (!ProductSku.TryParse(line, out var skus))
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
var article = skus.First().Id; else
DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField); {
var article = skus.First().Id;
IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
if (ExcelAsyncUtil.Run(nameof(РЕХАУИМЯ), line, delegate
{
return databaseClient.GetProducts(article)
.GetAwaiter()
.GetResult();
}) is not IEnumerable<Product> requestResult)
{
return "Загрузка...";
}
else
{
if (!requestResult.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var firstProduct = requestResult.First();
return firstProduct.Name;
}
}
}
}
var functionName = nameof(GetProductPriceRub); [ExcelFunction(Description = "Поиск цены артикула РЕХАУ в евро")]
var parameters = new object[] { date }; public static object РЕХАУЦЕНА([ExcelArgument(Name = "АРТИКУЛ", Description = "Ячейка содержащая артикул РЕХАУ")] string line)
{
if (!ProductSku.TryParse(line, out var skus))
{
return ExcelError.ExcelErrorNA;
}
else
{
var article = skus.First().Id;
IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () => if (ExcelAsyncUtil.Run(nameof(РЕХАУЦЕНА), line, delegate
{ {
var requestResult = await currencyClient.GetExchangeRate(date); return databaseClient.GetProducts(article)
return requestResult ?? -1m; .GetAwaiter()
}) is not decimal exchangeRate) .GetResult();
{ }) is not IEnumerable<Product> requestResult)
return "Загрузка..."; {
} return "Загрузка...";
}
else
{
if (!requestResult.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var firstProduct = requestResult.First();
return Math.Round(firstProduct.Price * 1.2m, 2);
}
}
}
}
parameters = new object[] { query }; [ExcelFunction(Description = "Поиск цены артикула РЕХАУ в рублях")]
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () => public static object РЕХАУЦЕНАРУБ([ExcelArgument(Name = "АРТИКУЛ", Description = "Ячейка содержащая артикул РЕХАУ")] string line,
{ [ExcelArgument(Name = "ДАТА", Description = "Дата в формате Excel (необязательно)")] double dateField)
var products = await databaseClient.GetProducts(article); {
var product = products.FirstOrDefault(); if (!ProductSku.TryParse(line, out var skus))
return product == null ? -1m : {
product.Price * (decimal)exchangeRate * 1.2m; return ExcelError.ExcelErrorNA;
}) is not decimal requestResult) }
{ else
return "Загрузка..."; {
} IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
else if (requestResult < 0 || exchangeRate < 0) ICurrencyClient currencyClient = RhSolutionsAddIn.ServiceProvider.GetRequiredService<ICurrencyClient>();
{ IMemoryCache memoryCache = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IMemoryCache>();
return ExcelError.ExcelErrorNA; var article = skus.First().Id;
} DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField);
else
{ if (!memoryCache.TryGetValue(date, out decimal exchangeRate))
return Math.Round(requestResult, 2); {
} var result = ExcelAsyncUtil.Run(nameof(КУРСЕВРО), dateField, delegate
} {
var requestResult = currencyClient.GetExchangeRate(date)
.GetAwaiter()
.GetResult();
return requestResult ?? -1m;
});
if (result is not decimal)
{
return "Загрузка...";
}
else
{
exchangeRate = (decimal)result;
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1));
memoryCache.Set(date, exchangeRate, cacheEntryOptions);
}
}
if (ExcelAsyncUtil.Run(nameof(РЕХАУЦЕНАРУБ), line, delegate
{
var product = databaseClient.GetProducts(article)
.GetAwaiter()
.GetResult()
.FirstOrDefault();
return product == null ? -1m :
product.Price * exchangeRate * 1.2m;
}) is not decimal requestResult)
{
return "Загрузка...";
}
else
{
if (requestResult < 0)
{
return ExcelError.ExcelErrorNA;
}
else
{
return Math.Round(requestResult, 2);
}
}
}
}
[ExcelFunction(Description = "Получить курс евро по ЦБ")]
public static object КУРСЕВРО([ExcelArgument(Name = "ДАТА", Description = "Дата в формате Excel (необязательно)")] double dateField)
{
ICurrencyClient currencyClient = RhSolutionsAddIn.ServiceProvider.GetRequiredService<ICurrencyClient>();
IMemoryCache memoryCache = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IMemoryCache>();
DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField);
if (!memoryCache.TryGetValue(date, out decimal exchangeRate))
{
var result = ExcelAsyncUtil.Run(nameof(КУРСЕВРО), dateField, delegate
{
var requestResult = currencyClient.GetExchangeRate(date)
.GetAwaiter()
.GetResult();
return requestResult ?? -1m;
});
if (result is not decimal)
{
return "Загрузка...";
}
else
{
exchangeRate = (decimal)result;
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1));
memoryCache.Set(date, exchangeRate, cacheEntryOptions);
}
}
return exchangeRate < 0 ? ExcelError.ExcelErrorNA : exchangeRate;
}
} }

View File

@ -1,11 +1,16 @@
using ExcelDna.Integration.CustomUI; using ExcelDna.Integration.CustomUI;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if! NET472
using System.Runtime.Versioning;
#endif
using System.Windows.Forms; using System.Windows.Forms;
namespace RhSolutions.Controllers; namespace RhSolutions.Controllers;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
[ComVisible(true)] [ComVisible(true)]
public class RibbonController : ExcelRibbon public class RibbonController : ExcelRibbon
{ {
@ -109,7 +114,7 @@ public class RibbonController : ExcelRibbon
public string GetPriceListPathLabel(IRibbonControl control) public string GetPriceListPathLabel(IRibbonControl control)
{ {
string name = RhSolutionsAddIn.Configuration.GetPriceListFileName(); string name = RhSolutionsAddIn.Configuration.GetPriceListFileName();
return string.IsNullOrEmpty(name) ? "Указать шаблонный файл" : name; return string.IsNullOrEmpty(name) ? "Шаблонный файл" : name;
} }
public static void UpdateWorkbookValidation() public static void UpdateWorkbookValidation()
@ -123,13 +128,4 @@ public class RibbonController : ExcelRibbon
_workbookIsValid = worksheet.IsValidSource(); _workbookIsValid = worksheet.IsValidSource();
} }
} }
public static void EnsurePriseListExists()
{
string pricelistPath = RhSolutionsAddIn.Configuration.GetPriceListPath();
if (!File.Exists(pricelistPath))
{
RhSolutionsAddIn.Configuration.SetPriceListPath(string.Empty);
}
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.9.5.1")] [assembly: AssemblyVersion("1.9.0.0")]
[assembly: AssemblyFileVersion("1.9.5.1")] [assembly: AssemblyFileVersion("1.9.0.0")]

View File

@ -3,7 +3,7 @@
"Excel": { "Excel": {
"commandName": "Executable", "commandName": "Executable",
"executablePath": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE", "executablePath": "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE",
"commandLineArgs": "/x \"RhSolutions-AddIn64.xll\"" "commandLineArgs": "RhSolutions-AddIn64.xll"
} }
} }
} }

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<DnaLibrary Name="RhSolutions Add-In" RuntimeVersion="v4.0" xmlns="http://schemas.excel-dna.net/addin/2020/07/dnalibrary"> <DnaLibrary Name="RhSolutions Add-In" RuntimeVersion="v4.0" xmlns="http://schemas.excel-dna.net/addin/2020/07/dnalibrary">
<ExternalLibrary Path="RhSolutions.AddIn.dll" ExplicitExports="false" LoadFromBytes="true" Pack="true" IncludePdb="false" /> <ExternalLibrary Path="RhSolutions.AddIn.dll" ExplicitExports="false" LoadFromBytes="true" Pack="true" IncludePdb="false" />
<Reference Path="ExcelDna.Registration.dll" Pack="true" />
<Reference Path="Microsoft.Bcl.AsyncInterfaces.dll" Pack="true" /> <Reference Path="Microsoft.Bcl.AsyncInterfaces.dll" Pack="true" />
<Reference Path="Microsoft.Bcl.HashCode.dll" Pack="true" /> <Reference Path="Microsoft.Bcl.HashCode.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Caching.Abstractions.dll" Pack="true" /> <Reference Path="Microsoft.Extensions.Caching.Abstractions.dll" Pack="true" />
@ -13,6 +12,7 @@
<Reference Path="Microsoft.Extensions.Logging.dll" Pack="true" /> <Reference Path="Microsoft.Extensions.Logging.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Options.dll" Pack="true" /> <Reference Path="Microsoft.Extensions.Options.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Primitives.dll" Pack="true" /> <Reference Path="Microsoft.Extensions.Primitives.dll" Pack="true" />
<Reference Path="ExcelDna.IntelliSense.dll" Pack="true" />
<Reference Path="Newtonsoft.Json.dll" Pack="true" /> <Reference Path="Newtonsoft.Json.dll" Pack="true" />
<Reference Path="netDxf.dll" Pack="true" /> <Reference Path="netDxf.dll" Pack="true" />
<Reference Path="RhSolutions.ProductSku.dll" Pack="true" /> <Reference Path="RhSolutions.ProductSku.dll" Pack="true" />

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net472;net6.0-windows</TargetFrameworks> <TargetFrameworks>net472</TargetFrameworks>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<RootNamespace>RhSolutions</RootNamespace> <RootNamespace>RhSolutions</RootNamespace>
@ -8,17 +8,35 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets> <ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<ExcelDnaPackCompressResources>false</ExcelDnaPackCompressResources>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net472|AnyCPU'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net472|AnyCPU'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0-windows7.0|AnyCPU'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0-windows7.0|AnyCPU'">
<NoWarn>1701;1702</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ExcelDna.AddIn" Version="1.8.0" /> <PackageReference Include="ExcelDna.AddIn" Version="1.6.0">
<PackageReference Include="ExcelDna.Integration" Version="1.8.0" /> <TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="ExcelDna.Integration" Version="1.6.0" />
<PackageReference Include="ExcelDna.IntelliSense" Version="1.6.0" />
<PackageReference Include="ExcelDna.Interop" Version="15.0.1" /> <PackageReference Include="ExcelDna.Interop" Version="15.0.1" />
<PackageReference Include="ExcelDna.Registration" Version="1.8.0" /> <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" /> <PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="netDxf" Version="2022.11.2" /> <PackageReference Include="netDxf" Version="2022.11.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Buffers" Version="4.5.1" /> <PackageReference Include="System.Buffers" Version="4.5.1" />

View File

@ -38,11 +38,11 @@ public class CouplingsCalculator : IFittingsCalculator
.ToDictionary(kvp => .ToDictionary(kvp =>
kvp.Key switch kvp.Key switch
{ {
"16" => new Product("11080111001"), "16" => new Product("11600111001"),
"20" => new Product("11080121001"), "20" => new Product("11600121001"),
"25" => new Product("11080131001"), "25" => new Product("11600131001"),
"32" => new Product("11080141001"), "32" => new Product("11600141001"),
"40" => new Product("11080151001"), "40" => new Product("11600151001"),
"50" => new Product("14563021001"), "50" => new Product("14563021001"),
"63" => new Product("14563031001"), "63" => new Product("14563031001"),
_ => throw new Exception($"Неизвестный диаметр {kvp.Key}") _ => throw new Exception($"Неизвестный диаметр {kvp.Key}")

View File

@ -1,5 +1,4 @@
using Microsoft.Extensions.Caching.Memory; using System.Net;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
@ -9,42 +8,34 @@ namespace RhSolutions.Services;
public class CurrencyClient : ICurrencyClient public class CurrencyClient : ICurrencyClient
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly IMemoryCache _memoryCache;
private const string _requestAddress = @"https://www.cbr.ru/scripts/XML_daily.asp?date_req="; private const string _requestAddress = @"https://www.cbr.ru/scripts/XML_daily.asp?date_req=";
public HttpStatusCode StatusCode { get; private set; } public HttpStatusCode StatusCode { get; private set; }
public CurrencyClient(HttpClient httpClient, IMemoryCache memoryCache) public CurrencyClient(HttpClient httpClient)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_memoryCache = memoryCache;
} }
public async Task<decimal?> GetExchangeRate(DateTime date) public async Task<decimal?> GetExchangeRate(DateTime date)
{ {
if (!_memoryCache.TryGetValue(date, out decimal exchangeRate)) string request = $"{_requestAddress}{date.Date:dd/MM/yyyy}";
HttpResponseMessage response = await _httpClient.GetAsync(request);
try
{ {
string request = $"{_requestAddress}{date.Date:dd/MM/yyyy}"; response.EnsureSuccessStatusCode();
HttpResponseMessage response = await _httpClient.GetAsync(request); string xml = await response.Content.ReadAsStringAsync();
try XElement valCourses = XElement.Parse(xml);
{
response.EnsureSuccessStatusCode(); decimal? exchangeRate = decimal.Parse(valCourses.Elements("Valute")
string xml = await response.Content.ReadAsStringAsync(); .Where(e => e.Element("Name").Value == "Евро")
XElement valCourses = XElement.Parse(xml); .FirstOrDefault()
exchangeRate = decimal.Parse(valCourses.Elements("Valute") .Element("Value").Value);
.Where(e => e.Element("Name").Value == "Евро") return exchangeRate;
.FirstOrDefault() }
.Element("Value").Value); catch
var cacheEntryOptions = new MemoryCacheEntryOptions() {
.SetSlidingExpiration(TimeSpan.FromHours(1)); StatusCode = response.StatusCode;
_memoryCache.Set(date, exchangeRate, cacheEntryOptions); return null;
return exchangeRate;
}
catch
{
StatusCode = response.StatusCode;
return null;
}
} }
return exchangeRate;
} }
} }

View File

@ -79,7 +79,7 @@ public class ExcelReader : IReader, IDisposable
return readResult; return readResult;
} }
public IEnumerable<(string, Dictionary<Product, double>)> public List<(string, Dictionary<Product, double>)>
ReadProducts(IEnumerable<Worksheet> worksheets) ReadProducts(IEnumerable<Worksheet> worksheets)
{ {
List<(string, Dictionary<Product, double>)> result = new(); List<(string, Dictionary<Product, double>)> result = new();
@ -151,15 +151,11 @@ public class ExcelReader : IReader, IDisposable
result.Add((wbName, readResult)); result.Add((wbName, readResult));
} }
return result.OrderBy(x => x.Item1); return result;
} }
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(string[] files) public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files)
{ {
HashSet<string> openedFiles = RhSolutionsAddIn.Excel.Workbooks
.Cast<Workbook>()
.Select(wb => wb.FullName)
.ToHashSet();
_progressBar = new("Открываю исходные файлы...", files.Length); _progressBar = new("Открываю исходные файлы...", files.Length);
List<Worksheet> worksheets = new(); List<Worksheet> worksheets = new();
@ -174,11 +170,7 @@ public class ExcelReader : IReader, IDisposable
var result = ReadProducts(worksheets); var result = ReadProducts(worksheets);
foreach (var ws in worksheets) foreach (var ws in worksheets)
{ {
string file = (string)ws.Parent.FullName; ws.Parent.Close();
if (!openedFiles.Contains(file))
{
ws.Parent.Close(SaveChanges: false);
}
} }
return result; return result;
} }

View File

@ -70,7 +70,6 @@ namespace RhSolutions.Services
.Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow); .Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow);
Range newColumnHeader = _worksheet.Cells[_amountCell.Row, _amountCell.Column - 1]; Range newColumnHeader = _worksheet.Cells[_amountCell.Row, _amountCell.Column - 1];
newColumnHeader.NumberFormat = "@";
newColumnHeader.Value2 = $"{product.Item1}"; newColumnHeader.Value2 = $"{product.Item1}";
newColumnHeader.WrapText = true; newColumnHeader.WrapText = true;

View File

@ -192,7 +192,7 @@ public class GuessReader : IReader
return result; return result;
} }
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets) public List<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets)
{ {
List<(string, Dictionary<Product, double>)> result = new(); List<(string, Dictionary<Product, double>)> result = new();
foreach (Worksheet worksheet in worksheets) foreach (Worksheet worksheet in worksheets)
@ -206,7 +206,7 @@ public class GuessReader : IReader
return result; return result;
} }
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(string[] files) public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files)
{ {
_progressBar = new("Открываю исходные файлы...", files.Length); _progressBar = new("Открываю исходные файлы...", files.Length);
List<Worksheet> worksheets = new(); List<Worksheet> worksheets = new();

View File

@ -3,7 +3,7 @@
public interface IReader : IDisposable public interface IReader : IDisposable
{ {
public Dictionary<Product, double> ReadProducts(Range range); public Dictionary<Product, double> ReadProducts(Range range);
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets); public List<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets);
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(string[] files); public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files);
new void Dispose(); new void Dispose();
} }

View File

@ -4,84 +4,60 @@ namespace RhSolutions.Services;
public class SleevesCalculator : IFittingsCalculator public class SleevesCalculator : IFittingsCalculator
{ {
private const string doublePattern = private const string doublePattern =
@"((?i)равнопроходная|угольник\s+90|угольник\s+45|Т-образная|Комплект\s+трубок(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+"; @"((?i)равнопроходная|угольник\s+90|угольник\s+45|Т-образная|Комплект\s+трубок(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+";
private const string singlePattern = private const string singlePattern =
@"((?i)муфта|тройник|переходник|угольник|штуцер|Г-образная|заглушка(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+"; @"((?i)муфта|тройник|переходник|угольник|штуцер|Г-образная|заглушка(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+";
public Dictionary<Product, double> Calculate(Dictionary<Product, double> products) public Dictionary<Product, double> Calculate(Dictionary<Product, double> products)
{ {
Dictionary<string, double> result = new() Dictionary<string, double> result = new()
{ {
["16"] = 0, ["16"] = 0,
["20"] = 0, ["20"] = 0,
["25"] = 0, ["25"] = 0,
["32"] = 0, ["32"] = 0,
["40"] = 0, ["40"] = 0,
["50"] = 0, ["50"] = 0,
["63"] = 0, ["63"] = 0,
["16PX"] = 0, };
["20PX"] = 0, var rautitanProducts = products.Where(kvp => kvp.Key.ProductLines.Contains("RAUTITAN"));
["25PX"] = 0,
["32PX"] = 0,
["40PX"] = 0
};
var rautitanProducts = products.Where(kvp => kvp.Key.ProductLines.Contains("RAUTITAN"));
foreach (var kvp in rautitanProducts) foreach (var kvp in rautitanProducts)
{ {
var doubleCollection = Regex.Matches(kvp.Key.Name, doublePattern); var doubleCollection = Regex.Matches(kvp.Key.Name, doublePattern);
if (doubleCollection.Count != 0) if (doubleCollection.Count != 0)
{ {
CaptureCollection collection = doubleCollection[0].Groups["Sleeve"].Captures; CaptureCollection collection = doubleCollection[0].Groups["Sleeve"].Captures;
foreach (Capture sleeve in collection) foreach (Capture sleeve in collection)
{ {
if (kvp.Key.Name.Contains("PX")) result[sleeve.Value] += kvp.Value * 2;
{ }
result[$"{sleeve.Value}PX"] += kvp.Value * 2; continue;
} }
else var singleCollection = Regex.Matches(kvp.Key.Name, singlePattern);
{ if (singleCollection.Count != 0)
result[sleeve.Value] += kvp.Value * 2; {
} CaptureCollection collection = singleCollection[0].Groups["Sleeve"].Captures;
} foreach (Capture sleeve in collection)
continue; {
} result[sleeve.Value] += kvp.Value;
var singleCollection = Regex.Matches(kvp.Key.Name, singlePattern); }
if (singleCollection.Count != 0) }
{ }
CaptureCollection collection = singleCollection[0].Groups["Sleeve"].Captures;
foreach (Capture sleeve in collection)
{
if (kvp.Key.Name.Contains("PX"))
{
result[$"{sleeve.Value}PX"] += kvp.Value;
}
else
{
result[sleeve.Value] += kvp.Value;
}
}
}
}
return result return result
.ToDictionary(kvp => .ToDictionary(kvp =>
kvp.Key switch kvp.Key switch
{ {
"16" => new Product("11080011001"), "16" => new Product("11600011001"),
"20" => new Product("11080021001"), "20" => new Product("11600021001"),
"25" => new Product("11080031001"), "25" => new Product("11600031001"),
"32" => new Product("11080041001"), "32" => new Product("11600041001"),
"40" => new Product("11080051001"), "40" => new Product("11600051001"),
"50" => new Product("11397713002"), "50" => new Product("11397711002"),
"63" => new Product("11397813002"), "63" => new Product("11397811002"),
"16PX" => new Product("11600011001"), _ => throw new Exception($"Неизвестный диаметр {kvp.Key}")
"20PX" => new Product("11600021001"), }, kvp => kvp.Value);
"25PX" => new Product("11600031001"), }
"32PX" => new Product("11600041001"),
"40PX" => new Product("11600051001"),
_ => throw new Exception($"Неизвестный диаметр {kvp.Key}")
}, kvp => kvp.Value);
}
} }

View File

@ -1,12 +1,17 @@
using RhSolutions.Controllers; using RhSolutions.Controllers;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools; namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal static class EventsUtil internal static class EventsUtil
{ {
public static void Initialize() public static void Initialize()
{ {
RibbonController.EnsurePriseListExists();
RhSolutionsAddIn.Excel.SheetSelectionChange += RefreshExportButton; RhSolutionsAddIn.Excel.SheetSelectionChange += RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate += RefreshButtons; RhSolutionsAddIn.Excel.SheetActivate += RefreshButtons;
RhSolutionsAddIn.Excel.WorkbookActivate += RefreshButtons; RhSolutionsAddIn.Excel.WorkbookActivate += RefreshButtons;

View File

@ -17,12 +17,9 @@ internal class MergeTool : Tool
{ {
IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IFileDialog>(); IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IFileDialog>();
string[] files = dialog.GetFiles(); string[] files = dialog.GetFiles();
if (files.Length > 0) _reader = _readerFactory.GetReader("Excel");
{ var products = _reader.ReadProducts(files);
_reader = _readerFactory.GetReader("Excel"); _writer = _writerFactory.GetWriter("NewPrice");
var products = _reader.ReadProducts(files); _writer.WriteProducts(products);
_writer = _writerFactory.GetWriter("NewPrice");
_writer.WriteProducts(products);
}
} }
} }

View File

@ -5,37 +5,38 @@ namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)] [ExcelTestSettings(OutOfProcess = true)]
public class CanFillCouplings : IDisposable public class CanFillCouplings : IDisposable
{ {
private RhSolutionsAddIn _addIn; private RhSolutionsAddIn _addIn;
private IFittingsCalculator _calculator; private IFittingsCalculator _calculator;
private IReader _reader; private IReader _reader;
private IWriter _writer; private IWriter _writer;
private Worksheet _worksheet; private Worksheet _worksheet;
public CanFillCouplings() public CanFillCouplings()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true"); Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new(); _addIn = new();
_addIn.AutoOpen(); _addIn.AutoOpen();
_calculator = new CouplingsCalculator(); _calculator = new CouplingsCalculator();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration); _reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);
_writer = new CurrentPriceWriter(Util.Application, RhSolutionsAddIn.Configuration); _writer = new CurrentPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_worksheet = Util.Workbook.Worksheets[1]; _worksheet = Util.Workbook.Worksheets[1];
} }
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationCouplings.xlsx")] [ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationCouplings.xlsx")]
public void CanCalculateSleeves() public void CanCalculateSleeves()
{ {
var products = _reader.ReadProducts(new[] { _worksheet }); var products = _reader.ReadProducts(new[] { _worksheet });
var couplings = _calculator.Calculate(products.First().Item2); var couplings = _calculator.Calculate(products.First().Item2);
_writer.WriteProducts(couplings); _writer.WriteProducts(couplings);
for (int i = 2; i < 14; i++)
{
Assert.Equal(_worksheet.Range[$"F{i}"].Value, _worksheet.Range[$"E{i}"].Value);
}
}
public void Dispose() Assert.Equal(7, _worksheet.Range["E2"].Value);
{ Assert.Equal(1, _worksheet.Range["E3"].Value);
_addIn.AutoClose(); Assert.Equal(1, _worksheet.Range["E5"].Value);
} Assert.Equal(1, _worksheet.Range["E7"].Value);
}
public void Dispose()
{
_addIn.AutoClose();
}
} }

View File

@ -28,10 +28,14 @@ public class CanFillSleeves : IDisposable
var products = _reader.ReadProducts(new[] { _worksheet }); var products = _reader.ReadProducts(new[] { _worksheet });
var sleeves = _calculator.Calculate(products.First().Item2); var sleeves = _calculator.Calculate(products.First().Item2);
_writer.WriteProducts(sleeves); _writer.WriteProducts(sleeves);
for (int i = 2; i < 14; i++)
{ Assert.Equal(25, _worksheet.Range["E2"].Value);
Assert.Equal(_worksheet.Range[$"F{i}"].Value, _worksheet.Range[$"E{i}"].Value); Assert.Equal(15, _worksheet.Range["E3"].Value);
} Assert.Equal(7, _worksheet.Range["E4"].Value);
Assert.Equal(8, _worksheet.Range["E5"].Value);
Assert.Equal(1, _worksheet.Range["E6"].Value);
Assert.Equal(3, _worksheet.Range["E7"].Value);
Assert.Equal(4, _worksheet.Range["E8"].Value);
} }
public void Dispose() public void Dispose()

View File

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="ExcelDna.Interop" Version="15.0.1" /> <PackageReference Include="ExcelDna.Interop" Version="15.0.1" />
<PackageReference Include="ExcelDna.Testing" Version="1.8.0" /> <PackageReference Include="ExcelDna.Testing" Version="1.6.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>