Compare commits

...

30 Commits

Author SHA1 Message Date
Sergey Chebotar
c638085ac7 Version update 2023-04-07 07:30:42 +03:00
Sergey Chebotar
c86f0b8143 Refactoring 2023-04-07 07:27:42 +03:00
Sergey Chebotar
75cb3c4f5d Dispose if not null 2023-04-07 07:25:47 +03:00
Sergey Chebotar
0a7d9646f3 Fix dispose null reference exception 2023-04-07 07:25:03 +03:00
Sergey Chebotar
dfd0db9133 Fix bar disposing 2023-04-07 07:19:49 +03:00
Sergey Chebotar
f7eec55b4a DNA Pack fix 2023-04-07 07:19:31 +03:00
Sergey Chebotar
b1a9959c1a Remove net7.0 target 2023-04-07 07:19:00 +03:00
Sergey Chebotar
0aa9d644df PackageReference Include Fix 2023-04-07 07:18:42 +03:00
Sergey Chebotar
93195a9cc9 Add Tests 2023-04-06 21:38:11 +03:00
Sergey Chebotar
99f46e089c Remove IntelliSence 2023-04-06 21:38:01 +03:00
Sergey Chebotar
51c97607ef Move parameters to field 2023-04-06 21:37:21 +03:00
Sergey Chebotar
8bcc8d34d4 Make Addin static properties setter private 2023-04-06 21:36:24 +03:00
Sergey Chebotar
02a6c7151f Move Extensions to Tools 2023-04-06 16:40:00 +03:00
Sergey Chebotar
bf97036724 Rename Source validation extension 2023-04-06 08:49:12 +03:00
Sergey Chebotar
bfd7702939 DI Refactoring 2023-04-06 08:29:39 +03:00
Sergey Chebotar
448af8ecd7 Move Excel extensions to own project 2023-04-01 15:58:42 +03:00
Sergey Chebotar
da29243d1d Add Pricelist Headers to configuration 2023-04-01 15:24:04 +03:00
Sergey Chebotar
f01228d945 Add Excel Table classes 2023-03-31 15:27:31 +03:00
Sergey Chebotar
cdb153c988 Usings simplify 2023-03-28 10:36:36 +03:00
Sergey Chebotar
2280b49ae1 Add Usings 2023-03-28 10:03:19 +03:00
Sergey Chebotar
de2f7f43ec Switch to Excel internal file dialogs 2023-03-28 09:58:43 +03:00
Sergey Chebotar
7bb6ce6c5a Application Singleton fix 2023-03-28 07:55:45 +03:00
Sergey Chebotar
6555d6343f nameof fix 2023-03-28 07:33:32 +03:00
Sergey Chebotar
68512dba2b Add Application Singleton 2023-03-28 07:33:13 +03:00
Sergey Chebotar
15dd27fb68 Add Configuration Interface 2023-03-28 07:25:10 +03:00
Sergey Chebotar
9846a3c35e Edit DatabaseClient 2023-03-27 15:52:28 +03:00
Sergey Chebotar
19007ff43e Add IDatabaseClient DI 2023-03-27 13:55:58 +03:00
Sergey Chebotar
a3094fe275 Add DLLs to packed 2023-03-27 08:23:22 +03:00
Sergey Chebotar
f34690828c Remove dependency 2023-03-27 08:23:05 +03:00
Sergey Chebotar
6291d30027 Add HttpClient DI Injection 2023-03-27 07:12:04 +03:00
61 changed files with 1726 additions and 1180 deletions

View File

@ -1,56 +0,0 @@
using ExcelDna.Integration;
using RhSolutions.Models;
using RhSolutions.Services;
using System.Linq;
namespace RhSolutions.AddIn
{
public class Functions
{
[ExcelFunction(Description = "Распознать артикул и попробовать найти его в прайс-листе")]
public static object RHSOLUTIONS([ExcelArgument(Name = "\"Строка с названием материала\"")] string line)
{
object result;
result = ExcelAsyncUtil.Run("Database request", line, delegate
{
return RhDatabaseClient.GetProduct(line).GetAwaiter().GetResult();
});
string parsedSku = Sku.TryParse(line, out var skus)
? skus.First().ToString() : string.Empty;
if (result == null)
{
if (string.IsNullOrEmpty(parsedSku))
{
return ExcelError.ExcelErrorNA;
}
else
{
return skus.First().ToString();
}
}
if (result.Equals(ExcelError.ExcelErrorNA))
{
if (string.IsNullOrEmpty(parsedSku))
{
return "Загрузка...";
}
else
{
return skus.First().ToString();
}
}
return result;
}
[ExcelFunction]
public static void _ResetStatusBar()
{
RhSolutionsAddIn.Excel.StatusBar = false;
}
}
}

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,35 +1,42 @@
using ExcelDna.Integration; using System.Net;
using ExcelDna.IntelliSense; #if !NET472
using Microsoft.Office.Interop.Excel; using System.Runtime.Versioning;
using RhSolutions.Services; #endif
using System.Net;
using System.Net.Http;
namespace RhSolutions.AddIn namespace RhSolutions.AddIn;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public sealed class RhSolutionsAddIn : IExcelAddIn
{ {
class RhSolutionsAddIn : IExcelAddIn
{
public static Application Excel { get; private set; } public static Application Excel { get; private set; }
public static HttpClient HttpClient { get; private set; } public static ServiceProvider ServiceProvider { get; private set; }
public static IAddInConfiguration Configuration { get; private set; }
public void AutoOpen() public void AutoOpen()
{ {
Excel = (Application)ExcelDnaUtil.Application; IServiceCollection Services = new ServiceCollection();
HttpClient = new HttpClient();
IntelliSenseServer.Install();
RegistryUtil.Initialize();
EventsUtil.Initialize();
Services.AddHttpClient()
.AddSingleton((Application)ExcelDnaUtil.Application)
.AddSingleton<IDatabaseClient, RhDatabaseClient>()
.AddSingleton<IAddInConfiguration, RhAddInConfiguration>()
.AddTransient<IFileDialog, ExcelFileDialog>()
.AddTransient<IExcelReader, RhExcelReader>()
.AddTransient<IExcelWriter, RhExcelWriter>();
ServiceProvider = Services.BuildServiceProvider();
Configuration = ServiceProvider.GetService<IAddInConfiguration>();
Excel = ServiceProvider.GetService<Application>();
EventsUtil.Initialize();
ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12; SecurityProtocolType.Tls12;
} }
public void AutoClose() public void AutoClose()
{ {
IntelliSenseServer.Uninstall();
RegistryUtil.Uninitialize();
EventsUtil.Uninitialize(); EventsUtil.Uninitialize();
HttpClient.Dispose();
}
} }
} }

View File

@ -0,0 +1,55 @@
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.AddIn;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public class RhSolutionsFunction
{
[ExcelFunction(Description = "Распознать артикул и попробовать найти его в прайс-листе")]
public static object RHSOLUTIONS([ExcelArgument(Name = "\"Строка с названием материала\"")] string line)
{
IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
Sku.TryParse(line, out var skus);
if (ExcelAsyncUtil.Run("Database request", line, delegate
{
return databaseClient.GetProducts(line)
.GetAwaiter()
.GetResult();
}) is not IEnumerable<Product> requestResult)
{
if (skus.Any())
{
return $"{skus.First()} ...";
}
else
{
return "Загрузка...";
}
}
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}";
}
}
}
}

View File

@ -1,57 +0,0 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using Dialog = RhSolutions.Models.Dialog;
using Range = Microsoft.Office.Interop.Excel.Range;
namespace RhSolutions.Controllers
{
internal class CombineTool : ToolBase
{
private List<SourcePriceList> SourceFiles { get; set; }
public CombineTool()
{
string[] files = Dialog.GetMultiplyFiles();
if (files != null)
{
SourceFiles = SourcePriceList.GetSourceLists(files);
}
else
{
throw new Exception("Не выбраны файлы");
}
}
public override void FillTarget()
{
using (ProgressBar = new ProgressBar("Заполняю строки...", SourceFiles.Sum(file => file.PositionAmount.Count)))
using (ResultBar = new ResultBar())
{
foreach (SourcePriceList source in SourceFiles)
{
TargetFile.Sheet.Columns[TargetFile.AmountCell.Column]
.EntireColumn
.Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow);
Range newColumnHeader = TargetFile.Sheet.Cells[TargetFile.AmountCell.Row, TargetFile.AmountCell.Column - 1];
newColumnHeader.Value2 = $"{source.Name}";
newColumnHeader.WrapText = true;
foreach (var kvp in source.PositionAmount)
{
FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column - 1, TargetFile.AmountCell.Column);
ProgressBar.Update();
}
}
FilterByAmount();
ResultBar.Update();
}
}
}
}

View File

@ -1,30 +0,0 @@
using RhSolutions.Models;
namespace RhSolutions.Controllers
{
internal class ConvertTool : ToolBase
{
private SourcePriceList Current { get; set; }
public ConvertTool()
{
Current = new SourcePriceList(ExcelApp.ActiveWorkbook);
}
public override void FillTarget()
{
using (ProgressBar = new ProgressBar("Заполняю строки...", Current.PositionAmount.Count))
using (ResultBar = new ResultBar())
{
foreach (var kvp in Current.PositionAmount)
{
FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column);
ProgressBar.Update();
}
FilterByAmount();
ResultBar.Update();
}
}
}
}

View File

@ -1,101 +0,0 @@
using Microsoft.Office.Interop.Excel;
using System;
using System.Collections.Generic;
using RhSolutions.Models;
using System.Linq;
using Range = Microsoft.Office.Interop.Excel.Range;
namespace RhSolutions.Controllers
{
internal class ExportTool : ToolBase
{
private Dictionary<Product, double> PositionAmount;
private readonly Range Selection;
public ExportTool()
{
Selection = ExcelApp.Selection;
GetSelected();
if (PositionAmount.Count == 0)
{
throw new Exception("В выделенном диапазоне не найдены позиции для экспорта");
}
}
public override void FillTarget()
{
using (ProgressBar = new ProgressBar("Заполняю строки...", PositionAmount.Count))
using (ResultBar = new ResultBar())
{
foreach (var kvp in PositionAmount)
{
FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column);
ProgressBar.Update();
}
FilterByAmount();
ResultBar.Update();
}
}
private void GetSelected()
{
object[,] cells = Selection.Value2;
PositionAmount = new Dictionary<Product, double>();
int rowsCount = Selection.Rows.Count;
for (int row = 1; row <= rowsCount; row++)
{
if (cells[row, 1] == null || cells[row, 2] == null)
continue;
string sku = null;
double? amount = null;
for (int column = 1; column <= 2; column++)
{
object current = cells[row, column];
if (Sku.TryParse(current.ToString(), out var rauSku))
{
sku = rauSku.FirstOrDefault().ToString() ?? null;
}
else if (current.GetType() == typeof(string)
&& double.TryParse(current.ToString(), out _))
{
amount = double.Parse((string)current);
}
else if (current.GetType() == typeof(double))
{
amount = (double)current;
}
}
if (sku == null || amount == null)
{
continue;
}
Product position = new Product
{
ProductSku = sku
};
if (PositionAmount.ContainsKey(position))
{
PositionAmount[position] += amount.Value;
}
else
{
PositionAmount.Add(position, amount.Value);
}
}
}
}
}

View File

@ -1,46 +0,0 @@
using RhSolutions.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace RhSolutions.Controllers
{
internal class MergeTool : ToolBase
{
private List<SourcePriceList> SourceFiles { get; set; }
public MergeTool()
{
string[] files = Dialog.GetMultiplyFiles();
if (files != null)
{
SourceFiles = SourcePriceList.GetSourceLists(files);
}
else
{
throw new Exception("Не выбраны файлы");
}
}
public override void FillTarget()
{
using (ProgressBar = new ProgressBar("Заполняю строки...", SourceFiles.Sum(x => x.PositionAmount.Count)))
using (ResultBar = new ResultBar())
{
foreach (SourcePriceList source in SourceFiles)
{
foreach (var kvp in source.PositionAmount)
{
FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column);
ProgressBar.Update();
}
}
FilterByAmount();
ResultBar.Update();
}
}
}
}

View File

@ -1,19 +1,19 @@
using ExcelDna.Integration.CustomUI; using ExcelDna.Integration.CustomUI;
using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using RhSolutions.Services;
using System;
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;
using Range = Microsoft.Office.Interop.Excel.Range;
namespace RhSolutions.Controllers namespace RhSolutions.Controllers;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
[ComVisible(true)]
public class RibbonController : ExcelRibbon
{ {
[ComVisible(true)]
public class RibbonController : ExcelRibbon
{
private static IRibbonUI ribbonUi; private static IRibbonUI ribbonUi;
public override string GetCustomUI(string RibbonID) public override string GetCustomUI(string RibbonID)
@ -26,10 +26,7 @@ namespace RhSolutions.Controllers
<group id='priceList' label='Прайс-лист'> <group id='priceList' label='Прайс-лист'>
<button id='export' getEnabled='GetExportEnabled' label='Экспорт в новый файл' size='normal' imageMso='PivotExportToExcel' 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'/>
<menu id='conjoinMenu' label='Объединить' imageMso='Copy'> <button id='merge' label='Объединить' size='normal' imageMso='Copy' onAction='OnToolPressed'/>
<button id='merge' label='Сложить' onAction='OnToolPressed'/>
<button id='combine' label='По колонкам' onAction='OnToolPressed'/>
</menu>
</group> </group>
<group id='rausettings' getLabel='GetVersionLabel'> <group id='rausettings' getLabel='GetVersionLabel'>
<button id='setPriceList' getLabel='GetPriceListPathLabel' size='large' imageMso='TableExcelSpreadsheetInsert' onAction='OnSetPricePressed'/> <button id='setPriceList' getLabel='GetPriceListPathLabel' size='large' imageMso='TableExcelSpreadsheetInsert' onAction='OnSetPricePressed'/>
@ -47,18 +44,18 @@ namespace RhSolutions.Controllers
public static void RefreshControl(string id) public static void RefreshControl(string id)
{ {
if (ribbonUi != null) ribbonUi?.InvalidateControl(id);
{
ribbonUi.InvalidateControl(id);
}
} }
public void OnSetPricePressed(IRibbonControl control) public void OnSetPricePressed(IRibbonControl control)
{ {
string path = Models.Dialog.GetFilePath(); IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetService<IFileDialog>();
string file = dialog.GetFile();
if (!string.IsNullOrEmpty(path)) if (!string.IsNullOrEmpty(file))
{ {
RegistryUtil.PriceListPath = path; RhSolutionsAddIn.Configuration.SetPriceListPath(file);
RhSolutionsAddIn.Configuration.SaveSettings();
} }
} }
@ -66,27 +63,14 @@ namespace RhSolutions.Controllers
{ {
try try
{ {
ToolBase tool; using ToolBase tool = control.Id switch
switch (control.Id)
{ {
case "export": "export" => new ExportTool(),
tool = new ExportTool(); "convert" => new ConvertTool(),
break; "merge" => new MergeTool(),
case "convert": _ => throw new Exception("Неизвестный инструмент"),
tool = new ConvertTool(); };
break; tool.Execute();
case "merge":
tool = new MergeTool();
break;
case "combine":
tool = new CombineTool();
break;
default:
throw new Exception("Неизвестный инструмент");
}
tool.OpenNewPrice();
tool.FillTarget();
} }
catch (Exception exception) catch (Exception exception)
@ -108,7 +92,7 @@ namespace RhSolutions.Controllers
else else
{ {
Worksheet worksheet = RhSolutionsAddIn.Excel.ActiveWorkbook.ActiveSheet; Worksheet worksheet = RhSolutionsAddIn.Excel.ActiveWorkbook.ActiveSheet;
return worksheet.IsRehauSource(); return worksheet.IsValidSource();
} }
} }
@ -132,8 +116,7 @@ namespace RhSolutions.Controllers
public string GetPriceListPathLabel(IRibbonControl control) public string GetPriceListPathLabel(IRibbonControl control)
{ {
string name = RegistryUtil.GetPriceListName(); string name = RhSolutionsAddIn.Configuration.GetPriceListFileName();
return string.IsNullOrEmpty(name) ? "Нет файла шаблона!" : name; return string.IsNullOrEmpty(name) ? "Шаблонный файл" : name;
}
} }
} }

View File

@ -1,190 +0,0 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using RhSolutions.Models;
using RhSolutions.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using Range = Microsoft.Office.Interop.Excel.Range;
namespace RhSolutions.Controllers
{
internal abstract class ToolBase
{
protected Application ExcelApp = RhSolutionsAddIn.Excel;
protected TargetPriceList TargetFile { get; set; }
protected ResultBar ResultBar { get; set; }
protected ProgressBar ProgressBar { get; set; }
public abstract void FillTarget();
public void OpenNewPrice()
{
if (ExcelApp.Workbooks
.Cast<Workbook>()
.FirstOrDefault(w => w.FullName == RegistryUtil.PriceListPath) != null)
{
throw new ArgumentException("Шаблонный файл редактируется в другом месте");
}
Workbook wb = ExcelApp.Workbooks.Open(RegistryUtil.PriceListPath, null, true);
try
{
TargetFile = new TargetPriceList(wb);
}
catch (Exception exception)
{
if (wb != null)
{
wb.Close();
}
throw exception;
}
}
protected void FillPositionAmountToColumns(KeyValuePair<Product, double> positionAmount, params int[] columns)
{
Range worksheetCells = TargetFile.Sheet.Cells;
Range skuColumn = TargetFile.SkuCell.EntireColumn;
int? row = GetPositionRow(skuColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine);
if (row != null)
{
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
ResultBar.IncrementSuccess();
return;
}
if (TargetFile.OldSkuCell != null)
{
row = GetPositionRow(TargetFile.OldSkuCell.EntireColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine);
if (row != null)
{
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
ResultBar.IncrementReplaced();
return;
}
}
string sku = positionAmount.Key.ProductSku.Substring(1, 6);
row = GetPositionRow(skuColumn, sku, positionAmount.Key.ProductLine);
if (row != null)
{
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
ResultBar.IncrementReplaced();
return;
}
FillMissing(positionAmount, columns);
ResultBar.IncrementNotFound();
}
protected void FillMissing(KeyValuePair<Product, double> positionAmount, params int[] columns)
{
Range worksheetCells = TargetFile.Sheet.Cells;
Range worksheetRows = TargetFile.Sheet.Rows;
int skuColumn = TargetFile.SkuCell.Column;
int groupColumn = TargetFile.GroupCell.Column;
int nameColumn = TargetFile.NameCell.Column;
int row = worksheetCells[worksheetRows.Count, skuColumn]
.End[XlDirection.xlUp]
.Row + 1;
worksheetRows[row]
.EntireRow
.Insert(XlInsertShiftDirection.xlShiftDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
Range previous = worksheetRows[row - 1];
Range current = worksheetRows[row];
previous.Copy(current);
current.ClearContents();
worksheetCells[row, groupColumn].Value2 = positionAmount.Key.ProductLine;
worksheetCells[row, nameColumn].Value2 = positionAmount.Key.Name;
if (TargetFile.OldSkuCell != null)
{
worksheetCells[row, skuColumn].Value2 = "Не найден";
worksheetCells[row, TargetFile.OldSkuCell.Column].Value2 = positionAmount.Key.ProductSku;
}
else
{
worksheetCells[row, skuColumn].Value2 = positionAmount.Key.ProductSku;
}
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
}
protected int? GetPositionRow(Range range, string sku, string group)
{
Range found = range.Find(sku);
string foundGroupValue;
if (found == null)
{
return null;
}
int firstFoundRow = found.Row;
if (string.IsNullOrEmpty(group))
{
return found.Row;
}
while (true)
{
foundGroupValue = TargetFile.Sheet.Cells[found.Row, TargetFile.GroupCell.Column].Value2.ToString();
if (group.Equals(foundGroupValue))
{
return found.Row;
}
found = range.FindNext(found);
if (found.Row == firstFoundRow)
{
return null;
}
}
}
protected void FilterByAmount()
{
AutoFilter filter = TargetFile.Sheet.AutoFilter;
int startColumn = filter.Range.Column;
filter.Range.AutoFilter(TargetFile.AmountCell.Column - startColumn + 1, "<>0", XlAutoFilterOperator.xlAnd, "<>");
TargetFile.Sheet.Range["A1"].Activate();
}
}
}

View File

@ -1,40 +0,0 @@
using Microsoft.Office.Interop.Excel;
using System.Collections.Generic;
using System.Windows.Forms;
namespace RhSolutions.Models
{
static class Dialog
{
public static string GetFilePath()
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "Файлы Excel (*.xls;*.xlsx;*.xlsm)|*.xls;*.xlsx;*.xlsm";
if (dialog.ShowDialog() == DialogResult.OK)
{
return dialog.FileName;
}
else return string.Empty;
}
}
public static string[] GetMultiplyFiles()
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "Файлы Excel (*.xls;*.xlsx;*.xlsm)|*.xls;*.xlsx;*.xlsm";
dialog.Multiselect = true;
if (dialog.ShowDialog() == DialogResult.OK)
{
return dialog.FileNames;
}
else return null;
}
}
}
}

View File

@ -1,15 +0,0 @@
using Microsoft.Office.Interop.Excel;
namespace RhSolutions.Models
{
internal abstract class PriceListBase
{
public Range AmountCell { get; protected set; }
public Range SkuCell { get; protected set; }
public Range GroupCell { get; protected set; }
public Range NameCell { get; protected set; }
public Worksheet Sheet { get; protected set; }
public string Name { get; protected set; }
}
}

View File

@ -1,11 +0,0 @@
namespace RhSolutions.Models
{
internal static class PriceListHeaders
{
public static readonly string Amount = "Кол-во";
public static readonly string OldSku = "Прежний материал";
public static readonly string Sku = "Актуальный материал";
public static readonly string Group = "Программа";
public static readonly string Name = "Наименование";
}
}

View File

@ -1,22 +0,0 @@
namespace RhSolutions.Models
{
internal class ProgressBar : StatusbarBase
{
private double CurrentProgress { get; set; }
private readonly double TaskWeight;
private readonly string Message;
public ProgressBar(string message, int weight)
{
Message = message;
TaskWeight = weight;
CurrentProgress = 0;
}
public override void Update()
{
double percent = ++CurrentProgress / TaskWeight * 100;
Excel.StatusBar = $"{Message} Выполнено {percent:#.#} %";
}
}
}

View File

@ -1,45 +0,0 @@
using System;
using System.Text;
namespace RhSolutions.Models
{
internal class ResultBar : StatusbarBase
{
private int Success { get; set; }
private int Replaced { get; set; }
private int NotFound { get; set; }
public ResultBar()
{
Success = 0;
Replaced = 0;
NotFound = 0;
}
public void IncrementSuccess() => Success++;
public void IncrementReplaced() => Replaced++;
public void IncrementNotFound() => NotFound++;
public override void Update()
{
StringBuilder sb = new StringBuilder();
if (Success > 0)
{
sb.Append($"Успешно экспортировано {Success} артикулов. ");
}
if (Replaced > 0)
{
sb.Append($"Заменено {Replaced} артикулов. ");
}
if (NotFound > 0)
{
sb.Append($"Не найдено {NotFound} артикулов.");
}
Excel.StatusBar = sb.ToString();
}
}
}

View File

@ -1,116 +0,0 @@
using ExcelDna.Integration;
using Microsoft.Office.Interop.Excel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Range = Microsoft.Office.Interop.Excel.Range;
namespace RhSolutions.Models
{
internal class SourcePriceList : PriceListBase
{
public Dictionary<Product, double> PositionAmount { get; private set; }
public SourcePriceList(Workbook workbook)
{
if (workbook == null)
{
throw new ArgumentException($"Нет рабочего файла");
}
Sheet = workbook.ActiveSheet;
Name = Path.GetFileNameWithoutExtension(workbook.FullName);
Range[] cells = new[]
{
AmountCell = Sheet.Cells.Find(PriceListHeaders.Amount),
SkuCell = Sheet.Cells.Find(PriceListHeaders.Sku),
GroupCell = Sheet.Cells.Find(PriceListHeaders.Group),
NameCell = Sheet.Cells.Find(PriceListHeaders.Name)
};
if (cells.Any(x => x == null))
{
throw new ArgumentException($"Файл {Name} не распознан");
}
CreatePositionsDict();
}
public static List<SourcePriceList> GetSourceLists(string[] files)
{
var ExcelApp = (Application)ExcelDnaUtil.Application;
ProgressBar bar = new ProgressBar("Открываю исходные файлы...", files.Length);
List<SourcePriceList> sourceFiles = new List<SourcePriceList>();
foreach (string file in files)
{
ExcelApp.ScreenUpdating = false;
Workbook wb = ExcelApp.Workbooks.Open(file);
try
{
SourcePriceList priceList = new SourcePriceList(wb);
sourceFiles.Add(priceList);
wb.Close();
bar.Update();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show
(ex.Message,
"Ошибка открытия исходного прайс-листа",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Information);
wb.Close();
bar.Update();
}
ExcelApp.ScreenUpdating = true;
}
return sourceFiles;
}
private void CreatePositionsDict()
{
PositionAmount = new Dictionary<Product, double>();
for (int row = AmountCell.Row + 1; row <= Sheet.Cells[Sheet.Rows.Count, AmountCell.Column].End[XlDirection.xlUp].Row; row++)
{
double? amount = Sheet.Cells[row, AmountCell.Column].Value2 as double?;
if (amount != null && amount.Value != 0)
{
object group = Sheet.Cells[row, GroupCell.Column].Value2;
object name = Sheet.Cells[row, NameCell.Column].Value2;
object sku = Sheet.Cells[row, SkuCell.Column].Value2;
if (group == null || name == null || sku == null)
continue;
if (!Sku.TryParse(sku.ToString(), out _))
continue;
Product p = new Product
{
ProductSku = sku.ToString(),
ProductLine = group.ToString(),
Name = name.ToString()
};
if (PositionAmount.ContainsKey(p))
{
PositionAmount[p] += amount.Value;
}
else
{
PositionAmount.Add(p, amount.Value);
}
}
}
}
}
}

View File

@ -1,18 +0,0 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using System;
namespace RhSolutions.Models
{
internal abstract class StatusbarBase : IDisposable
{
protected Application Excel = RhSolutionsAddIn.Excel;
public abstract void Update();
public void Dispose()
{
Excel.OnTime(DateTime.Now + new TimeSpan(0, 0, 5), "_ResetStatusBar");
}
}
}

View File

@ -1,41 +0,0 @@
using Microsoft.Office.Interop.Excel;
using System;
using System.IO;
using System.Linq;
using Range = Microsoft.Office.Interop.Excel.Range;
namespace RhSolutions.Models
{
internal class TargetPriceList : PriceListBase
{
public Range OldSkuCell { get; private set; }
public TargetPriceList(Workbook workbook)
{
if (workbook == null)
{
throw new ArgumentException("Невозможно открыть книгу шаблонного файла. " +
"Возможно открыт файл с именем, совпадающим с именем шаблонного файла.");
}
Sheet = workbook.ActiveSheet;
Name = Path.GetFileNameWithoutExtension(workbook.FullName);
Range[] cells = new[]
{
AmountCell = Sheet.Cells.Find(PriceListHeaders.Amount),
SkuCell = Sheet.Cells.Find(PriceListHeaders.Sku),
GroupCell = Sheet.Cells.Find(PriceListHeaders.Group),
NameCell = Sheet.Cells.Find(PriceListHeaders.Name)
};
OldSkuCell = Sheet.Cells.Find(PriceListHeaders.OldSku);
if (cells.Any(x => x == null))
{
throw new ArgumentException($"Шаблон {Name} не является прайс-листом");
}
}
}
}

View File

@ -1,41 +0,0 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.Models;
using System.Linq;
namespace RhSolutions.Services
{
public static class WorksheetExtensions
{
public static bool IsRehauSource(this Worksheet worksheet)
{
Range amountCell;
Range skuCell;
Range groupCell;
Range nameCell;
Range[] cells = new[]
{
amountCell = worksheet.Cells.Find(PriceListHeaders.Amount),
skuCell = worksheet.Cells.Find(PriceListHeaders.Sku),
groupCell = worksheet.Cells.Find(PriceListHeaders.Group),
nameCell = worksheet.Cells.Find(PriceListHeaders.Name)
};
return cells.All(x => x != null);
}
public static void AddValue(this Range range, double value)
{
if (range.Value2 == null)
{
range.Value2 = value;
}
else
{
range.Value2 += value;
}
}
}
}

View File

@ -1,7 +1,5 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
@ -33,5 +31,5 @@ using System.Runtime.Versioning;
// 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.2.4.0")] [assembly: AssemblyVersion("1.5.0.0")]
[assembly: AssemblyFileVersion("1.2.4.0")] [assembly: AssemblyFileVersion("1.5.0.0")]

View File

@ -1,26 +1,22 @@
<?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.IntelliSense.dll" Pack="true" /> <Reference Path="Microsoft.Bcl.AsyncInterfaces.dll" Pack="true" />
<Reference Path="Microsoft.Bcl.HashCode.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.DependencyInjection.Abstractions.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.DependencyInjection.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Http.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Logging.Abstractions.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Logging.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Options.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Primitives.dll" Pack="true" />
<Reference Path="Newtonsoft.Json.dll" Pack="true" /> <Reference Path="Newtonsoft.Json.dll" Pack="true" />
<Reference Path="RhSolutions.Sku.dll" Pack="true" /> <Reference Path="RhSolutions.Sku.dll" Pack="true" />
<Reference Path="System.Buffers.dll" Pack="true" />
<Reference Path="System.Diagnostics.DiagnosticSource.dll" Pack="true" />
<!-- <Reference Path="System.Memory.dll" Pack="true" />
The RuntimeVersion attribute above allows only the following setting: <Reference Path="System.Numerics.Vectors.dll" Pack="true" />
* RuntimeVersion="v4.0" - for .NET 4.5 or higher <Reference Path="System.Runtime.CompilerServices.Unsafe.dll" Pack="true" />
<Reference Path="System.Threading.Tasks.Extensions.dll" Pack="true" />
You can have IntelliSense (autocomplete) and validation for this file. <Reference Path="System.ValueTuple.dll" Pack="true" />
See https://github.com/Excel-DNA/ExcelDna/tree/master/Distribution/XmlSchemas/
Additional referenced assemblies can be specified by adding 'Reference' tags.
These libraries will not be examined and registered with Excel as add-in libraries,
but will be packed into the -packed.xll file and loaded at runtime as needed.
For example:
<Reference Path="Another.Library.dll" Pack="true" />
Excel-DNA also allows the XML for ribbon UI extensions to be specified in the .dna file.
See the main Excel-DNA site at https://excel-dna.net for downloads of the full distribution.
-->
</DnaLibrary> </DnaLibrary>

View File

@ -1,9 +1,9 @@
<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.AddIn</RootNamespace> <RootNamespace>RhSolutions</RootNamespace>
<AssemblyName>RhSolutions.AddIn</AssemblyName> <AssemblyName>RhSolutions.AddIn</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
@ -12,14 +12,31 @@
<PropertyGroup> <PropertyGroup>
<StartupObject /> <StartupObject />
</PropertyGroup> </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>
<ItemGroup> <ItemGroup>
<PackageReference Include="ExcelDna.AddIn" Version="1.6.0" /> <PackageReference Include="ExcelDna.AddIn" Version="1.6.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="ExcelDna.Integration" Version="1.6.0" /> <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="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<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.Http" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RhSolutions.Sku" Version="0.1.1" /> <PackageReference Include="RhSolutions.Sku" Version="0.1.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,35 +0,0 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using RhSolutions.Controllers;
namespace RhSolutions.Services
{
internal static class EventsUtil
{
private static readonly Application Excel = RhSolutionsAddIn.Excel;
public static void Initialize()
{
Excel.SheetSelectionChange += RefreshExportButton;
Excel.SheetActivate += RefreshConvertButton;
Excel.WorkbookActivate += RefreshConvertButton;
}
public static void Uninitialize()
{
Excel.SheetSelectionChange -= RefreshExportButton;
Excel.SheetActivate -= RefreshConvertButton;
Excel.WorkbookActivate -= RefreshConvertButton;
}
private static void RefreshConvertButton(object sh)
{
RibbonController.RefreshControl("convert");
}
private static void RefreshExportButton(object sh, Range target)
{
RibbonController.RefreshControl("export");
}
}
}

View File

@ -0,0 +1,45 @@
namespace RhSolutions.Services;
public class ExcelFileDialog : IFileDialog
{
private Application _application;
public ExcelFileDialog(Application application)
{
_application = application;
}
public string GetFile()
{
var dialog = _application.FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker];
dialog.AllowMultiSelect = false;
dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm");
if (dialog.Show() == -1)
{
return dialog.SelectedItems.Item(1);
}
else return string.Empty;
}
public string[] GetFiles()
{
var dialog = _application.FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker];
dialog.AllowMultiSelect = true;
dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm");
if (dialog.Show() == -1)
{
List<string> files = new();
foreach (string file in dialog.SelectedItems)
{
files.Add(file);
}
return files.ToArray();
}
else return Array.Empty<string>();
}
}

View File

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

View File

@ -0,0 +1,11 @@
using RhSolutions.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace RhSolutions.Services
{
public interface IDatabaseClient
{
public Task<IEnumerable<Product>> GetProducts(string query);
}
}

View File

@ -0,0 +1,9 @@
namespace RhSolutions.Services;
public interface IExcelReader : IDisposable
{
public Dictionary<Product, double> ReadProducts(Range range);
public List<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets);
public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files);
new void Dispose();
}

View File

@ -0,0 +1,8 @@
namespace RhSolutions.Services;
public interface IExcelWriter : IDisposable
{
public void WriteProducts(IEnumerable<(string, Dictionary<Product, double>)> products);
public void WriteProducts(Dictionary<Product, double> products);
new void Dispose();
}

View File

@ -0,0 +1,7 @@
namespace RhSolutions.Services;
public interface IFileDialog
{
public string[] GetFiles();
public string GetFile();
}

View File

@ -1,74 +0,0 @@
using Microsoft.Win32;
using RhSolutions.Controllers;
using RhSolutions.Models;
using System;
using System.IO;
using System.Windows.Forms;
namespace RhSolutions.Services
{
static class RegistryUtil
{
private static string priceListPath;
private static RegistryKey RootKey { get; set; }
public static void Initialize()
{
RootKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\REHAU\SkuAssist");
priceListPath = RootKey.GetValue("PriceListPath") as string;
}
public static void Uninitialize()
{
RootKey.Close();
}
public static string PriceListPath
{
get
{
if (string.IsNullOrEmpty(priceListPath) || !File.Exists(priceListPath))
{
DialogResult result = MessageBox.Show("Прайс-лист отсутствует или неверный файл шаблона прайс-листа. " +
"Укажите файл шаблона прайс-листа.",
"Нет файла шаблона",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
if (result == DialogResult.OK)
{
string fileName = Dialog.GetFilePath();
if (string.IsNullOrEmpty(fileName))
{
throw new Exception("Нет файла шаблона");
}
priceListPath = fileName;
RootKey.SetValue("PriceListPath", fileName);
return priceListPath;
}
else
throw new Exception("Нет файла шаблона");
}
else
{
return priceListPath;
}
}
set
{
priceListPath = value;
RootKey.SetValue("PriceListPath", value);
RibbonController.RefreshControl("setPriceList");
}
}
public static string GetPriceListName()
{
return Path.GetFileName(priceListPath);
}
}
}

View File

@ -0,0 +1,103 @@
using System.Configuration;
using System.IO;
namespace RhSolutions.Services;
public class RhAddInConfiguration : ApplicationSettingsBase, IAddInConfiguration
{
private readonly Dictionary<string, string> _priceListHeaders;
public RhAddInConfiguration()
{
_priceListHeaders = new Dictionary<string, string>()
{
["Amount"] = AmountHeader,
["OldSku"] = OldSkuHeader,
["Sku"] = SkuHeader,
["ProductLine"] = ProductLineHeader,
["Name"] = NameHeader
};
}
[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]
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;
}

View File

@ -1,17 +1,21 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using RhSolutions.AddIn; using System.Net;
using RhSolutions.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace RhSolutions.Services namespace RhSolutions.Services;
public class RhDatabaseClient : IDatabaseClient
{ {
public static class RhDatabaseClient private readonly IServiceProvider serviceProvider;
public HttpStatusCode StatusCode { get; private set; }
public RhDatabaseClient(IServiceProvider provider)
{ {
public static async Task<object> GetProduct(string line) this.serviceProvider = provider;
}
public async Task<IEnumerable<Product>> GetProducts(string line)
{ {
string request; string request;
@ -25,28 +29,21 @@ namespace RhSolutions.Services
request = @"https://rh.cebotari.ru/api/search?query=" + line; request = @"https://rh.cebotari.ru/api/search?query=" + line;
} }
var response = await RhSolutionsAddIn.HttpClient.GetAsync(request); var client = serviceProvider.GetRequiredService<HttpClient>();
var response = await client.GetAsync(request);
try try
{ {
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync(); string json = await response.Content.ReadAsStringAsync();
var product = JsonConvert.DeserializeObject<IEnumerable<Product>>(json) return JsonConvert.DeserializeObject<IEnumerable<Product>>(json) ?? Enumerable.Empty<Product>();
.FirstOrDefault(); }
if (product == null)
{
return null;
}
else
{
return $"{product.ProductSku} {product.Name}";
}
}
catch catch
{ {
return $"Ошибка сервера {response.StatusCode}"; StatusCode = response.StatusCode;
}
} }
return Enumerable.Empty<Product>();
} }
} }

View File

@ -0,0 +1,168 @@
using System.IO;
#if !NET472
using System.Runtime.Versioning;
using RhSolutions.Tools;
#endif
namespace RhSolutions.Services;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public class RhExcelReader : IExcelReader, IDisposable
{
private ProgressBar _progressBar;
private readonly Dictionary<string, string> headers;
private readonly Application _application;
public RhExcelReader(Application application, IAddInConfiguration configuration)
{
_application = application;
headers = configuration.GetPriceListHeaders();
}
public Dictionary<Product, double> ReadProducts(Range range)
{
object[,] cells = range.Value2;
Dictionary<Product, double> readResult = new();
for (int row = 1; row <= range.Rows.Count; row++)
{
if (cells[row, 1] == null || cells[row, 2] == null)
continue;
string currentSku = null;
double? currentAmount = null;
for (int column = 1; column <= 2; column++)
{
object currentCell = cells[row, column];
if (Sku.TryParse(currentCell.ToString(), out var validSku))
{
currentSku = validSku.FirstOrDefault().ToString() ?? null;
}
else if (currentCell.GetType() == typeof(string)
&& double.TryParse(currentCell.ToString(), out _))
{
currentAmount = double.Parse((string)currentCell);
}
else if (currentCell.GetType() == typeof(double))
{
currentAmount = (double)currentCell;
}
}
if (currentSku == null || currentAmount == null)
{
continue;
}
Product product = new() { ProductSku = currentSku };
if (readResult.ContainsKey(product))
{
readResult[product] += currentAmount.Value;
}
else
{
readResult.Add(product, currentAmount.Value);
}
}
return readResult;
}
public List<(string, Dictionary<Product, double>)>
ReadProducts(IEnumerable<Worksheet> worksheets)
{
List<(string, Dictionary<Product, double>)> result = new();
foreach (Worksheet worksheet in worksheets)
{
if (!worksheet.IsValidSource())
{
continue;
}
string wbName = Path.GetFileNameWithoutExtension(
worksheet.Parent.Name);
Range AmountCell = worksheet.Cells.Find(headers["Amount"]),
SkuCell = worksheet.Cells.Find(headers["Sku"]),
ProductLineCelll = worksheet.Cells.Find(headers["ProductLine"]),
NameCell = worksheet.Cells.Find(headers["Name"]);
var lastRowIndex = worksheet.Cells[worksheet.Rows.Count, AmountCell.Column]
.End[XlDirection.xlUp].Row;
Dictionary<Product, double> readResult = new();
for (int row = AmountCell.Row + 1; row <= lastRowIndex; row++)
{
double? amount = worksheet.Cells[row, AmountCell.Column].Value2 as double?;
if (amount != null && amount.Value != 0)
{
object programLine = worksheet.Cells[row, ProductLineCelll.Column].Value2;
object name = worksheet.Cells[row, NameCell.Column].Value2;
object sku = worksheet.Cells[row, SkuCell.Column].Value2;
if (programLine == null || name == null || sku == null)
continue;
if (!Sku.TryParse(sku.ToString(), out _))
continue;
Product p = new()
{
ProductSku = sku.ToString(),
ProductLine = programLine.ToString(),
Name = name.ToString()
};
if (readResult.ContainsKey(p))
{
readResult[p] += amount.Value;
}
else
{
readResult.Add(p, amount.Value);
}
}
}
result.Add((wbName, readResult));
}
return result;
}
public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files)
{
_progressBar = new("Открываю исходные файлы...", files.Length);
List<Worksheet> worksheets = new();
_application.ScreenUpdating = false;
foreach (string file in files)
{
Workbook wb = _application.Workbooks.Open(file);
worksheets.Add(wb.ActiveSheet);
_progressBar.Update();
}
_application.ScreenUpdating = true;
var result = ReadProducts(worksheets);
foreach (var ws in worksheets)
{
ws.Parent.Close();
}
return result;
}
public void Dispose()
{
_progressBar?.Dispose();
}
}

View File

@ -0,0 +1,255 @@
#if !NET472
using System.Runtime.Versioning;
using RhSolutions.Tools;
#endif
namespace RhSolutions.Services;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public class RhExcelWriter : IExcelWriter, IDisposable
{
private readonly Application _application;
private Worksheet _worksheet;
private readonly ResultBar _resultBar;
private readonly Dictionary<string, string> _headers;
private readonly string _pricelistPath;
private ProgressBar _progressBar;
private Range _amountCell,
_skuCell,
_programLineCell,
_nameCell,
_oldSkuCell;
public RhExcelWriter(Application application, IAddInConfiguration configuration)
{
_application = application;
_pricelistPath = configuration.GetPriceListPath();
_resultBar = new();
_headers = configuration.GetPriceListHeaders();
}
public void WriteProducts(Dictionary<Product, double> products)
{
WriteProducts(new[] { (string.Empty, products) });
}
public void WriteProducts(IEnumerable<(string, Dictionary<Product, double>)> products)
{
_worksheet = OpenNewPrice();
if (!_worksheet.IsValidSource())
{
_application.ActiveWorkbook.Close();
throw new ArgumentException(
$"Целевой файл {_application.ActiveWorkbook.Name} не является прайс-листом.");
}
_amountCell = _worksheet.Cells.Find(_headers["Amount"]);
_skuCell = _worksheet.Cells.Find(_headers["Sku"]);
_programLineCell = _worksheet.Cells.Find(_headers["ProductLine"]);
_nameCell = _worksheet.Cells.Find(_headers["Name"]);
_oldSkuCell = _worksheet.Cells.Find(_headers["OldSku"]);
_progressBar = new("Заполняю строки...", products
.Select(p => p.Item2)
.Sum(set => set.Count));
if (products.Count() == 1)
{
foreach (var kvp in products.First().Item2)
{
FillPositionAmountToColumns(kvp, _amountCell.Column);
_progressBar.Update();
}
FilterByAmount();
_resultBar.Update();
}
else
{
foreach (var product in products)
{
_worksheet.Columns[_amountCell.Column]
.EntireColumn
.Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow);
Range newColumnHeader = _worksheet.Cells[_amountCell.Row, _amountCell.Column - 1];
newColumnHeader.Value2 = $"{product.Item1}";
newColumnHeader.WrapText = true;
foreach (var kvp in product.Item2)
{
FillPositionAmountToColumns(kvp, _amountCell.Column - 1, _amountCell.Column);
_progressBar.Update();
}
}
FilterByAmount();
_resultBar.Update();
}
}
private Worksheet OpenNewPrice()
{
if (_application.Workbooks
.Cast<Workbook>()
.FirstOrDefault(w => w.FullName == _pricelistPath) != null)
{
throw new ArgumentException("Шаблонный файл редактируется в другом месте");
}
return _application.Workbooks.Open(_pricelistPath, null, true).ActiveSheet;
}
private void FillPositionAmountToColumns(KeyValuePair<Product, double> positionAmount, params int[] columns)
{
Range worksheetCells = _worksheet.Cells;
Range skuColumn = _skuCell.EntireColumn;
int? row = GetPositionRow(skuColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine);
if (row != null)
{
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
_resultBar.IncrementSuccess();
return;
}
if (_oldSkuCell != null)
{
row = GetPositionRow(_oldSkuCell.EntireColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine);
if (row != null)
{
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
_resultBar.IncrementReplaced();
return;
}
}
string sku = positionAmount.Key.ProductSku.Substring(1, 6);
row = GetPositionRow(skuColumn, sku, positionAmount.Key.ProductLine);
if (row != null)
{
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
_resultBar.IncrementReplaced();
return;
}
FillMissing(positionAmount, columns);
_resultBar.IncrementNotFound();
}
private void FillMissing(KeyValuePair<Product, double> positionAmount, params int[] columns)
{
Range worksheetCells = _worksheet.Cells;
Range worksheetRows = _worksheet.Rows;
int skuColumn = _skuCell.Column;
int groupColumn = _programLineCell.Column;
int nameColumn = _nameCell.Column;
int row = worksheetCells[worksheetRows.Count, skuColumn]
.End[XlDirection.xlUp]
.Row + 1;
worksheetRows[row]
.EntireRow
.Insert(XlInsertShiftDirection.xlShiftDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove);
Range previous = worksheetRows[row - 1];
Range current = worksheetRows[row];
previous.Copy(current);
current.ClearContents();
worksheetCells[row, groupColumn].Value2 = positionAmount.Key.ProductLine;
worksheetCells[row, nameColumn].Value2 = positionAmount.Key.Name;
if (_oldSkuCell != null)
{
worksheetCells[row, skuColumn].Value2 = "Не найден";
worksheetCells[row, _oldSkuCell.Column].Value2 = positionAmount.Key.ProductSku;
}
else
{
worksheetCells[row, skuColumn].Value2 = positionAmount.Key.ProductSku;
}
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(positionAmount.Value);
}
}
private int? GetPositionRow(Range range, string sku, string group)
{
Range found = range.Find(sku);
string foundGroupValue;
if (found == null)
{
return null;
}
int firstFoundRow = found.Row;
if (string.IsNullOrEmpty(group))
{
return found.Row;
}
while (true)
{
foundGroupValue = _worksheet.Cells[found.Row, _programLineCell.Column].Value2.ToString();
if (group.Equals(foundGroupValue))
{
return found.Row;
}
found = range.FindNext(found);
if (found.Row == firstFoundRow)
{
return null;
}
}
}
private void FilterByAmount()
{
AutoFilter filter = _worksheet.AutoFilter;
int startColumn = filter.Range.Column;
filter.Range.AutoFilter(_amountCell.Column - startColumn + 1, "<>0", XlAutoFilterOperator.xlAnd, "<>");
_worksheet.Range["A1"].Activate();
}
public void Dispose()
{
_progressBar?.Dispose();
_resultBar?.Dispose();
}
}

View File

@ -0,0 +1,20 @@
using RhSolutions.AddIn;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class ConvertTool : ToolBase
{
public override void Execute()
{
Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
var products = _reader.ReadProducts(new[] { worksheet });
_writer.WriteProducts(products);
}
}

View File

@ -0,0 +1,47 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using RhSolutions.Controllers;
using System.Configuration;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools
{
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal static class EventsUtil
{
public static void Initialize()
{
RhSolutionsAddIn.Excel.SheetSelectionChange += RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate += RefreshConvertButton;
RhSolutionsAddIn.Excel.WorkbookActivate += RefreshConvertButton;
RhSolutionsAddIn.Configuration.OnSettingsChange += RefreshSettingTitle;
}
public static void Uninitialize()
{
RhSolutionsAddIn.Excel.SheetSelectionChange -= RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate -= RefreshConvertButton;
RhSolutionsAddIn.Excel.WorkbookActivate -= RefreshConvertButton;
RhSolutionsAddIn.Configuration.OnSettingsChange -= RefreshSettingTitle;
}
private static void RefreshConvertButton(object sh)
{
RibbonController.RefreshControl("convert");
}
private static void RefreshExportButton(object sh, Range target)
{
RibbonController.RefreshControl("export");
}
private static void RefreshSettingTitle(object sender, SettingChangingEventArgs e)
{
RibbonController.RefreshControl("setPriceList");
}
}
}

View File

@ -0,0 +1,20 @@
using RhSolutions.AddIn;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class ExportTool : ToolBase
{
public override void Execute()
{
Application app = RhSolutionsAddIn.Excel.Application;
var products = _reader.ReadProducts(app.Selection);
_writer.WriteProducts(products);
}
}

View File

@ -0,0 +1,23 @@
#if !NET472
using System.Runtime.Versioning;
using RhSolutions;
using RhSolutions.Models;
using RhSolutions.Tools;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class MergeTool : ToolBase
{
public override void Execute()
{
IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IFileDialog>();
string[] files = dialog.GetFiles();
var products = _reader.ReadProducts(files);
_writer.WriteProducts(products);
}
}

View File

@ -0,0 +1,28 @@
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class ProgressBar : StatusbarBase
{
private double CurrentProgress { get; set; }
private readonly double TaskWeight;
private readonly string Message;
public ProgressBar(string message, int weight)
{
Message = message;
TaskWeight = weight;
CurrentProgress = 0;
}
public override void Update()
{
double percent = ++CurrentProgress / TaskWeight * 100;
Excel.StatusBar = $"{Message} Выполнено {percent:#.#} %";
}
}

View File

@ -0,0 +1,49 @@
using System.Text;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class ResultBar : StatusbarBase
{
private int Success { get; set; }
private int Replaced { get; set; }
private int NotFound { get; set; }
public ResultBar()
{
Success = 0;
Replaced = 0;
NotFound = 0;
}
public void IncrementSuccess() => Success++;
public void IncrementReplaced() => Replaced++;
public void IncrementNotFound() => NotFound++;
public override void Update()
{
StringBuilder sb = new StringBuilder();
if (Success > 0)
{
sb.Append($"Успешно экспортировано {Success} артикулов. ");
}
if (Replaced > 0)
{
sb.Append($"Заменено {Replaced} артикулов. ");
}
if (NotFound > 0)
{
sb.Append($"Не найдено {NotFound} артикулов.");
}
Excel.StatusBar = sb.ToString();
}
}

View File

@ -0,0 +1,24 @@
#if !NET472
using System.Runtime.Versioning;
using RhSolutions;
using RhSolutions.Models;
using RhSolutions.Tools;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal abstract class StatusbarBase : IDisposable
{
protected Application Excel = RhSolutionsAddIn.Excel;
public abstract void Update();
public void Dispose()
{
Excel.OnTime(DateTime.Now + new TimeSpan(0, 0, 5), "StatusBarReset");
}
}

View File

@ -0,0 +1,28 @@
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal abstract class ToolBase : IDisposable
{
protected readonly IExcelReader _reader;
protected readonly IExcelWriter _writer;
public ToolBase()
{
_reader = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IExcelReader>();
_writer = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IExcelWriter>();
}
public void Dispose()
{
_reader.Dispose();
_writer.Dispose();
}
public abstract void Execute();
}

View File

@ -0,0 +1,46 @@
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public static class WorksheetExtensions
{
private static readonly Dictionary<string, string> pricelistParameters =
RhSolutionsAddIn.Configuration.GetPriceListHeaders();
public static bool IsValidSource(this Worksheet worksheet)
{
Range amountCell;
Range skuCell;
Range programLineCell;
Range nameCell;
Range[] cells = new[]
{
amountCell = worksheet.Cells.Find(pricelistParameters["Amount"]),
skuCell = worksheet.Cells.Find(pricelistParameters["Sku"]),
programLineCell = worksheet.Cells.Find(pricelistParameters["ProductLine"]),
nameCell = worksheet.Cells.Find(pricelistParameters["Name"])
};
return cells.All(x => x != null);
}
public static void AddValue(this Range range, double value)
{
if (range.Value2 == null)
{
range.Value2 = value;
}
else
{
range.Value2 += value;
}
}
}

View File

@ -0,0 +1,11 @@
global using ExcelDna.Integration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Office.Interop.Excel;
global using RhSolutions.AddIn;
global using RhSolutions.Models;
global using RhSolutions.Services;
global using RhSolutions.Tools;
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using Range = Microsoft.Office.Interop.Excel.Range;

View File

@ -0,0 +1,26 @@
namespace RhSolutions.ExcelExtensions;
public sealed class Cell
{
public Table ParentTable { get; }
public Row ParentRow
{
get => ParentTable.Rows[ParentTable.Range.Row - _range.Row];
}
public Column ParentColumn
{
get => ParentTable.Columns[ParentTable.Range.Column - _range.Column];
}
public object Value
{
get => _range.Cells[1, 1].Value2;
set => _range.Cells[1, 1].Value2 = value;
}
private Range _range;
public Cell(Range range, Table table)
{
_range = range;
ParentTable = table;
}
}

View File

@ -0,0 +1,65 @@
namespace RhSolutions.ExcelExtensions;
public sealed class Column
{
public Table ParentTable { get; }
public string Header
{
get => _range.Cells[1, 1].Value2.ToString() ?? String.Empty;
}
public int Index
{
get => _range.Column - ParentTable.Range.Column;
}
public int Length
{
get => _range.Rows.Count;
}
private Cell[] _cells;
private readonly Range _range;
public Column(Range range, Table table)
{
_cells = new Cell[range.Rows.Count];
_range = range;
ParentTable = table ??
throw new ArgumentNullException("table");
}
public Cell this[int index]
{
get
{
if (_cells[index] == null)
{
_cells[index] = new Cell(_range.Cells[index + 1, 1], ParentTable);
return _cells[index];
}
else
{
return _cells[index];
}
}
set
{
if (_cells[index] == null)
{
_cells[index] = new Cell(_range.Cells[index + 1, 1], ParentTable);
_cells[index].Value = value;
}
else
{
_cells[index].Value = value;
}
}
}
public Column AddLeft()
{
_range.EntireColumn
.Insert(XlInsertShiftDirection.xlShiftToRight,
XlInsertFormatOrigin.xlFormatFromRightOrBelow);
return ParentTable.Columns[this.Index - 1];
}
}

View File

@ -0,0 +1,49 @@
using System.Collections;
namespace RhSolutions.ExcelExtensions;
public class Columns : IEnumerable<Column>
{
public Table ParentTable { get; }
public int Length
{
get => _range.Columns.Count;
}
private Column[] _columns;
private Range _range;
public Columns(Table parentTable)
{
ParentTable = parentTable;
_range = parentTable.Range;
_columns = new Column[Length];
}
public Column this[int index]
{
get
{
if (index < 0 || index >= Length)
{
throw new IndexOutOfRangeException();
}
if (_columns[index] == null)
{
_columns[index] = new Column(_range.Columns[index + 1], ParentTable);
return _columns[index];
}
else
{
return _columns[index];
}
}
}
public IEnumerator<Column> GetEnumerator()
{
return new ColumnsEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -0,0 +1,52 @@
using System.Collections;
namespace RhSolutions.ExcelExtensions;
public class ColumnsEnumerator: IEnumerator<Column>
{
private Columns _columns;
private int position = -1;
object IEnumerator.Current
{
get
{
return Current;
}
}
public Column Current
{
get
{
try
{
return _columns[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
public ColumnsEnumerator(Columns columns)
{
_columns = columns;
}
public bool MoveNext()
{
position++;
return (position < _columns.Length);
}
public void Reset()
{
position = -1;
}
public void Dispose()
{
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0-windows7.0</TargetFrameworks>
<LangVersion>10</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ExcelDna.Interop" Version="15.0.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,54 @@
namespace RhSolutions.ExcelExtensions;
public sealed class Row
{
public Table ParentTable { get; }
public int Index
{
get => _range.Row - ParentTable.Range.Row;
}
public int Length
{
get => _range.Columns.Count;
}
private readonly Cell[] _cells;
private readonly Range _range;
public Row(Range range, Table table)
{
_cells = new Cell[range.Columns.Count];
_range = range;
ParentTable = table ??
throw new ArgumentNullException("table");
}
public Cell this[int index]
{
get
{
if (index < 0 || index >= Length)
{
throw new IndexOutOfRangeException();
}
if (_cells[index] == null)
{
_cells[index] = new Cell(_range.Cells[1, index + 1], ParentTable);
return _cells[index];
}
else
{
return _cells[index];
}
}
}
public Cell this[string header]
{
get
{
int columnIndex = ParentTable.ColumnByHeader(header).Index;
return this[columnIndex];
}
}
}

View File

@ -0,0 +1,44 @@
using System.Collections;
namespace RhSolutions.ExcelExtensions;
public class Rows : IEnumerable<Row>
{
public Table ParentTable { get; }
public int Length
{
get => _range.Rows.Count;
}
private Row[] _rows;
private Range _range;
public Rows(Table parentTable)
{
ParentTable = parentTable;
_range = parentTable.Range;
_rows = new Row[Length];
}
public Row this[int index]
{
get
{
if (_rows[index] == null)
{
_rows[index] = new Row(_range.Rows[index + 1], ParentTable);
return _rows[index];
}
else
{
return _rows[index];
}
}
}
public IEnumerator<Row> GetEnumerator()
{
return new RowsEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -0,0 +1,52 @@
using System.Collections;
namespace RhSolutions.ExcelExtensions;
public class RowsEnumerator : IEnumerator<Row>
{
private Rows _rows;
private int position = -1;
object IEnumerator.Current
{
get
{
return Current;
}
}
public Row Current
{
get
{
try
{
return _rows[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
public RowsEnumerator(Rows rows)
{
_rows = rows;
}
public bool MoveNext()
{
position++;
return (position < _rows.Length);
}
public void Reset()
{
position = -1;
}
public void Dispose()
{
}
}

View File

@ -0,0 +1,64 @@
namespace RhSolutions.ExcelExtensions;
public class Table
{
public Range Range { get; protected set; }
public Table ParentTable { get; protected set; }
public Rows Rows { get; }
public Columns Columns { get; }
private Dictionary<string, Column> _columnsByHeader;
public Table(Range range)
{
Range = range;
ParentTable = this;
Rows = new Rows(this);
Columns = new Columns(this);
_columnsByHeader = new();
foreach(var column in Columns)
{
if (_columnsByHeader.ContainsKey(column.Header))
{
throw new ArgumentException($"Заголовок столбца {column.Header} не уникален");
}
else
{
_columnsByHeader.Add(column.Header, column);
}
}
}
public Column ColumnByHeader(string header)
{
return _columnsByHeader[header];
}
public Cell this[int row, int column]
{
get => this.Rows[row][column];
}
public IEnumerable<Cell> Search(object item)
{
Range firstFound = Range.Find(item);
if (firstFound == null)
{
yield break;
}
Range nextFound = firstFound;
while (true)
{
yield return this[nextFound.Row - ParentTable.Range.Row, nextFound.Column - ParentTable.Range.Column];
nextFound = Range.FindNext(nextFound);
if (nextFound.Row == firstFound.Row
&& nextFound.Column == firstFound.Column)
{
yield break;
}
}
}
}

View File

@ -0,0 +1,3 @@
global using Microsoft.Office.Interop.Excel;
global using System.Collections.Generic;
global using Range = Microsoft.Office.Interop.Excel.Range;

View File

@ -0,0 +1,63 @@
using Microsoft.Extensions.DependencyInjection;
using RhSolutions.AddIn;
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class CanReadProducts : IDisposable
{
private RhSolutionsAddIn _addIn;
private IExcelReader _reader;
private Workbook _testWorkbook;
public CanReadProducts()
{
_addIn = new();
_testWorkbook = Util.Application.Workbooks.Add();
_addIn.AutoOpen();
_reader = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IExcelReader>();
}
[ExcelFact]
public void CanReadRange()
{
Worksheet worksheet = _testWorkbook.Sheets[1];
worksheet.Range["A1"].Value = "11600011001";
worksheet.Range["A2"].Value = "11600011001";
worksheet.Range["A3"].Value = "160002-001";
worksheet.Range["A4"].Value = "Fuzz";
worksheet.Range["B1"].Value = 10;
worksheet.Range["B2"].Value = 10;
worksheet.Range["B3"].Value = 5;
worksheet.Range["B5"].Value = 1000_000;
worksheet.Range["A6"].Value = "111111-111";
worksheet.Range["B6"].Value = 100;
Range testRange = worksheet.Range["A1:B6"];
var products = _reader.ReadProducts(testRange);
Assert.NotNull(products);
Assert.NotEmpty(products);
Assert.Equal("11600011001", products.First().Key.ProductSku);
Assert.Equal(20.0, products.First().Value);
Assert.Equal(125.0, products.Sum(p => p.Value));
Assert.Equal(3, products.Count());
}
[ExcelFact(Workbook = @"TestWorkbooks\Specifications\HeatingFloor.xlsx")]
public void CanReadWorkbook()
{
Worksheet worksheet = Util.Workbook.Worksheets[1];
var products = _reader.ReadProducts(new[] { worksheet });
Assert.NotNull(products);
Assert.NotEmpty(products);
}
public void Dispose()
{
_addIn.AutoClose();
Util.Application.ActiveWindow.Close(SaveChanges: false);
}
}

View File

@ -24,6 +24,12 @@
<None Update="TestWorkbooks\EmptyWorkbook.xlsx"> <None Update="TestWorkbooks\EmptyWorkbook.xlsx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="TestWorkbooks\ExcelTableTest.xlsx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestWorkbooks\Specifications\HeatingFloor.xlsx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,29 +0,0 @@
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class WorkbookCheck : IDisposable
{
public WorkbookCheck()
{
Util.Application.Workbooks.Add();
}
[ExcelFact(Workbook = @"TestWorkbooks\EmptyTestTable.xlsx")]
public void WorksheetIsCorrect()
{
Worksheet worksheet= Util.Workbook.Sheets[1];
Assert.True(worksheet.IsRehauSource());
}
[ExcelFact(Workbook = @"TestWorkbooks\EmptyWorkbook.xlsx")]
public void EmptyWorkbookIsNotCorrect()
{
Worksheet worksheet = Util.Workbook.Sheets[1];
Assert.False(worksheet.IsRehauSource());
}
public void Dispose()
{
Util.Application.ActiveWorkbook.Close(SaveChanges: false);
}
}

Binary file not shown.

View File

@ -1,5 +1,4 @@
global using Xunit; global using ExcelDna.Testing;
global using Microsoft.Office.Interop.Excel; global using Microsoft.Office.Interop.Excel;
global using ExcelDna.Testing;
global using RhSolutions.Models;
global using RhSolutions.Services; global using RhSolutions.Services;
global using Xunit;

View File

@ -0,0 +1,37 @@
using RhSolutions.AddIn;
using RhSolutions.Tools;
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class WorkbookValidationTests : IDisposable
{
private readonly RhSolutionsAddIn _addIn;
public WorkbookValidationTests()
{
_addIn = new RhSolutionsAddIn();
_addIn.AutoOpen();
Util.Application.Workbooks.Add();
}
[ExcelFact(Workbook = @"TestWorkbooks\EmptyTestTable.xlsx")]
public void WorksheetIsCorrect()
{
Worksheet worksheet = Util.Workbook.Sheets[1];
Assert.True(worksheet.IsValidSource());
}
[ExcelFact(Workbook = @"TestWorkbooks\EmptyWorkbook.xlsx")]
public void EmptyWorkbookIsNotCorrect()
{
Worksheet worksheet = Util.Workbook.Sheets[1];
Assert.False(worksheet.IsValidSource());
}
public void Dispose()
{
_addIn.AutoClose();
Util.Application.ActiveWindow.Close(SaveChanges: false);
}
}