Compare commits

..

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

56 changed files with 649 additions and 1214 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"
},
]
}

View File

@ -7,14 +7,12 @@
- Актуализация прайс-листа до последней версии - Актуализация прайс-листа до последней версии
- Объединение нескольких прайс-листов в один файл - Объединение нескольких прайс-листов в один файл
- Поиск пар артикул-количество в любой сторонней таблице и экспорт в таблицу заказов - Поиск пар артикул-количество в любой сторонней таблице и экспорт в таблицу заказов
- Подбор монтажных гильз для фитингов 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

@ -0,0 +1,16 @@
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.AddIn;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public static class ResetBarFunction
{
[ExcelFunction]
public static void StatusBarReset()
{
RhSolutionsAddIn.Excel.StatusBar = false;
}
}

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; }
@ -18,6 +25,7 @@ public sealed class RhSolutionsAddIn : IExcelAddIn
.AddSingleton<IAddInConfiguration, AddInConfiguration>() .AddSingleton<IAddInConfiguration, AddInConfiguration>()
.AddSingleton<IDatabaseClient, DatabaseClient>() .AddSingleton<IDatabaseClient, DatabaseClient>()
.AddSingleton<ICurrencyClient, CurrencyClient>() .AddSingleton<ICurrencyClient, CurrencyClient>()
.AddSingleton<ISleevesCalculator, SleevesCalculator>()
.AddTransient<IFileDialog, FileDialog>(); .AddTransient<IFileDialog, FileDialog>();
Services.AddSingleton<WriterFactory>(); Services.AddSingleton<WriterFactory>();
@ -34,12 +42,6 @@ public sealed class RhSolutionsAddIn : IExcelAddIn
Services.AddTransient<GuessReader>() Services.AddTransient<GuessReader>()
.AddTransient<IReader, GuessReader>(s => s.GetService<GuessReader>()); .AddTransient<IReader, GuessReader>(s => s.GetService<GuessReader>());
Services.AddSingleton<FittingsCalculatorFactory>();
Services.AddTransient<CouplingsCalculator>()
.AddTransient<IFittingsCalculator, CouplingsCalculator>(s => s.GetService<CouplingsCalculator>());
Services.AddTransient<SleevesCalculator>()
.AddTransient<IFittingsCalculator, SleevesCalculator>(s => s.GetService<SleevesCalculator>());
Services.AddSingleton<ToolFactory>(); Services.AddSingleton<ToolFactory>();
ServiceProvider = Services.BuildServiceProvider(); ServiceProvider = Services.BuildServiceProvider();
@ -48,13 +50,34 @@ public sealed class RhSolutionsAddIn : IExcelAddIn
EventsUtil.Initialize(); EventsUtil.Initialize();
bool isTesting = Environment.GetEnvironmentVariable("ISTESTING") switch
{
"true" => true,
"false" => false,
_ => false
};
if (!isTesting)
{
IntelliSenseServer.Install();
}
ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12; SecurityProtocolType.Tls12;
ServicePointManager.DefaultConnectionLimit = 50;
} }
public void AutoClose() public void AutoClose()
{ {
EventsUtil.Uninitialize(); EventsUtil.Uninitialize();
bool isTesting = Environment.GetEnvironmentVariable("ISTESTING") switch
{
"true" => true,
"false" => false,
_ => false
};
if (!isTesting)
{
IntelliSenseServer.Uninstall();
}
} }
} }

View File

@ -1,76 +1,99 @@
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()); if (ExcelAsyncUtil.Run(nameof(RHSOLUTIONS), line, delegate
var functionName = nameof(ProductSearch);
var parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{ {
return await databaseClient.GetProducts(query); return databaseClient.GetProducts(line)
}) is not IEnumerable<Product> products) .GetAwaiter()
.GetResult();
}) is not IEnumerable<Product> requestResult)
{
if (skus.Any())
{
return $"{skus.First()} ...";
}
else
{ {
return "Загрузка..."; return "Загрузка...";
} }
else if (!products.Any()) }
else
{
if (!requestResult.Any() && !skus.Any())
{
return ExcelError.ExcelErrorNA;
}
else if (!requestResult.Any())
{
return $"{skus.First()}";
}
else
{
var firstProduct = requestResult.First();
return $"{firstProduct.ProductSku} {firstProduct.Name}";
}
}
}
[ExcelFunction(Description = "Поиск артикула в базе данных")]
public static object РЕХАУ([ExcelArgument(Name = "СТРОКА", Description = "Ячейка с артикулом РЕХАУ или поисковый запрос в свободной форме")] string line)
=> RHSOLUTIONS(line);
[ExcelFunction(Description = "Выделить артикул РЕХАУ из ячейки")]
public static object РЕХАУАРТИКУЛ([ExcelArgument(Name = "СТРОКА", Description = "Ячейка содержащая артикул РЕХАУ")] string line)
{
if (ProductSku.TryParse(line, out var skus))
{
return skus.First().Id;
}
else
{
return ExcelError.ExcelErrorNA;
}
}
[ExcelFunction(Description = "Поиск названия по артикулу РЕХАУ")]
public static object РЕХАУИМЯ([ExcelArgument(Name = "АРТИКУЛ", Description = "Ячейка содержащая артикул РЕХАУ")] string line)
{
if (!ProductSku.TryParse(line, out var skus))
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
else else
{ {
var product = products.First();
return $"{product.Id} {product.Name}";
}
}
[ExcelFunction(Name = "РЕХАУАРТИКУЛ")]
public static object SkuSearch(string query)
{
if (ProductSku.TryParse(query, out var skus))
{
return skus.First().Id;
}
return ExcelError.ExcelErrorNA;
}
[ExcelFunction(Name = "РЕХАУИМЯ")]
public static object GetProductName(string query)
{
if (!ProductSku.TryParse(query, out var skus))
{
return ExcelError.ExcelErrorNA;
}
var article = skus.First().Id; var article = skus.First().Id;
var functionName = nameof(GetProductName); IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
var parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () => if (ExcelAsyncUtil.Run(nameof(РЕХАУИМЯ), line, delegate
{ {
return await databaseClient.GetProducts(article); return databaseClient.GetProducts(article)
.GetAwaiter()
.GetResult();
}) is not IEnumerable<Product> requestResult) }) is not IEnumerable<Product> requestResult)
{ {
return "Загрузка..."; return "Загрузка...";
} }
else if (!requestResult.Any()) else
{
if (!requestResult.Any())
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
@ -80,26 +103,33 @@ public static class RhSolutionsFunctions
return firstProduct.Name; return firstProduct.Name;
} }
} }
}
}
[ExcelFunction(Name = "РЕХАУЦЕНА")] [ExcelFunction(Description = "Поиск цены артикула РЕХАУ в евро")]
public static object GetProductPrice(string query) 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
var functionName = nameof(GetProductPrice);
var parameters = new object[] { article };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{ {
return await databaseClient.GetProducts(article); 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) }) is not IEnumerable<Product> requestResult)
{ {
return "Загрузка..."; return "Загрузка...";
} }
else if (!requestResult.Any()) else
{
if (!requestResult.Any())
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
@ -109,42 +139,65 @@ public static class RhSolutionsFunctions
return Math.Round(firstProduct.Price * 1.2m, 2); 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,
[ExcelArgument(Name = "ДАТА", Description = "Дата в формате Excel (необязательно)")] double dateField)
{ {
if (!ProductSku.TryParse(query, out var skus)) if (!ProductSku.TryParse(line, out var skus))
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
else
{
IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
ICurrencyClient currencyClient = RhSolutionsAddIn.ServiceProvider.GetRequiredService<ICurrencyClient>();
IMemoryCache memoryCache = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IMemoryCache>();
var article = skus.First().Id; var article = skus.First().Id;
DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField); DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField);
if (!memoryCache.TryGetValue(date, out decimal exchangeRate))
var functionName = nameof(GetProductPriceRub);
var parameters = new object[] { date };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{ {
var requestResult = await currencyClient.GetExchangeRate(date); var result = ExcelAsyncUtil.Run(nameof(КУРСЕВРО), dateField, delegate
{
var requestResult = currencyClient.GetExchangeRate(date)
.GetAwaiter()
.GetResult();
return requestResult ?? -1m; return requestResult ?? -1m;
}) is not decimal exchangeRate) });
if (result is not decimal)
{ {
return "Загрузка..."; return "Загрузка...";
} }
else
parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{ {
var products = await databaseClient.GetProducts(article); exchangeRate = (decimal)result;
var product = products.FirstOrDefault(); 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 : return product == null ? -1m :
product.Price * (decimal)exchangeRate * 1.2m; product.Price * exchangeRate * 1.2m;
}) is not decimal requestResult) }) is not decimal requestResult)
{ {
return "Загрузка..."; return "Загрузка...";
} }
else if (requestResult < 0 || exchangeRate < 0) else
{
if (requestResult < 0)
{ {
return ExcelError.ExcelErrorNA; return ExcelError.ExcelErrorNA;
} }
@ -153,4 +206,40 @@ public static class RhSolutionsFunctions
return Math.Round(requestResult, 2); 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
{ {
@ -15,25 +20,20 @@ public class RibbonController : ExcelRibbon
public override string GetCustomUI(string RibbonID) public override string GetCustomUI(string RibbonID)
{ {
return @" return @"
<customUI onLoad='RibbonLoad' xmlns='http://schemas.microsoft.com/office/2006/01/customui' loadImage='LoadImage'> <customUI onLoad='RibbonLoad' xmlns='http://schemas.microsoft.com/office/2006/01/customui'>
<ribbon> <ribbon>
<tabs> <tabs>
<tab id='rau' label='RhSolutions'> <tab id='rau' label='RhSolutions'>
<group id='priceList' label='Прайс-лист'> <group id='priceList' label='Прайс-лист'>
<button id='export' getEnabled='GetExportEnabled' label='Экспорт в новый файл' size='normal' image='RhSolutions' onAction='OnToolPressed'/> <button id='export' getEnabled='GetExportEnabled' label='Экспорт в новый файл' size='normal' imageMso='PivotExportToExcel' onAction='OnToolPressed'/>
<button id='convert' getEnabled='GetConvertEnabled' label='Актуализировать' size='normal' imageMso='FileUpdate' onAction='OnToolPressed'/> <button id='convert' getEnabled='GetConvertEnabled' label='Актуализировать' size='normal' imageMso='FileUpdate' onAction='OnToolPressed'/>
<button id='merge' label='Объединить' size='normal' imageMso='Copy' onAction='OnToolPressed'/> <button id='merge' label='Объединить' size='normal' imageMso='Copy' onAction='OnToolPressed'/>
<button id='guess' getEnabled='GetGuessEnabled' label='Найти и экспортировать' size='large' imageMso='ControlWizards' onAction='OnToolPressed'/> <button id='guess' getEnabled='GetGuessEnabled' label='Найти и экспортировать' size='normal' imageMso='ControlWizards' onAction='OnToolPressed'/>
<button id='fillsleeves' getEnabled='GetSleevesEnabled' label='Подобрать гильзы' size='normal' imageMso='CreateQueryFromWizard' onAction='OnToolPressed'/>
<button id='dxfexport' getEnabled='GetDxfEnabled' label='Экспортировать в DXF' size='normal' imageMso='ExportExcel' onAction='OnToolPressed'/>
</group> </group>
<group id='fittingsCalc' label='Расчет фитингов'> <group id='rausettings' getLabel='GetVersionLabel'>
<button id='fillsleeves' getEnabled='GetFittingsCalcEnabled' label='Гильзы' size='large' image='Sleeve' onAction='OnToolPressed'/> <button id='setPriceList' getLabel='GetPriceListPathLabel' size='large' imageMso='TableExcelSpreadsheetInsert' onAction='OnSetPricePressed'/>
<button id='fillcouplings' getEnabled='GetFittingsCalcEnabled' label='Муфты' size='large' image='Coupling' onAction='OnToolPressed'/>
</group>
<group id='exportTab' label='Экспорт'>
<button id='dxfexport' getEnabled='GetDxfEnabled' label='DXF' size='large' image='DXF' onAction='OnToolPressed'/>
</group>
<group id='settings' getLabel='GetVersionLabel'>
<button id='setPriceList' getLabel='GetPriceListPathLabel' size='large' image='RhSolutions' onAction='OnSetPricePressed'/>
</group> </group>
</tab> </tab>
</tabs> </tabs>
@ -85,7 +85,7 @@ public class RibbonController : ExcelRibbon
public bool GetConvertEnabled(IRibbonControl control) => _workbookIsValid; public bool GetConvertEnabled(IRibbonControl control) => _workbookIsValid;
public bool GetDxfEnabled(IRibbonControl control) => _workbookIsValid; public bool GetDxfEnabled(IRibbonControl control) => _workbookIsValid;
public bool GetFittingsCalcEnabled(IRibbonControl control) => _workbookIsValid; public bool GetSleevesEnabled(IRibbonControl control) => _workbookIsValid;
public bool GetGuessEnabled(IRibbonControl control) => RhSolutionsAddIn.Excel.ActiveWorkbook != null && !_workbookIsValid; public bool GetGuessEnabled(IRibbonControl control) => RhSolutionsAddIn.Excel.ActiveWorkbook != null && !_workbookIsValid;
public bool GetExportEnabled(IRibbonControl control) public bool GetExportEnabled(IRibbonControl control)
@ -109,7 +109,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 +123,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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 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.8.5.0")]
[assembly: AssemblyFileVersion("1.9.5.1")] [assembly: AssemblyFileVersion("1.8.5.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" />
@ -23,8 +23,4 @@
<Reference Path="System.Runtime.CompilerServices.Unsafe.dll" Pack="true" /> <Reference Path="System.Runtime.CompilerServices.Unsafe.dll" Pack="true" />
<Reference Path="System.Threading.Tasks.Extensions.dll" Pack="true" /> <Reference Path="System.Threading.Tasks.Extensions.dll" Pack="true" />
<Reference Path="System.ValueTuple.dll" Pack="true" /> <Reference Path="System.ValueTuple.dll" Pack="true" />
<Image Name='RhSolutions' Path='Images\RhSolutions.png' Pack='true' />
<Image Name='DXF' Path='Images\DXF.png' Pack='true' />
<Image Name='Sleeve' Path='Images\Sleeve.png' Pack='true' />
<Image Name='Coupling' Path='Images\Coupling.png' Pack='true' />
</DnaLibrary> </DnaLibrary>

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,37 +8,39 @@
<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="RhSolutions.ProductSku" Version="1.0.2" />
<PackageReference Include="System.Buffers" Version="4.5.1" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" /> <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RhSolutions.ProductSku\RhSolutions.ProductSku.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Images\Coupling.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\DXF.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\RhSolutions.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\Sleeve.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -1,43 +1,114 @@
using Microsoft.Win32; using System.Configuration;
using System.IO; using System.IO;
namespace RhSolutions.Services; namespace RhSolutions.Services;
public class AddInConfiguration : IAddInConfiguration public class AddInConfiguration : ApplicationSettingsBase, IAddInConfiguration
{ {
private RegistryKey _rootKey; private readonly Dictionary<string, string> _priceListHeaders;
private string _priceListPath;
private Dictionary<string, string> _priceListHeaders;
public event IAddInConfiguration.SettingsHandler OnSettingsChange;
public AddInConfiguration() public AddInConfiguration()
{ {
_rootKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\RhSolutions\RhSolutions-AddIn"); _priceListHeaders = new Dictionary<string, string>()
_priceListPath = (string)_rootKey.GetValue("PriceListPath");
_priceListHeaders = new()
{ {
["Amount"] = "Кол-во", ["Amount"] = AmountHeader,
["OldSku"] = "Прежний материал", ["OldSku"] = OldSkuHeader,
["Sku"] = "Актуальный материал", ["Sku"] = SkuHeader,
["ProductLine"] = "Программа", ["ProductLine"] = ProductLineHeader,
["Name"] = "Наименование", ["Name"] = NameHeader,
["Measure"] = "Ед. изм." ["Measure"] = MeasureHeader
}; };
} }
public string GetPriceListFileName() => Path.GetFileName(_priceListPath); [UserScopedSetting]
[DefaultSettingValue("Кол-во")]
public string AmountHeader
{
get
{
return (string)this[nameof(AmountHeader)];
}
}
[UserScopedSetting]
[DefaultSettingValue("Прежний материал")]
public string OldSkuHeader
{
get
{
return (string)this[nameof(OldSkuHeader)];
}
}
[UserScopedSetting]
[DefaultSettingValue("Актуальный материал")]
public string SkuHeader
{
get
{
return (string)this[nameof(SkuHeader)];
}
}
[UserScopedSetting]
[DefaultSettingValue("Программа")]
public string ProductLineHeader
{
get
{
return (string)this[nameof(ProductLineHeader)];
}
}
[UserScopedSetting]
[DefaultSettingValue("Наименование")]
public string NameHeader
{
get
{
return (string)this[nameof(NameHeader)];
}
}
[UserScopedSetting]
[DefaultSettingValue("Ед. изм.")]
public string MeasureHeader
{
get
{
return (string)this[nameof(MeasureHeader)];
}
}
[UserScopedSetting]
public string PriceListPath
{
get
{
return (string)this[nameof(PriceListPath)];
}
set
{
this[nameof(PriceListPath)] = value;
}
}
public event SettingChangingEventHandler OnSettingsChange
{
add
{
base.SettingChanging += value;
}
remove
{
base.SettingChanging -= value;
}
}
public string GetPriceListFileName() => Path.GetFileName(PriceListPath);
public string GetPriceListPath() => PriceListPath;
public void SetPriceListPath(string value) => PriceListPath = value;
public void SaveSettings() => base.Save();
public Dictionary<string, string> GetPriceListHeaders() => _priceListHeaders; public Dictionary<string, string> GetPriceListHeaders() => _priceListHeaders;
public string GetPriceListPath() => _priceListPath;
public void SaveSettings()
{
_rootKey.SetValue("PriceListPath", _priceListPath);
OnSettingsChange.Invoke();
}
public void SetPriceListPath(string value)
{
_priceListPath = value;
}
} }

View File

@ -1,67 +0,0 @@
using System.Text.RegularExpressions;
namespace RhSolutions.Services;
public class CouplingsCalculator : IFittingsCalculator
{
private static readonly string pattern =
@"(^|\W)труба.*(?'Diameter'16|20|25|32|40|50|63).*(отрезки|бухт[ае])\D*(?'Length'\d{1,3})(\D|$)";
public Dictionary<Product, double> Calculate(Dictionary<Product, double> products)
{
Dictionary<string, double> result = new()
{
["16"] = 0,
["20"] = 0,
["25"] = 0,
["32"] = 0,
["40"] = 0,
["50"] = 0,
["63"] = 0,
};
var rautitanProducts = products.Where(kvp => kvp.Key.ProductLines.Contains("RAUTITAN"));
Regex regex = new(pattern, RegexOptions.Multiline | RegexOptions.IgnoreCase);
foreach (var kvp in rautitanProducts)
{
var match = regex.Match(kvp.Key.Name);
if (match.Success)
{
string diameter = match.Groups["Diameter"].Value;
int packingLength = int.Parse(match.Groups["Length"].Value);
result[diameter] += GetCouplesCount(kvp.Value, packingLength);
}
}
return result
.ToDictionary(kvp =>
kvp.Key switch
{
"16" => new Product("11080111001"),
"20" => new Product("11080121001"),
"25" => new Product("11080131001"),
"32" => new Product("11080141001"),
"40" => new Product("11080151001"),
"50" => new Product("14563021001"),
"63" => new Product("14563031001"),
_ => throw new Exception($"Неизвестный диаметр {kvp.Key}")
}, kvp => kvp.Value);
}
private int GetCouplesCount(double amount, int packingLength)
{
if (amount < packingLength)
{
return 0;
}
else if (amount % packingLength == 0)
{
return (int)amount / packingLength - 1;
}
else
{
return (int)amount / packingLength;
}
}
}

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,19 +8,15 @@ 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}"; string request = $"{_requestAddress}{date.Date:dd/MM/yyyy}";
HttpResponseMessage response = await _httpClient.GetAsync(request); HttpResponseMessage response = await _httpClient.GetAsync(request);
@ -30,13 +25,11 @@ public class CurrencyClient : ICurrencyClient
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
string xml = await response.Content.ReadAsStringAsync(); string xml = await response.Content.ReadAsStringAsync();
XElement valCourses = XElement.Parse(xml); XElement valCourses = XElement.Parse(xml);
exchangeRate = decimal.Parse(valCourses.Elements("Valute")
decimal? exchangeRate = decimal.Parse(valCourses.Elements("Valute")
.Where(e => e.Element("Name").Value == "Евро") .Where(e => e.Element("Name").Value == "Евро")
.FirstOrDefault() .FirstOrDefault()
.Element("Value").Value); .Element("Value").Value);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1));
_memoryCache.Set(date, exchangeRate, cacheEntryOptions);
return exchangeRate; return exchangeRate;
} }
catch catch
@ -45,6 +38,4 @@ public class CurrencyClient : ICurrencyClient
return null; return null;
} }
} }
return exchangeRate;
}
} }

View File

@ -20,12 +20,15 @@ public class DatabaseClient : IDatabaseClient
public async Task<IEnumerable<Product>> GetProducts(string line) public async Task<IEnumerable<Product>> GetProducts(string line)
{ {
string request;
IEnumerable<Product> products;
if (ProductSku.TryParse(line, out var skus)) if (ProductSku.TryParse(line, out var skus))
{ {
ProductSku sku = skus.FirstOrDefault(); ProductSku sku = skus.FirstOrDefault();
string request = @"https://rh.cebotari.ru/api/products/" + sku.ToString(); request = @"https://rh.cebotari.ru/api/products/" + sku.ToString();
if (!_memoryCache.TryGetValue(sku, out IEnumerable<Product> products)) if (!_memoryCache.TryGetValue(sku, out products))
{ {
var response = await _httpClient.GetAsync(request); var response = await _httpClient.GetAsync(request);
@ -38,29 +41,19 @@ public class DatabaseClient : IDatabaseClient
catch catch
{ {
StatusCode = response.StatusCode; StatusCode = response.StatusCode;
return Enumerable.Empty<Product>();
} }
var cacheEntryOptions = new MemoryCacheEntryOptions() var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1)); .SetSlidingExpiration(TimeSpan.FromHours(1));
_memoryCache.Set(sku, products, cacheEntryOptions); _memoryCache.Set(sku, products, cacheEntryOptions);
return products;
}
else
{
return products;
} }
} }
else else
{ {
UriBuilder builder = new(@"https://rh.cebotari.ru/api/search") request = @"https://rh.cebotari.ru/api/search?query=" + line;
{
Query = $"query={line.Replace("&", "%26")}"
};
string request = builder.Uri.AbsoluteUri;
if (!_memoryCache.TryGetValue(line, out IEnumerable<Product> products)) if (!_memoryCache.TryGetValue(line, out products))
{ {
var response = await _httpClient.GetAsync(request); var response = await _httpClient.GetAsync(request);
@ -73,22 +66,18 @@ public class DatabaseClient : IDatabaseClient
catch catch
{ {
StatusCode = response.StatusCode; StatusCode = response.StatusCode;
return Enumerable.Empty<Product>();
} }
var cacheEntryOptions = new MemoryCacheEntryOptions() var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1)); .SetSlidingExpiration(TimeSpan.FromHours(1));
_memoryCache.Set(line, products, cacheEntryOptions); _memoryCache.Set(line, products, cacheEntryOptions);
if (products.Any()) if (products.Count() > 0)
{ {
_memoryCache.Set(products.First(), products, cacheEntryOptions); _memoryCache.Set(products.First(), products, cacheEntryOptions);
} }
}
}
return products; return products;
} }
else
{
return products;
}
}
}
} }

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();
@ -93,28 +93,26 @@ public class ExcelReader : IReader, IDisposable
string wbName = Path.GetFileNameWithoutExtension( string wbName = Path.GetFileNameWithoutExtension(
worksheet.Parent.Name); worksheet.Parent.Name);
Range amountCell = worksheet.Cells.Find(headers["Amount"]); Range AmountCell = worksheet.Cells.Find(headers["Amount"]),
Range headerRow = amountCell.EntireRow; SkuCell = worksheet.Cells.Find(headers["Sku"]),
Range skuCell = headerRow.Find(headers["Sku"]), ProductLineCell = worksheet.Cells.Find(headers["ProductLine"]),
productLineCell = headerRow.Find(headers["ProductLine"]), NameCell = worksheet.Cells.Find(headers["Name"]),
nameCell = headerRow.Find(headers["Name"]), MeasureCell = worksheet.Cells.Find(headers["Measure"]);
measureCell = headerRow.Find(headers["Measure"]); var lastRowIndex = worksheet.Cells[worksheet.Rows.Count, SkuCell.Column]
var lastRowIndex = worksheet.Cells[worksheet.Rows.Count, skuCell.Column]
.End[XlDirection.xlUp].Row; .End[XlDirection.xlUp].Row;
Dictionary<Product, double> readResult = new(); Dictionary<Product, double> readResult = new();
for (int row = amountCell.Row + 1; row <= lastRowIndex; row++) for (int row = AmountCell.Row + 1; row <= lastRowIndex; row++)
{ {
double? amount = worksheet.Cells[row, amountCell.Column].Value2 as double?; double? amount = worksheet.Cells[row, AmountCell.Column].Value2 as double?;
if (amount != null && amount.Value != 0) if (amount != null && amount.Value != 0)
{ {
object productLine = worksheet.Cells[row, productLineCell.Column].Value2; object productLine = worksheet.Cells[row, ProductLineCell.Column].Value2;
object name = worksheet.Cells[row, nameCell.Column].Value2; object name = worksheet.Cells[row, NameCell.Column].Value2;
object sku = worksheet.Cells[row, skuCell.Column].Value2; object sku = worksheet.Cells[row, SkuCell.Column].Value2;
object measure = worksheet.Cells[row, measureCell.Column].Value2; object measure = worksheet.Cells[row, MeasureCell.Column].Value2;
var productMeasure = measure?.ToString() switch var productMeasure = measure?.ToString() switch
{ {
"м" => Measure.M, "м" => Measure.M,
@ -132,7 +130,7 @@ public class ExcelReader : IReader, IDisposable
Product p = new(sku.ToString()) Product p = new(sku.ToString())
{ {
ProductLines = new List<string>() { productLine.ToString() }, ProductLines = new List<string>() { productLine.ToString() },
Name = name.ToString().Split('\n').First(), Name = name.ToString(),
ProductMeasure = productMeasure ProductMeasure = productMeasure
}; };
@ -151,15 +149,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 +168,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;
@ -134,18 +133,11 @@ namespace RhSolutions.Services
cell.AddValue(amount); cell.AddValue(amount);
} }
else else
{
if (amount == 0)
{
cell.Value2 = null;
}
else
{ {
cell.Value2 = amount; cell.Value2 = amount;
} }
} }
} }
}
private void FillAmounts(KeyValuePair<Product, double> positionAmount, params int[] columns) private void FillAmounts(KeyValuePair<Product, double> positionAmount, params int[] columns)
{ {

View File

@ -1,21 +0,0 @@
namespace RhSolutions.Services;
public class FittingsCalculatorFactory
{
private readonly IServiceProvider _serviceProvider;
public FittingsCalculatorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IFittingsCalculator GetFittingsCalculator(string calculatorName)
{
return calculatorName switch
{
"Sleeves" => (IFittingsCalculator)_serviceProvider.GetService(typeof(SleevesCalculator)),
"Couplings" => (IFittingsCalculator)_serviceProvider.GetService(typeof(CouplingsCalculator)),
_ => throw new ArgumentException($"Незвестный интерфейс {nameof(IFittingsCalculator)}: {calculatorName}")
};
}
}

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

@ -1,12 +1,13 @@
namespace RhSolutions.Services; using System.Configuration;
namespace RhSolutions.Services;
public interface IAddInConfiguration public interface IAddInConfiguration
{ {
public string GetPriceListPath(); public string GetPriceListPath();
public void SetPriceListPath(string value);
public string GetPriceListFileName(); public string GetPriceListFileName();
public Dictionary<string, string> GetPriceListHeaders(); public Dictionary<string, string> GetPriceListHeaders();
public delegate void SettingsHandler(); public event SettingChangingEventHandler OnSettingsChange;
public event SettingsHandler OnSettingsChange; public void SetPriceListPath(string value);
public void SaveSettings(); public void SaveSettings();
} }

View File

@ -1,6 +0,0 @@
namespace RhSolutions.Services;
public interface IFittingsCalculator
{
public Dictionary<Product, double> Calculate(Dictionary<Product, double> products);
}

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

@ -0,0 +1,6 @@
namespace RhSolutions.Services;
public interface ISleevesCalculator
{
public Dictionary<Product, double> CalculateSleeves(Dictionary<Product, double> products);
}

View File

@ -2,14 +2,14 @@
namespace RhSolutions.Services; namespace RhSolutions.Services;
public class SleevesCalculator : IFittingsCalculator public class SleevesCalculator : ISleevesCalculator
{ {
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)равнопроходная|угольник 90|угольник 45|Т-образная|Комплект трубок(?i))(.+?(?<Sleeve>\b16\b|\b20\b|\b25\b|\b32\b|\b40\b|\b50\b|\b63\b))+";
private const string singlePattern = private const string singlePattern =
@"((?i)муфта|тройник|переходник|угольник|штуцер|Г-образная|заглушка(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+"; @"((?i)муфта|тройник|переходник|угольник|штуцер|Г-образная|заглушка(?i))(.+?(?<Sleeve>\b16\b|\b20\b|\b25\b|\b32\b|\b40\b|\b50\b|\b63\b))+";
public Dictionary<Product, double> Calculate(Dictionary<Product, double> products) public Dictionary<Product, double> CalculateSleeves(Dictionary<Product, double> products)
{ {
Dictionary<string, double> result = new() Dictionary<string, double> result = new()
{ {
@ -20,11 +20,6 @@ public class SleevesCalculator : IFittingsCalculator
["40"] = 0, ["40"] = 0,
["50"] = 0, ["50"] = 0,
["63"] = 0, ["63"] = 0,
["16PX"] = 0,
["20PX"] = 0,
["25PX"] = 0,
["32PX"] = 0,
["40PX"] = 0
}; };
var rautitanProducts = products.Where(kvp => kvp.Key.ProductLines.Contains("RAUTITAN")); var rautitanProducts = products.Where(kvp => kvp.Key.ProductLines.Contains("RAUTITAN"));
@ -35,16 +30,9 @@ public class SleevesCalculator : IFittingsCalculator
{ {
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}PX"] += kvp.Value * 2;
}
else
{ {
result[sleeve.Value] += kvp.Value * 2; result[sleeve.Value] += kvp.Value * 2;
} }
}
continue; continue;
} }
var singleCollection = Regex.Matches(kvp.Key.Name, singlePattern); var singleCollection = Regex.Matches(kvp.Key.Name, singlePattern);
@ -52,35 +40,23 @@ public class SleevesCalculator : IFittingsCalculator
{ {
CaptureCollection collection = singleCollection[0].Groups["Sleeve"].Captures; CaptureCollection collection = singleCollection[0].Groups["Sleeve"].Captures;
foreach (Capture sleeve in collection) foreach (Capture sleeve in collection)
{
if (kvp.Key.Name.Contains("PX"))
{
result[$"{sleeve.Value}PX"] += kvp.Value;
}
else
{ {
result[sleeve.Value] += kvp.Value; 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"),
"20PX" => new Product("11600021001"),
"25PX" => new Product("11600031001"),
"32PX" => new Product("11600041001"),
"40PX" => new Product("11600051001"),
_ => throw new Exception($"Неизвестный диаметр {kvp.Key}") _ => throw new Exception($"Неизвестный диаметр {kvp.Key}")
}, kvp => kvp.Value); }, kvp => kvp.Value);
} }

View File

@ -19,8 +19,8 @@ internal class ConvertTool : Tool
Application app = RhSolutionsAddIn.Excel.Application; Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet; Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Excel"); _reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(new[] { worksheet });
_writer = _writerFactory.GetWriter("NewPrice"); _writer = _writerFactory.GetWriter("NewPrice");
var products = _reader.ReadProducts(new[] { worksheet });
_writer.WriteProducts(products); _writer.WriteProducts(products);
} }
} }

View File

@ -1,12 +1,20 @@
using RhSolutions.Controllers; using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using RhSolutions.Controllers;
using System.Configuration;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools; namespace RhSolutions.Tools
internal static class EventsUtil
{ {
#if !NET472
[SupportedOSPlatform("windows")]
#endif
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;
@ -28,7 +36,6 @@ internal static class EventsUtil
RibbonController.RefreshControl("dxfexport"); RibbonController.RefreshControl("dxfexport");
RibbonController.RefreshControl("guess"); RibbonController.RefreshControl("guess");
RibbonController.RefreshControl("fillsleeves"); RibbonController.RefreshControl("fillsleeves");
RibbonController.RefreshControl("fillcouplings");
} }
private static void RefreshExportButton(object sh, Range target) private static void RefreshExportButton(object sh, Range target)
@ -36,8 +43,9 @@ internal static class EventsUtil
RibbonController.RefreshControl("export"); RibbonController.RefreshControl("export");
} }
private static void RefreshSettingTitle() private static void RefreshSettingTitle(object sender, SettingChangingEventArgs e)
{ {
RibbonController.RefreshControl("setPriceList"); RibbonController.RefreshControl("setPriceList");
} }
}
} }

View File

@ -17,8 +17,8 @@ internal class ExportTool : Tool
{ {
Application app = RhSolutionsAddIn.Excel.Application; Application app = RhSolutionsAddIn.Excel.Application;
_reader = _readerFactory.GetReader("Excel"); _reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(app.Selection);
_writer = _writerFactory.GetWriter("NewPrice"); _writer = _writerFactory.GetWriter("NewPrice");
var products = _reader.ReadProducts(app.Selection);
_writer.WriteProducts(products); _writer.WriteProducts(products);
} }
} }

View File

@ -1,25 +0,0 @@
namespace RhSolutions.Tools;
internal class FittingsTool : Tool
{
private readonly FittingsCalculatorFactory _factory;
private string _calculatorName;
public FittingsTool(ReaderFactory readerFactory, WriterFactory writerFactory, FittingsCalculatorFactory calculatorFactory, string calculatorName) : base(readerFactory, writerFactory)
{
_factory = calculatorFactory;
_calculatorName = calculatorName;
}
public override void Execute()
{
Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(new[] { worksheet });
var calculator = _factory.GetFittingsCalculator(_calculatorName);
var fittings = calculator.Calculate(products.Select(p => p.Item2).First());
_writer = _writerFactory.GetWriter("CurrentPrice");
_writer.WriteProducts(fittings);
}
}

View File

@ -15,8 +15,8 @@ internal class GuessTool : Tool
Application app = RhSolutionsAddIn.Excel.Application; Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet; Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Guess"); _reader = _readerFactory.GetReader("Guess");
var products = _reader.ReadProducts(new[] { worksheet });
_writer = _writerFactory.GetWriter("NewPrice"); _writer = _writerFactory.GetWriter("NewPrice");
var products = _reader.ReadProducts(new[] { worksheet });
_writer.WriteProducts(products); _writer.WriteProducts(products);
} }
} }

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"); _reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(files);
_writer = _writerFactory.GetWriter("NewPrice"); _writer = _writerFactory.GetWriter("NewPrice");
var products = _reader.ReadProducts(files);
_writer.WriteProducts(products); _writer.WriteProducts(products);
} }
}
} }

View File

@ -0,0 +1,22 @@
namespace RhSolutions.Tools;
internal class SleevesTool : Tool
{
private readonly ISleevesCalculator _sleevesCaluculator;
public SleevesTool(ReaderFactory readerFactory, WriterFactory writerFactory, ISleevesCalculator sleevesCaluculator) : base(readerFactory, writerFactory)
{
_sleevesCaluculator = sleevesCaluculator;
}
public override void Execute()
{
Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Excel");
_writer = _writerFactory.GetWriter("CurrentPrice");
var products = _reader.ReadProducts(new[] { worksheet });
var sleeves = _sleevesCaluculator.CalculateSleeves(products.Select(p => p.Item2).First());
_writer.WriteProducts(sleeves);
}
}

View File

@ -15,12 +15,6 @@ public abstract class StatusbarBase : IDisposable
{ {
protected Application Excel = RhSolutionsAddIn.Excel; protected Application Excel = RhSolutionsAddIn.Excel;
[ExcelFunction(IsHidden = true)]
public static void StatusBarReset()
{
RhSolutionsAddIn.Excel.StatusBar = false;
}
public abstract void Update(); public abstract void Update();
public void Dispose() public void Dispose()

View File

@ -4,7 +4,7 @@ internal class ToolFactory
{ {
static ReaderFactory readerFactory = RhSolutionsAddIn.ServiceProvider.GetService<ReaderFactory>(); static ReaderFactory readerFactory = RhSolutionsAddIn.ServiceProvider.GetService<ReaderFactory>();
static WriterFactory writerFactory = RhSolutionsAddIn.ServiceProvider.GetService<WriterFactory>(); static WriterFactory writerFactory = RhSolutionsAddIn.ServiceProvider.GetService<WriterFactory>();
static FittingsCalculatorFactory fittingsCalculatorFactory = RhSolutionsAddIn.ServiceProvider.GetService<FittingsCalculatorFactory>(); static ISleevesCalculator sleevesCaluculator = RhSolutionsAddIn.ServiceProvider.GetService<ISleevesCalculator>();
public Tool GetTool(string toolName) public Tool GetTool(string toolName)
{ {
@ -15,8 +15,7 @@ internal class ToolFactory
"merge" => new MergeTool(readerFactory, writerFactory), "merge" => new MergeTool(readerFactory, writerFactory),
"dxfexport" => new DxfTool(readerFactory, writerFactory), "dxfexport" => new DxfTool(readerFactory, writerFactory),
"guess" => new GuessTool(readerFactory, writerFactory), "guess" => new GuessTool(readerFactory, writerFactory),
"fillsleeves" => new FittingsTool(readerFactory, writerFactory, fittingsCalculatorFactory, "Sleeves"), "fillsleeves" => new SleevesTool(readerFactory, writerFactory, sleevesCaluculator),
"fillcouplings" => new FittingsTool(readerFactory, writerFactory, fittingsCalculatorFactory, "Couplings"),
_ => throw new Exception($"Неизвестный инструмент {toolName}"), _ => throw new Exception($"Неизвестный инструмент {toolName}"),
}; };
return tool; return tool;

View File

@ -14,32 +14,22 @@ public static class WorksheetExtensions
public static bool IsValidSource(this Worksheet worksheet) public static bool IsValidSource(this Worksheet worksheet)
{ {
Range headerRow; Range amountCell;
Range skuCell;
Range programLineCell;
Range nameCell;
Range measureCell;
string[] fields = pricelistParameters.Values Range[] cells = new[]
.Where(v => v != "Прежний материал")
.ToArray();
var value = worksheet.Cells.Find(fields[0]);
if (value == null)
{ {
return false; amountCell = worksheet.Cells.Find(pricelistParameters["Amount"]),
} skuCell = worksheet.Cells.Find(pricelistParameters["Sku"]),
else programLineCell = worksheet.Cells.Find(pricelistParameters["ProductLine"]),
{ nameCell = worksheet.Cells.Find(pricelistParameters["Name"]),
headerRow = value.EntireRow; measureCell = worksheet.Cells.Find(pricelistParameters["Measure"])
} };
for (int i = 1; i < fields.Length; i++) return cells.All(x => x != null);
{
if (headerRow.Find(fields[i]) == null)
{
return false;
}
}
return true;
} }
public static void AddValue(this Range range, double value) public static void AddValue(this Range range, double value)

View File

@ -1,3 +0,0 @@
namespace RhSolutions.Models;
public enum Measure { Kg, M, M2, P }

View File

@ -1,91 +0,0 @@
using Newtonsoft.Json;
namespace RhSolutions.Models;
public class Product : IDisposable
{
[JsonIgnore]
public string Id
{
get => ProductSku.Id;
set
{
ProductSku = new(value);
}
}
public string Name { get; set; } = string.Empty;
public ProductSku ProductSku { get; set; }
public List<ProductSku> DeprecatedSkus { get; set; } = new();
public List<string> ProductLines { get; set; } = new();
public bool IsOnWarehouse { get; set; } = false;
public Measure ProductMeasure { get; set; }
public double? DeliveryMakeUp { get; set; }
public decimal Price { get; set; }
[JsonConstructor]
public Product(string id,
string name,
ProductSku productSku,
ProductSku[] deprecatedSkus,
string[] productLines,
bool isOnWarehouse,
int productMeasure,
int deliveryMakeUp,
decimal price)
{
Id = id;
Name = name;
ProductSku = productSku;
DeprecatedSkus = deprecatedSkus.ToList();
ProductLines = productLines.ToList();
IsOnWarehouse = isOnWarehouse;
ProductMeasure = (Measure)productMeasure;
DeliveryMakeUp = deliveryMakeUp;
Price = price;
}
public Product(string sku)
{
ProductSku = new(sku);
}
public Product(ProductSku productSku)
{
ProductSku = productSku;
}
public override bool Equals(object? obj)
{
return obj is Product product &&
Name == product.Name &&
EqualityComparer<ProductSku>.Default.Equals(ProductSku, product.ProductSku) &&
DeprecatedSkus.SequenceEqual(product.DeprecatedSkus) &&
ProductLines.SequenceEqual(product.ProductLines) &&
IsOnWarehouse == product.IsOnWarehouse &&
ProductMeasure == product.ProductMeasure &&
DeliveryMakeUp == product.DeliveryMakeUp &&
Price == product.Price;
}
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Name);
hash.Add(ProductSku);
DeprecatedSkus.ForEach(x => hash.Add(x));
ProductLines.ForEach(x => hash.Add(x));
hash.Add(IsOnWarehouse);
hash.Add(ProductMeasure);
hash.Add(DeliveryMakeUp);
hash.Add(Price);
return hash.ToHashCode();
}
public override string ToString()
{
return $"({ProductSku}) {Name}";
}
public void Dispose()
{
}
}

View File

@ -1,194 +0,0 @@
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace RhSolutions.Models;
public class ProductSku
{
[JsonIgnore]
public string Id
{
get
{
return $"1{Article}{Delimiter}{Variant}";
}
set
{
var matches = GetMatches(value);
if (matches.Count == 0)
{
throw new ArgumentException($"Wrong Id: {value}");
}
else
{
var p = GetProducts(matches).First();
_article = p.Article;
_delimiter = p.Delimiter;
_variant = p.Variant;
}
}
}
private const string matchPattern = @"(?<Lead>[1\s]|^|\b)(?<Article>\d{6})(?<Delimiter>[\s13-])(?<Variant>\d{3})(\b|$)";
private string _article;
private string _variant;
private char _delimiter = '1';
[JsonConstructor]
public ProductSku(string article, string delimiter, string variant)
{
_article = article;
_delimiter = delimiter[0];
_variant = variant;
}
public ProductSku(string article, string variant)
{
_article = IsCorrectArticle(article) ? article
: throw new ArgumentException($"Wrong Article: {article}");
_variant = IsCorrectVariant(variant) ? variant
: throw new ArgumentException($"Wrong Variant: {variant}");
}
public ProductSku(string line)
{
var matches = GetMatches(line);
if (matches.Count == 0)
{
throw new ArgumentException($"Wrong new Sku input {line}");
}
else
{
var p = GetProducts(matches).First();
_article = p.Article;
_delimiter = p.Delimiter;
_variant = p.Variant;
}
}
private ProductSku(Match match)
{
_article = match.Groups["Article"].Value;
_delimiter = match.Groups["Delimiter"].Value switch
{
"3" => '3',
_ => '1'
};
_variant = match.Groups["Variant"].Value;
}
public string Article
{
get => _article;
set
{
_article = IsCorrectArticle(value) ? value
: throw new ArgumentException($"Wrong Article: {value}");
}
}
public string Variant
{
get => _variant;
set
{
_variant = IsCorrectVariant(value) ? value
: throw new ArgumentException($"Wrong Variant: {value}");
}
}
public char Delimiter
{
get => _delimiter;
set
{
if (value != '1' || value != '3')
{
throw new ArgumentException($"Wrong Delimiter: {value}");
}
else
{
_delimiter = value;
}
}
}
public static IEnumerable<ProductSku> Parse(string line)
{
MatchCollection matches = GetMatches(line);
if (matches.Count == 0)
{
return Enumerable.Empty<ProductSku>();
}
else
{
return GetProducts(matches);
}
}
public static bool TryParse(string line, out IEnumerable<ProductSku> skus)
{
MatchCollection matches = GetMatches(line);
if (matches.Count == 0)
{
skus = Enumerable.Empty<ProductSku>();
return false;
}
else
{
skus = GetProducts(matches);
return true;
}
}
private static MatchCollection GetMatches(string line)
{
return Regex.Matches(line, matchPattern);
}
private static IEnumerable<ProductSku> GetProducts(MatchCollection matches)
{
foreach (Match match in matches)
{
yield return new ProductSku(match);
}
}
private static bool IsCorrectArticle(string line)
{
return line != null
&& line.Length == 6
&& line.All(c => char.IsDigit(c));
}
private static bool IsCorrectVariant(string line)
{
return line != null
&& line.Length == 3
&& line.All(c => char.IsDigit(c));
}
public override string ToString()
{
return Id;
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
ProductSku other = (ProductSku)obj;
return this.Article == other.Article &&
this.Delimiter == other.Delimiter &&
this.Variant == other.Variant;
}
public override int GetHashCode()
{
HashCode hash = new();
hash.Add(_article);
hash.Add(_variant);
hash.Add(_delimiter);
return hash.ToHashCode();
}
}

View File

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0;net7.0;net8.0</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.3</Version>
<Authors>Serghei Cebotari</Authors>
<Product>RhSolutions Sku</Product>
<Description>Библиотека с классами моделей артикулов для плагинов и приложений RhSolutions</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<RunConfiguration>
<EnvironmentVariables>
<ISTESTING>true</ISTESTING>
</EnvironmentVariables>
</RunConfiguration>
</RunSettings>

View File

@ -13,7 +13,6 @@ public class CanDoGuess : IDisposable
public CanDoGuess() public CanDoGuess()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new(); _addIn = new();
_addIn.AutoOpen(); _addIn.AutoOpen();
_guessReader = new GuessReader(Util.Application); _guessReader = new GuessReader(Util.Application);

View File

@ -1,41 +0,0 @@
using RhSolutions.AddIn;
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class CanFillCouplings : IDisposable
{
private RhSolutionsAddIn _addIn;
private IFittingsCalculator _calculator;
private IReader _reader;
private IWriter _writer;
private Worksheet _worksheet;
public CanFillCouplings()
{
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new();
_addIn.AutoOpen();
_calculator = new CouplingsCalculator();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);
_writer = new CurrentPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_worksheet = Util.Workbook.Worksheets[1];
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationCouplings.xlsx")]
public void CanCalculateSleeves()
{
var products = _reader.ReadProducts(new[] { _worksheet });
var couplings = _calculator.Calculate(products.First().Item2);
_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()
{
_addIn.AutoClose();
}
}

View File

@ -6,14 +6,13 @@ namespace RhSolutions.Tests;
public class CanFillSleeves : IDisposable public class CanFillSleeves : IDisposable
{ {
private RhSolutionsAddIn _addIn; private RhSolutionsAddIn _addIn;
private IFittingsCalculator _calculator; private ISleevesCalculator _calculator;
private IReader _reader; private IReader _reader;
private IWriter _writer; private IWriter _writer;
private Worksheet _worksheet; private Worksheet _worksheet;
public CanFillSleeves() public CanFillSleeves()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new(); _addIn = new();
_addIn.AutoOpen(); _addIn.AutoOpen();
_calculator = new SleevesCalculator(); _calculator = new SleevesCalculator();
@ -26,12 +25,16 @@ public class CanFillSleeves : IDisposable
public void CanCalculateSleeves() public void CanCalculateSleeves()
{ {
var products = _reader.ReadProducts(new[] { _worksheet }); var products = _reader.ReadProducts(new[] { _worksheet });
var sleeves = _calculator.Calculate(products.First().Item2); var sleeves = _calculator.CalculateSleeves(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

@ -12,7 +12,6 @@ public class CanReadProducts : IDisposable
public CanReadProducts() public CanReadProducts()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new(); _addIn = new();
_testWorkbook = Util.Application.Workbooks.Add(); _testWorkbook = Util.Application.Workbooks.Add();
_addIn.AutoOpen(); _addIn.AutoOpen();

View File

@ -12,7 +12,6 @@ public class CanWriteProducts : IDisposable
public CanWriteProducts() public CanWriteProducts()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new(); _addIn = new();
_addIn.AutoOpen(); _addIn.AutoOpen();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration); _reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);

View File

@ -12,7 +12,6 @@ public class RealPricelistTest : IDisposable
public RealPricelistTest() public RealPricelistTest()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new(); _addIn = new();
_addIn.AutoOpen(); _addIn.AutoOpen();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration); _reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);

View File

@ -10,12 +10,11 @@
<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>
<ProjectReference Include="..\RhSolutions.AddIn\RhSolutions.AddIn.csproj" /> <ProjectReference Include="..\RhSolutions.AddIn\RhSolutions.AddIn.csproj" />
<ProjectReference Include="..\RhSolutions.ProductSku\RhSolutions.ProductSku.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,171 +0,0 @@
using RhSolutions.Models;
namespace RhSolutions.Tests;
public class SkuTests
{
[Fact]
public void EqualityTest()
{
Product p1 = new("11600011001")
{
Name = "Test",
ProductLines = new List<string>
{
"TestLine"
},
IsOnWarehouse = true,
ProductMeasure = Measure.Kg,
DeliveryMakeUp = 100.0,
Price = 1000
};
Product p2 = new("11600011001")
{
Name = "Test",
ProductLines = new List<string>
{
"TestLine"
},
IsOnWarehouse = true,
ProductMeasure = Measure.Kg,
DeliveryMakeUp = 100.0,
Price = 1000
};
Product p3 = new("11600013001")
{
Name = "Test",
ProductLines = new List<string>
{
"TestLine"
},
IsOnWarehouse = true,
ProductMeasure = Measure.Kg,
DeliveryMakeUp = 100.0,
Price = 1000
};
Assert.True(p1.Equals(p2));
Assert.False(p1.Equals(p3));
}
[Fact]
public void ProductHashCodeTest()
{
Product p1 = new("11600011001")
{
Name = "Test",
ProductLines = new List<string>
{
"TestLine"
},
IsOnWarehouse = true,
ProductMeasure = Measure.Kg,
DeliveryMakeUp = 100.0,
Price = 1000
};
Product p2 = new("11600011001")
{
Name = "Test",
ProductLines = new List<string>
{
"TestLine"
},
IsOnWarehouse = true,
ProductMeasure = Measure.Kg,
DeliveryMakeUp = 100.0,
Price = 1000
};
Product p3 = new("11600013001")
{
Name = "Test",
ProductLines = new List<string>
{
"TestLine"
},
IsOnWarehouse = true,
ProductMeasure = Measure.Kg,
DeliveryMakeUp = 100.0,
Price = 1000
};
int hash1 = p1.GetHashCode();
int hash2 = p2.GetHashCode();
int hash3 = p3.GetHashCode();
Assert.True(hash1 == hash2);
Assert.False(hash1 == hash3);
}
[Fact]
public void SkuEqualityTest()
{
ProductSku sku1 = new("160001", "001");
ProductSku sku2 = new("11600011001");
Assert.True(sku1.Equals(sku2));
}
[Fact]
public void SkuHashCodeTest()
{
ProductSku sku1 = new("160001", "001");
ProductSku sku2 = new("11600011001");
int hash1 = sku1.GetHashCode();
int hash2 = sku2.GetHashCode();
Assert.True(hash1 == hash2);
}
[Theory]
[InlineData("12222221333")]
[InlineData("222222-333")]
[InlineData("222222 333")]
[InlineData("string 12222221333")]
[InlineData("12222221333 string")]
[InlineData("string 12222221333 string")]
[InlineData("string 222222-333")]
[InlineData("222222-333 string")]
[InlineData("string 222222-333 string")]
[InlineData("string 222222 333")]
[InlineData("222222 333 string")]
[InlineData("string 222222 333 string")]
public void StandardSkuParseTest(string line)
{
Assert.True(ProductSku.TryParse(line, out var skus));
Assert.True(skus.First().Article == "222222"
&& skus.First().Variant == "333"
&& skus.First().Delimiter == '1');
}
[Theory]
[InlineData("12222223444")]
[InlineData("string 12222223444")]
[InlineData("12222223444 string")]
[InlineData("string 12222223444 string")]
public void NewSkuParseTest(string line)
{
Assert.True(ProductSku.TryParse(line, out var skus));
Assert.True(skus.First().Article == "222222"
&& skus.First().Variant == "444"
&& skus.First().Delimiter == '3');
}
[Theory]
[InlineData("160001-001 11384611001 160002 002 11600033003")]
public void MultipleParseTest(string line)
{
Assert.True(ProductSku.TryParse(line, out var skus));
Assert.Equal(4, skus.Count());
}
[Theory]
[InlineData("160001 001")]
[InlineData("160001*001")]
[InlineData("160001001")]
[InlineData("31600011001")]
public void DoesntParse(string line)
{
Assert.False(ProductSku.TryParse(line, out _));
}
}

View File

@ -10,7 +10,6 @@ public class WorkbookValidationTests : IDisposable
public WorkbookValidationTests() public WorkbookValidationTests()
{ {
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new RhSolutionsAddIn(); _addIn = new RhSolutionsAddIn();
_addIn.AutoOpen(); _addIn.AutoOpen();
Util.Application.Workbooks.Add(); Util.Application.Workbooks.Add();

View File

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhSolutions.AddIn", "RhSolu
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhSolutions.Tests", "RhSolutions.Tests\RhSolutions.Tests.csproj", "{6EECCDDB-741C-404A-874F-BB8656265162}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhSolutions.Tests", "RhSolutions.Tests\RhSolutions.Tests.csproj", "{6EECCDDB-741C-404A-874F-BB8656265162}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.ProductSku", "RhSolutions.ProductSku\RhSolutions.ProductSku.csproj", "{59CD05D0-71E0-4027-968A-8BE89A6FDCEF}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -23,10 +21,6 @@ Global
{6EECCDDB-741C-404A-874F-BB8656265162}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EECCDDB-741C-404A-874F-BB8656265162}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EECCDDB-741C-404A-874F-BB8656265162}.Release|Any CPU.ActiveCfg = Release|Any CPU {6EECCDDB-741C-404A-874F-BB8656265162}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EECCDDB-741C-404A-874F-BB8656265162}.Release|Any CPU.Build.0 = Release|Any CPU {6EECCDDB-741C-404A-874F-BB8656265162}.Release|Any CPU.Build.0 = Release|Any CPU
{59CD05D0-71E0-4027-968A-8BE89A6FDCEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{59CD05D0-71E0-4027-968A-8BE89A6FDCEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{59CD05D0-71E0-4027-968A-8BE89A6FDCEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{59CD05D0-71E0-4027-968A-8BE89A6FDCEF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE