Compare commits

...

177 Commits

Author SHA1 Message Date
a065c4c699 Merge branch 'master' of https://gitea.cebotari.ru/chebser/RhSolutions-AddIn 2024-11-06 22:46:57 +03:00
a73b2441b3 Search for range vaules in Product search function 2024-11-06 22:46:44 +03:00
06e47d3135 Setup debug settings 2024-11-06 22:45:23 +03:00
5c50e6bc8e Bump outdated packages 2024-11-06 22:44:40 +03:00
87f030e0f9 Bump outdated packages 2024-11-06 22:44:04 +03:00
6d3f2bf55c Merge pull request 'dev' (#2) from dev into master
Reviewed-on: #2
2024-11-01 10:54:48 +03:00
8cb8f58714 Version 1.9.5.1 2024-10-31 23:31:45 +03:00
c8a5824add Add 40 sleeves and couplings support 2024-10-31 23:31:14 +03:00
38011f165e Update 50-63 sleeves skus 2024-08-26 18:11:57 +03:00
46b8468628 Merge branch 'master' of https://gitea.cebotari.ru/chebser/RhSolutions-AddIn
Update readme.
2024-07-24 10:01:03 +03:00
7a7c703aa7 Add vscode assets 2024-07-24 10:00:40 +03:00
3b3e37b1c3 Update README.md 2024-06-04 10:18:22 +03:00
a6e5ed1476 Version 1.9.5.0 2024-05-27 23:46:45 +03:00
dfe4ef7b5b Fix merged column header data format 2024-05-27 23:44:59 +03:00
dc1e8616a6 Fix merged column ordering 2024-05-27 23:39:43 +03:00
e71636f5f8 Edit icons 2024-05-26 07:23:37 +03:00
a6a0f104c5 Add mx couplings support 2024-05-26 07:07:18 +03:00
cc32e4792c Add LX sleeves support 2024-05-25 15:15:17 +03:00
678bdb47b9 Bump ExcelDNA packages 2024-05-19 16:22:16 +03:00
0292348f03 Fix usings 2024-02-22 18:10:53 +03:00
9b205a5b3e Ensure price list exists on Excel open 2024-02-22 18:09:27 +03:00
5c8c5f5736 Excel functions refactoring 2024-02-14 23:20:08 +03:00
339cd65dee Version 1.9.3.0 2024-01-21 23:15:22 +03:00
b931809534 Do not close workbook on merge if opened 2024-01-21 23:10:41 +03:00
964bb01a80 Revert "Refactoring Excel application field"
This reverts commit 56112eae51.
2024-01-21 15:14:35 +03:00
f0fca06beb Merge branch 'master' of https://gitea.cebotari.ru/chebser/RhSolutions-AddIn 2024-01-21 15:08:51 +03:00
ed47fd38f6 Use AsyncTaskUtil.RunTask for async functions in formulas 2024-01-21 15:04:51 +03:00
556bb3778e Compress Resources 2024-01-21 15:03:43 +03:00
d71ad65c80 Clean up csproj 2024-01-21 15:03:29 +03:00
2def24c9af Increase DefaultConnectionLimit to 50 2024-01-21 14:52:02 +03:00
56112eae51 Refactoring Excel application field 2024-01-21 14:51:38 +03:00
c6f6551e4e Remove InteslliSense library from pack 2024-01-18 23:55:27 +03:00
84001e050c Remove IntelliSense server 2024-01-18 23:23:26 +03:00
7b2a5955d8 Refactoring functions 2024-01-18 23:16:38 +03:00
fe58c854ea Add memory cache to currency client 2024-01-18 23:16:27 +03:00
822398e286 Update launchSettings.json 2024-01-18 22:38:02 +03:00
634f91dd13 Upgrade packages 2024-01-15 23:06:20 +03:00
03146a6561 Update couplings and sleeves calculator icons 2023-12-28 23:40:33 +03:00
17e1a1681e Fix merge of not selected files behaviour 2023-12-26 00:03:25 +03:00
e73c97e1f8 Update README.md 2023-12-12 23:51:08 +03:00
61f679f52e Delete Install.ps1 2023-12-12 23:50:09 +03:00
3043756014 Version 1.9.0.0 2023-12-12 23:43:23 +03:00
af2363b011 Ribbon update 2023-12-12 23:43:10 +03:00
3996b2175a Add RhSolutions and DXF icons 2023-12-09 23:59:57 +03:00
52f128c8b7 Remove ISTESTING setting 2023-12-09 23:59:40 +03:00
a8c192848d Fixt testing environment variable 2023-12-09 23:04:49 +03:00
f448cccbeb Add RhSolutions.ProductSku project 2023-12-08 23:14:56 +03:00
e6546254ba Save Configuration to registry 2023-11-12 16:31:26 +03:00
02d0b21a58 Add couplings calculator test 2023-11-12 14:57:43 +03:00
9fd1fd8266 Implement Couplings Calculator 2023-11-09 23:34:52 +03:00
1b72c00b1e Merge branch 'experimental' 2023-11-02 23:31:43 +03:00
6d6e91867c Use UriBuilder to create request uri 2023-11-02 23:31:17 +03:00
4bc663c2ab Revert "Fix query building with '&' sign"
This reverts commit e462add7f3.
2023-11-02 23:14:49 +03:00
8a8fc397bf Add Couplings calculator 2023-11-02 23:07:06 +03:00
d999a0b98a Version 1.8.5.4 2023-10-17 22:19:32 +03:00
24dcec3039 Update sleeves pattern 2023-10-17 22:18:01 +03:00
199540e530 Revert "Move Price List Validation to separate static class"
This reverts commit e14d714811.
2023-10-17 21:22:42 +03:00
e462add7f3 Fix query building with '&' sign 2023-10-16 22:50:25 +03:00
2a0430b7ec Fix Unsuccess Status Code return 2023-10-06 15:06:42 +03:00
2cb6b889b0 Ignore multiply rows in name cell 2023-08-04 15:41:40 +03:00
f19f8c6510 Add PS Install script 2023-08-02 16:58:17 +03:00
e14d714811 Move Price List Validation to separate static class 2023-07-26 17:17:09 +03:00
47c3b19a17 Price list validation optimization 2023-07-25 08:45:01 +03:00
1f5aebe62a Headers Find optimization 2023-07-25 08:44:36 +03:00
159ac0ec57 Fix multiple whitespaces in sku name sleeves calculator bug 2023-07-25 08:24:10 +03:00
Sergey Chebotar
d6de208df4 Hide StatusBarReset function 2023-06-23 07:33:33 +03:00
Sergey Chebotar
c1949a2159 Version 1.8.5.2 2023-06-21 17:03:42 +03:00
Sergey Chebotar
6fdc410c8f Merge branch 'master' of https://gitea.cebotari.ru/chebser/RhSolutions-AddIn 2023-06-21 17:03:07 +03:00
Sergey Chebotar
cd657e10b4 Fill cells with null if amount is zero 2023-06-21 16:45:38 +03:00
Sergey Chebotar
e4aa8dff93 Fix Application using ordering 2023-06-21 16:30:16 +03:00
a2165e7ef6 Update README.md 2023-06-21 07:50:30 +03:00
Sergey Chebotar
5fbfd3732f Version 1.8.5.0 2023-06-21 07:28:09 +03:00
Sergey Chebotar
9f4823378f Add Fill Sleeves Tests 2023-06-21 07:24:47 +03:00
Sergey Chebotar
6a5038246f Fix interface name 2023-06-21 07:11:31 +03:00
Sergey Chebotar
c17771a5d3 Fill not found sleeves with 0 2023-06-21 06:53:53 +03:00
Sergey Chebotar
101c1d4397 Add 'Add sleeves' button enabling 2023-06-21 06:39:15 +03:00
Sergey Chebotar
2224a98d32 Edit Fill Sleeves button 2023-06-21 06:35:28 +03:00
Sergey Chebotar
0da818da3e Implement sleeves calculator 2023-06-21 06:33:12 +03:00
Sergey Chebotar
5153e951e4 Edit sleeves tool 2023-06-20 11:52:42 +03:00
Sergey Chebotar
b9cdad649a Tools refactoring 2023-06-20 11:51:52 +03:00
Sergey Chebotar
2c44c5d2df Add Sleeves Calculator service 2023-06-20 11:50:11 +03:00
Sergey Chebotar
a9d7ac710e Implement CurrentPrice Writer service 2023-06-20 10:01:02 +03:00
Sergey Chebotar
a73cc46449 Add CurrentPrice Writer to Writer Factory 2023-06-20 09:58:39 +03:00
Sergey Chebotar
63a962f495 Implement fill sleeves tool 2023-06-20 09:51:44 +03:00
Sergey Chebotar
8c134644ba Fix filling filtered table 2023-06-20 09:51:25 +03:00
Sergey Chebotar
f570dd9a2d Add Fill Sleeves button 2023-06-20 09:50:24 +03:00
Sergey Chebotar
dc92388748 Fix guess tool button id 2023-06-20 09:50:03 +03:00
Sergey Chebotar
98bb135b62 Add CurrrentPriceWriter service 2023-06-20 09:46:52 +03:00
Sergey Chebotar
f89a3eb765 ExcelWriterBase Refactoring 2023-06-20 08:14:01 +03:00
Sergey Chebotar
7b8cab3e9e Extract Excel Write Base 2023-06-20 07:25:44 +03:00
Sergey Chebotar
50543ae697 Fix method name 2023-06-09 14:49:20 +03:00
Sergey Chebotar
a44f450df6 Version 1.8.4.0 2023-06-08 07:55:11 +03:00
Sergey Chebotar
213592b9e2 Check cache for currency rate before run async util 2023-06-08 07:47:14 +03:00
Sergey Chebotar
1399d71f3c Switch Currency client from transient to singleton 2023-06-08 07:43:49 +03:00
Sergey Chebotar
bfab7eb02b Edit caller function names 2023-06-08 06:44:11 +03:00
Sergey Chebotar
8b125e475e Tune amount column find 2023-06-06 08:17:52 +03:00
Sergey Chebotar
0e15ad7cd4 Continue instead of false return on value > maxValue 2023-05-30 09:08:11 +03:00
Sergey Chebotar
f6534fc8c7 Refactoring 2023-05-30 09:01:49 +03:00
Sergey Chebotar
51636ca61a Can guess single row with string headers 2023-05-30 08:59:16 +03:00
Sergey Chebotar
f1abb03ac9 Can guess amount columns with numbers in header 2023-05-30 08:48:21 +03:00
Sergey Chebotar
656f565152 Edit Guess Reader test 2023-05-30 08:47:16 +03:00
Sergey Chebotar
ab217c9052 Throw exceptions on unknown Service or Tool 2023-05-30 08:23:31 +03:00
Sergey Chebotar
65d027179c Refactoring 2023-05-27 08:58:23 +03:00
Sergey Chebotar
f620147515 Change DateTime from Now to Today 2023-05-27 08:58:09 +03:00
Sergey Chebotar
e67d94f199 Multiple buttons refreshing 2023-05-27 08:50:30 +03:00
Sergey Chebotar
2aae1805c5 Merge branch 'master' of https://gitea.cebotari.ru/chebser/RhSolutions-AddIn 2023-05-27 08:21:37 +03:00
Sergey Chebotar
cf472b66a4 Version 1.8.2.0 2023-05-27 08:20:39 +03:00
Sergey Chebotar
f58dec38a4 Fix wrong article found on replaced lookup 2023-05-27 08:19:10 +03:00
Sergey Chebotar
9c3c65a4f8 Fix write test 2023-05-27 08:17:36 +03:00
Sergey Chebotar
3e4a5e6563 Edit Replace and New Variant tests 2023-05-27 07:37:36 +03:00
4434da98be Update 'README.md' 2023-05-26 08:29:47 +03:00
Sergey Chebotar
1c655f1ab5 Version 1.8.1.0 2023-05-26 07:50:45 +03:00
Sergey Chebotar
1220999bf0 Fix row indeces 2023-05-26 07:50:23 +03:00
Sergey Chebotar
1e64511ae1 Version 1.8.0.0 2023-05-26 07:05:22 +03:00
Sergey Chebotar
3ecfc82b7e Lookup for amount untill last used row only 2023-05-26 07:02:49 +03:00
Sergey Chebotar
aa4c949270 Change find amount column algorithm 2023-05-26 06:33:35 +03:00
Sergey Chebotar
d058b3ed4e Fix null reference exception 2023-05-24 10:48:52 +03:00
Sergey Chebotar
79eeb2a206 Refactoring Guess Reader 2023-05-24 06:39:50 +03:00
Sergey Chebotar
a9aa1f30c5 Refactoring 2023-05-23 15:02:56 +03:00
Sergey Chebotar
7f8455d868 Fix exception on guessing single row 2023-05-23 15:02:14 +03:00
Sergey Chebotar
9d2f386a74 Add one row guess test 2023-05-23 14:39:49 +03:00
Sergey Chebotar
9840e0fb24 Add empty rows to test file 2023-05-23 14:35:45 +03:00
Sergey Chebotar
7fd139a234 Edit amount column looking up 2023-05-23 09:26:08 +03:00
Sergey Chebotar
9d1c213979 Add guess button enable and disable events 2023-05-23 07:55:58 +03:00
Sergey Chebotar
fba2859b84 Fix null cells reference 2023-05-23 07:55:14 +03:00
Sergey Chebotar
28c5a69797 Button change 2023-05-23 07:45:37 +03:00
Sergey Chebotar
c0c656c82c Fix null reference 2023-05-23 07:33:49 +03:00
Sergey Chebotar
a56300a620 Get writer before reading 2023-05-23 07:33:40 +03:00
Sergey Chebotar
16b5ddedb1 Rename Magic to Guess 2023-05-23 07:07:16 +03:00
Sergey Chebotar
733440392e Fix testing reader type 2023-05-23 07:01:33 +03:00
Sergey Chebotar
136aa7f238 Add Magic Tool 2023-05-23 06:54:37 +03:00
Sergey Chebotar
d894c50ca3 Add Magic button to Ribbon Controller 2023-05-23 06:53:20 +03:00
Sergey Chebotar
c308e72ee0 Add Magic Service 2023-05-23 06:52:52 +03:00
Sergey Chebotar
21fb58744c Implement Magic Reader 2023-05-22 10:24:18 +03:00
Sergey Chebotar
7a7cdaab58 Add Magic reader test 2023-05-22 08:05:44 +03:00
Sergey Chebotar
5720d2ede0 Add ReaderFactory 2023-05-22 07:39:48 +03:00
Sergey Chebotar
dcd42a32eb WriterFactory refactoring 2023-05-22 07:31:53 +03:00
Sergey Chebotar
0fcc632024 Add real pricelist test 2023-05-21 15:47:09 +03:00
Sergey Chebotar
58f5c5ba4a Add new variant writing test 2023-05-21 15:21:20 +03:00
Sergey Chebotar
de73af8b7d Add Replaced Writing Test 2023-05-21 15:17:49 +03:00
Sergey Chebotar
d4ef078b9d Add Not Found Writing Test 2023-05-21 15:13:17 +03:00
Sergey Chebotar
fbb36fb00d Add count check to product lines test 2023-05-21 10:26:02 +03:00
Sergey Chebotar
06b397cebf Add multiple product lines test 2023-05-21 10:24:16 +03:00
Sergey Chebotar
1d64c8839d Do not copy Test workbooks 2023-05-21 10:24:02 +03:00
Sergey Chebotar
71a0745c70 Edit replace and missing descriptions 2023-05-21 10:07:35 +03:00
Sergey Chebotar
d6a78c42c7 Merge branch 'testing' 2023-05-19 08:17:40 +03:00
Sergey Chebotar
08fcc33f9d Fix replace and missing descriptions duplicates 2023-05-19 08:16:40 +03:00
Sergey Chebotar
f9ff1e55b9 Set ExcelDnaPackCompressResources to false 2023-05-19 07:43:29 +03:00
Sergey Chebotar
ea69a77a0e Add description to replaced products 2023-05-18 15:26:38 +03:00
Sergey Chebotar
28d6c3b6b9 Fix file naming 2023-05-18 14:53:52 +03:00
Sergey Chebotar
8902d51c14 Add asserts to write test 2023-05-18 07:23:59 +03:00
Sergey Chebotar
5babd30c03 Test workbooks name fix 2023-05-18 07:23:33 +03:00
Sergey Chebotar
49efdad03e Add write products test 2023-05-18 07:15:52 +03:00
Sergey Chebotar
58a45df8cf Version 1.7.0.0 2023-05-17 08:15:38 +03:00
Sergey Chebotar
c5895f78c2 Add =КУРСЕВРО() function 2023-05-17 08:11:24 +03:00
Sergey Chebotar
9f0ef90005 Refactoring 2023-05-17 08:03:55 +03:00
Sergey Chebotar
e203155474 Edit functions intellisense descriptions 2023-05-17 07:58:13 +03:00
Sergey Chebotar
8dc186c7aa Add Intellisense library 2023-05-17 07:40:26 +03:00
Sergey Chebotar
dcbf7d6d7f Version 1.6.5.0 update 2023-05-16 18:10:16 +03:00
Sergey Chebotar
aca468c015 Add Workbook read tests 2023-05-16 18:05:35 +03:00
Sergey Chebotar
ade18e5e40 Extend missing product information on table fill 2023-05-16 18:02:32 +03:00
Sergey Chebotar
b06df453d2 Fix null reference exception on empty measure field. 2023-05-16 17:59:45 +03:00
Sergey Chebotar
786a5bfe26 Fix empty ProductLines field exception on writing 2023-05-16 17:04:30 +03:00
Sergey Chebotar
46e547e3e2 Cache found product sku on full text search request. 2023-05-16 15:28:36 +03:00
Sergey Chebotar
2178c1bea3 Find last row by Sku column 2023-05-16 09:03:51 +03:00
Sergey Chebotar
627195fafe Add caching 2023-05-16 07:43:57 +03:00
Sergey Chebotar
05914bf519 Version 1.6.3.0 2023-05-15 06:42:35 +03:00
Sergey Chebotar
d4d98ec850 Add =РЕХАУЦЕНАРУБ() function 2023-05-15 06:33:24 +03:00
Sergey Chebotar
53f82b03fb Version 1.6.2.1 2023-05-12 11:11:07 +03:00
Sergey Chebotar
ac978419ae Fix empty product lines exception 2023-05-12 11:10:39 +03:00
Sergey Chebotar
6d9ff9365c Add РЕХАУ, РЕХАУИМЯ, РЕХАУАРТИКУЛ, РЕХАУЦЕНА functions 2023-05-12 10:18:00 +03:00
Sergey Chebotar
9c5ba835b1 Update ProductSku library 2023-05-12 09:54:28 +03:00
Sergey Chebotar
4201b51511 Version update 2023-05-05 08:21:48 +03:00
Sergey Chebotar
62271e75fb Product package update 2023-05-05 08:21:26 +03:00
Sergey Chebotar
c0dd1afb18 Version update 2023-05-04 08:03:31 +03:00
Sergey Chebotar
b37dade370 Fill products as Text not Attributes 2023-05-04 08:02:57 +03:00
Sergey Chebotar
46db5452c8 Fix column missing header 2023-04-21 06:45:18 +03:00
81 changed files with 2310 additions and 739 deletions

27
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
// 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 Normal file
View File

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

View File

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

View File

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

View File

@ -1,16 +0,0 @@
#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,13 +1,7 @@
using System.Net;
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.AddIn;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public sealed class RhSolutionsAddIn : IExcelAddIn
{
public static Application Excel { get; private set; }
@ -19,17 +13,32 @@ public sealed class RhSolutionsAddIn : IExcelAddIn
IServiceCollection Services = new ServiceCollection();
Services.AddHttpClient()
.AddMemoryCache()
.AddSingleton((Application)ExcelDnaUtil.Application)
.AddSingleton<IAddInConfiguration, AddInConfiguration>()
.AddSingleton<IDatabaseClient, DatabaseClient>()
.AddTransient<IFileDialog, FileDialog>()
.AddTransient<IReader, ExcelReader>();
.AddSingleton<ICurrencyClient, CurrencyClient>()
.AddTransient<IFileDialog, FileDialog>();
Services.AddSingleton<WriterFactory>();
Services.AddTransient<ExcelWriter>()
.AddTransient<IWriter, ExcelWriter>(s => s.GetService<ExcelWriter>());
Services.AddTransient<NewPriceWriter>()
.AddTransient<IWriter, NewPriceWriter>(s => s.GetService<NewPriceWriter>());
Services.AddTransient<DxfWriter>()
.AddTransient<IWriter, DxfWriter>(s => s.GetService<DxfWriter>());
Services.AddTransient<CurrentPriceWriter>()
.AddTransient<IWriter, CurrentPriceWriter>(s => s.GetService<CurrentPriceWriter>());
Services.AddSingleton<ReaderFactory>();
Services.AddTransient<ExcelReader>()
.AddTransient<IReader, ExcelReader>(s => s.GetService<ExcelReader>());
Services.AddTransient<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>();
@ -38,8 +47,10 @@ public sealed class RhSolutionsAddIn : IExcelAddIn
Excel = ServiceProvider.GetService<Application>();
EventsUtil.Initialize();
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls12;
ServicePointManager.DefaultConnectionLimit = 50;
}
public void AutoClose()

View File

@ -1,55 +0,0 @@
#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>();
ProductSku.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

@ -0,0 +1,156 @@
namespace RhSolutions.AddIn;
public static class RhSolutionsFunctions
{
private static readonly IDatabaseClient databaseClient =
RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>();
private static readonly ICurrencyClient currencyClient =
RhSolutionsAddIn.ServiceProvider.GetRequiredService<ICurrencyClient>();
[ExcelFunction(Name = "РЕХАУ")]
public static object ProductSearch(object[,] values)
{
List<string> strings = new();
int rows = values.GetLength(0);
int columns = values.GetLength(1);
for (int row = 0; row < rows; row++)
{
for (int column = 0; column < columns; column++)
{
object value = values[row, column];
strings.Add(value.ToString());
}
}
string query = string.Join(" ", strings.ToArray());
var functionName = nameof(ProductSearch);
var parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
return await databaseClient.GetProducts(query);
}) is not IEnumerable<Product> products)
{
return "Загрузка...";
}
else if (!products.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var product = products.First();
return $"{product.Id} {product.Name}";
}
}
[ExcelFunction(Name = "РЕХАУАРТИКУЛ")]
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 functionName = nameof(GetProductName);
var parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
return await databaseClient.GetProducts(article);
}) is not IEnumerable<Product> requestResult)
{
return "Загрузка...";
}
else if (!requestResult.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var firstProduct = requestResult.First();
return firstProduct.Name;
}
}
[ExcelFunction(Name = "РЕХАУЦЕНА")]
public static object GetProductPrice(string query)
{
if (!ProductSku.TryParse(query, out var skus))
{
return ExcelError.ExcelErrorNA;
}
var article = skus.First().Id;
var functionName = nameof(GetProductPrice);
var parameters = new object[] { article };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
return await databaseClient.GetProducts(article);
}) is not IEnumerable<Product> requestResult)
{
return "Загрузка...";
}
else if (!requestResult.Any())
{
return ExcelError.ExcelErrorNA;
}
else
{
var firstProduct = requestResult.First();
return Math.Round(firstProduct.Price * 1.2m, 2);
}
}
[ExcelFunction(Name = "РЕХАУЦЕНАРУБ")]
public static object GetProductPriceRub(string query, double dateField)
{
if (!ProductSku.TryParse(query, out var skus))
{
return ExcelError.ExcelErrorNA;
}
var article = skus.First().Id;
DateTime date = dateField == 0 ? DateTime.Today : DateTime.FromOADate(dateField);
var functionName = nameof(GetProductPriceRub);
var parameters = new object[] { date };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
var requestResult = await currencyClient.GetExchangeRate(date);
return requestResult ?? -1m;
}) is not decimal exchangeRate)
{
return "Загрузка...";
}
parameters = new object[] { query };
if (ExcelAsyncUtil.RunTask(functionName, parameters, async () =>
{
var products = await databaseClient.GetProducts(article);
var product = products.FirstOrDefault();
return product == null ? -1m :
product.Price * (decimal)exchangeRate * 1.2m;
}) is not decimal requestResult)
{
return "Загрузка...";
}
else if (requestResult < 0 || exchangeRate < 0)
{
return ExcelError.ExcelErrorNA;
}
else
{
return Math.Round(requestResult, 2);
}
}
}

View File

@ -1,36 +1,39 @@
using ExcelDna.Integration.CustomUI;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
#if! NET472
using System.Runtime.Versioning;
#endif
using System.Windows.Forms;
namespace RhSolutions.Controllers;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
[ComVisible(true)]
public class RibbonController : ExcelRibbon
{
private static IRibbonUI ribbonUi;
private static bool _workbookIsValid;
public override string GetCustomUI(string RibbonID)
{
return @"
<customUI onLoad='RibbonLoad' xmlns='http://schemas.microsoft.com/office/2006/01/customui'>
<customUI onLoad='RibbonLoad' xmlns='http://schemas.microsoft.com/office/2006/01/customui' loadImage='LoadImage'>
<ribbon>
<tabs>
<tab id='rau' label='RhSolutions'>
<group id='priceList' label='Прайс-лист'>
<button id='export' getEnabled='GetExportEnabled' label='Экспорт в новый файл' size='normal' imageMso='PivotExportToExcel' onAction='OnToolPressed'/>
<button id='export' getEnabled='GetExportEnabled' label='Экспорт в новый файл' size='normal' image='RhSolutions' 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='dxfexport' getEnabled='GetDxfEnabled' label='Экспортировать в DXF' size='normal' imageMso='ExportExcel' onAction='OnToolPressed'/>
<button id='guess' getEnabled='GetGuessEnabled' label='Найти и экспортировать' size='large' imageMso='ControlWizards' onAction='OnToolPressed'/>
</group>
<group id='rausettings' getLabel='GetVersionLabel'>
<button id='setPriceList' getLabel='GetPriceListPathLabel' size='large' imageMso='TableExcelSpreadsheetInsert' onAction='OnSetPricePressed'/>
<group id='fittingsCalc' label='Расчет фитингов'>
<button id='fillsleeves' getEnabled='GetFittingsCalcEnabled' label='Гильзы' size='large' image='Sleeve' onAction='OnToolPressed'/>
<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>
</tab>
</tabs>
@ -80,17 +83,10 @@ public class RibbonController : ExcelRibbon
}
}
public bool GetConvertEnabled(IRibbonControl control)
{
if (RhSolutionsAddIn.Excel.ActiveWorkbook == null)
return false;
else
{
Worksheet worksheet = RhSolutionsAddIn.Excel.ActiveWorkbook.ActiveSheet;
return worksheet.IsValidSource();
}
}
public bool GetConvertEnabled(IRibbonControl control) => _workbookIsValid;
public bool GetDxfEnabled(IRibbonControl control) => _workbookIsValid;
public bool GetFittingsCalcEnabled(IRibbonControl control) => _workbookIsValid;
public bool GetGuessEnabled(IRibbonControl control) => RhSolutionsAddIn.Excel.ActiveWorkbook != null && !_workbookIsValid;
public bool GetExportEnabled(IRibbonControl control)
{
@ -104,18 +100,6 @@ public class RibbonController : ExcelRibbon
}
}
public bool GetDxfEnabled(IRibbonControl control)
{
if (RhSolutionsAddIn.Excel.ActiveWorkbook == null)
return false;
else
{
Worksheet worksheet = RhSolutionsAddIn.Excel.ActiveWorkbook.ActiveSheet;
return worksheet.IsValidSource();
}
}
public string GetVersionLabel(IRibbonControl control)
{
string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
@ -125,6 +109,27 @@ public class RibbonController : ExcelRibbon
public string GetPriceListPathLabel(IRibbonControl control)
{
string name = RhSolutionsAddIn.Configuration.GetPriceListFileName();
return string.IsNullOrEmpty(name) ? "Шаблонный файл" : name;
return string.IsNullOrEmpty(name) ? "Указать шаблонный файл" : name;
}
public static void UpdateWorkbookValidation()
{
if (RhSolutionsAddIn.Excel.ActiveWorkbook == null)
_workbookIsValid = false;
else
{
Worksheet worksheet = RhSolutionsAddIn.Excel.ActiveWorkbook.ActiveSheet;
_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.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

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
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.6.0.0")]
[assembly: AssemblyFileVersion("1.6.0.0")]
[assembly: AssemblyVersion("1.9.5.1")]
[assembly: AssemblyFileVersion("1.9.5.1")]

View File

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

View File

@ -1,8 +1,11 @@
<?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">
<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.HashCode.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Caching.Abstractions.dll" Pack="true" />
<Reference Path="Microsoft.Extensions.Caching.Memory.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" />
@ -12,12 +15,16 @@
<Reference Path="Microsoft.Extensions.Primitives.dll" Pack="true" />
<Reference Path="Newtonsoft.Json.dll" Pack="true" />
<Reference Path="netDxf.dll" Pack="true" />
<Reference Path="RhSolutions.Sku.dll" Pack="true" />
<Reference Path="RhSolutions.ProductSku.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" />
<Reference Path="System.Numerics.Vectors.dll" Pack="true" />
<Reference Path="System.Runtime.CompilerServices.Unsafe.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>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472</TargetFrameworks>
<TargetFrameworks>net472;net6.0-windows</TargetFrameworks>
<LangVersion>10</LangVersion>
<OutputType>Library</OutputType>
<RootNamespace>RhSolutions</RootNamespace>
@ -9,35 +9,36 @@
<UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
</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>
<ItemGroup>
<PackageReference Include="ExcelDna.AddIn" Version="1.6.0">
<TreatAsUsed>true</TreatAsUsed>
</PackageReference>
<PackageReference Include="ExcelDna.Integration" Version="1.6.0" />
<PackageReference Include="ExcelDna.AddIn" Version="1.8.0" />
<PackageReference Include="ExcelDna.Integration" Version="1.8.0" />
<PackageReference Include="ExcelDna.Interop" Version="15.0.1" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
<PackageReference Include="ExcelDna.Registration" Version="1.8.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="netDxf" Version="2022.11.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RhSolutions.Sku" Version="0.1.5" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</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>

View File

@ -1,114 +1,43 @@
using System.Configuration;
using Microsoft.Win32;
using System.IO;
namespace RhSolutions.Services;
public class AddInConfiguration : ApplicationSettingsBase, IAddInConfiguration
public class AddInConfiguration : IAddInConfiguration
{
private readonly Dictionary<string, string> _priceListHeaders;
private RegistryKey _rootKey;
private string _priceListPath;
private Dictionary<string, string> _priceListHeaders;
public event IAddInConfiguration.SettingsHandler OnSettingsChange;
public AddInConfiguration()
{
_priceListHeaders = new Dictionary<string, string>()
_rootKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\RhSolutions\RhSolutions-AddIn");
_priceListPath = (string)_rootKey.GetValue("PriceListPath");
_priceListHeaders = new()
{
["Amount"] = AmountHeader,
["OldSku"] = OldSkuHeader,
["Sku"] = SkuHeader,
["ProductLine"] = ProductLineHeader,
["Name"] = NameHeader,
["Measure"] = MeasureHeader
["Amount"] = "Кол-во",
["OldSku"] = "Прежний материал",
["Sku"] = "Актуальный материал",
["ProductLine"] = "Программа",
["Name"] = "Наименование",
["Measure"] = "Ед. изм."
};
}
[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 string GetPriceListFileName() => Path.GetFileName(_priceListPath);
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

@ -0,0 +1,67 @@
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

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

View File

@ -0,0 +1,19 @@
#if !NET472
using System.Runtime.Versioning;
using RhSolutions.Tools;
#endif
namespace RhSolutions.Services;
public class CurrentPriceWriter : ExcelWriterBase, IWriter, IDisposable
{
public CurrentPriceWriter(Application application, IAddInConfiguration configuration)
{
_application = application;
_resultBar = new();
_headers = configuration.GetPriceListHeaders();
_worksheet = _application.ActiveSheet;
_appendValues = false;
}
}

View File

@ -1,4 +1,5 @@
using Newtonsoft.Json;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
@ -7,43 +8,87 @@ namespace RhSolutions.Services;
public class DatabaseClient : IDatabaseClient
{
private readonly IServiceProvider serviceProvider;
private readonly HttpClient _httpClient;
private readonly IMemoryCache _memoryCache;
public HttpStatusCode StatusCode { get; private set; }
public DatabaseClient(IServiceProvider provider)
public DatabaseClient(HttpClient httpClient, IMemoryCache memoryCache)
{
this.serviceProvider = provider;
_httpClient = httpClient;
_memoryCache = memoryCache;
}
public async Task<IEnumerable<Product>> GetProducts(string line)
{
string request;
if (ProductSku.TryParse(line, out var skus))
{
request = @"https://rh.cebotari.ru/api/products/" + skus.FirstOrDefault().ToString();
ProductSku sku = skus.FirstOrDefault();
string request = @"https://rh.cebotari.ru/api/products/" + sku.ToString();
if (!_memoryCache.TryGetValue(sku, out IEnumerable<Product> products))
{
var response = await _httpClient.GetAsync(request);
try
{
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
products = JsonConvert.DeserializeObject<IEnumerable<Product>>(json) ?? Enumerable.Empty<Product>();
}
catch
{
StatusCode = response.StatusCode;
return Enumerable.Empty<Product>();
}
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1));
_memoryCache.Set(sku, products, cacheEntryOptions);
return products;
}
else
{
return products;
}
}
else
{
request = @"https://rh.cebotari.ru/api/search?query=" + line;
}
UriBuilder builder = new(@"https://rh.cebotari.ru/api/search")
{
Query = $"query={line.Replace("&", "%26")}"
};
string request = builder.Uri.AbsoluteUri;
if (!_memoryCache.TryGetValue(line, out IEnumerable<Product> products))
{
var response = await _httpClient.GetAsync(request);
var client = serviceProvider.GetRequiredService<HttpClient>();
var response = await client.GetAsync(request);
try
{
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
products = JsonConvert.DeserializeObject<IEnumerable<Product>>(json) ?? Enumerable.Empty<Product>();
}
catch
{
StatusCode = response.StatusCode;
return Enumerable.Empty<Product>();
}
try
{
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<Product>>(json) ?? Enumerable.Empty<Product>();
}
catch
{
StatusCode = response.StatusCode;
}
return Enumerable.Empty<Product>();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromHours(1));
_memoryCache.Set(line, products, cacheEntryOptions);
if (products.Any())
{
_memoryCache.Set(products.First(), products, cacheEntryOptions);
}
return products;
}
else
{
return products;
}
}
}
}

View File

@ -29,6 +29,47 @@ public class DxfWriter : IWriter
WriteProducts(new[] { (string.Empty, products) });
}
private IEnumerable<EntityObject> WriteRow(int x, int y, Product product, double amount)
{
string measure = product.ProductMeasure switch
{
Measure.Kg => "кг",
Measure.M => "м",
Measure.M2 => "м2",
Measure.P => "шт",
_ => string.Empty
};
return new[]
{
new Text(product.Name, new Vector2(x + 4180, y), 250)
{
Alignment = TextAlignment.MiddleLeft,
WidthFactor = 0.85
},
new Text(product.ProductSku.ToString(), new Vector2(x + 24750, y), 250)
{
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85
},
new Text("«РЕХАУ»", new Vector2(x + 28750, y), 250)
{
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85
},
new Text(measure, new Vector2(x + 32000, y), 250)
{
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85
},
new Text(amount.ToString(), new Vector2(x + 34000, y), 250)
{
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85
}
};
}
public void WriteProducts(IEnumerable<(string, Dictionary<Product, double>)> products)
{
productDict = products.First().Item2;
@ -39,6 +80,8 @@ public class DxfWriter : IWriter
for (int i = 0; i < tablesCount; i++)
{
int x = i * 43000;
var insertion = new Insert(tableBlock, new Vector2(tablePosition, 0))
{
Layer = doc.Layers["Таблицы"]
@ -48,30 +91,8 @@ public class DxfWriter : IWriter
for (int row = 0; row < 27 && i * 27 + row < pArray.Length; row++)
{
insertion.Attributes.AttributeWithTag($"Name{row}").Value = pArray[i * 27 + row].Name.Replace("\n", " ");
insertion.Attributes.AttributeWithTag($"ProductSku{row}").Value = pArray[i * 27 + row].ProductSku;
insertion.Attributes.AttributeWithTag($"Rh{row}").Value = "«РЕХАУ»";
insertion.Attributes.AttributeWithTag($"Amount{row}").Value = productDict[pArray[i * 27 + row]].ToString();
string measure = string.Empty;
switch (pArray[i * 27 + row].ProductMeasure)
{
case Measure.Kg:
measure = "кг";
break;
case Measure.M:
measure = "м";
break;
case Measure.M2:
measure = "м2";
break;
case Measure.P:
measure = "шт";
break;
default:
break;
}
insertion.Attributes.AttributeWithTag($"Measure{row}").Value = measure;
int y = 25400 - row * 800;
doc.Entities.Add(WriteRow(x, y, pArray[i * 27 + row], productDict[pArray[i * 27 + row]]));
}
insertion.Attributes.AttributeWithTag("Sheet").Value = (i + 1).ToString();
@ -384,6 +405,11 @@ public class DxfWriter : IWriter
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85
},
new Text("9", new Vector2(39500, 26200), 250)
{
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85
},
new Text("Изм.", new Vector2(23100, 525), 300)
{
Alignment = TextAlignment.BottomLeft,
@ -423,47 +449,6 @@ public class DxfWriter : IWriter
Block block = new("Таблица спецификации", entities);
y = 25400;
for (int i = 0; i < 27; i++)
{
block.AttributeDefinitions.Add(new AttributeDefinition($"Name{i}")
{
Position = new Vector3(4180, y, 0),
Alignment = TextAlignment.MiddleLeft,
WidthFactor = 0.85,
Height = 250
});
block.AttributeDefinitions.Add(new AttributeDefinition($"ProductSku{i}")
{
Position = new Vector3(24750, y, 0),
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85,
Height = 250
});
block.AttributeDefinitions.Add(new AttributeDefinition($"Rh{i}")
{
Position = new Vector3(28750, y, 0),
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85,
Height = 250
});
block.AttributeDefinitions.Add(new AttributeDefinition($"Measure{i}")
{
Position = new Vector3(32000, y, 0),
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85,
Height = 250
});
block.AttributeDefinitions.Add(new AttributeDefinition($"Amount{i}")
{
Position = new Vector3(34000, y, 0),
Alignment = TextAlignment.MiddleCenter,
WidthFactor = 0.85,
Height = 250
});
y -= 800;
}
block.AttributeDefinitions.Add(new AttributeDefinition($"Sheet")
{
Position = new Vector3(41000, 950, 0),
@ -475,6 +460,7 @@ public class DxfWriter : IWriter
return block;
}
public void Dispose()
{
Process.Start(file);

View File

@ -60,7 +60,10 @@ public class ExcelReader : IReader, IDisposable
continue;
}
Product product = new() { ProductSku = currentSku };
Product product = new(currentSku)
{
Name = $"Распознанный артикул"
};
if (readResult.ContainsKey(product))
{
@ -76,7 +79,7 @@ public class ExcelReader : IReader, IDisposable
return readResult;
}
public List<(string, Dictionary<Product, double>)>
public IEnumerable<(string, Dictionary<Product, double>)>
ReadProducts(IEnumerable<Worksheet> worksheets)
{
List<(string, Dictionary<Product, double>)> result = new();
@ -90,27 +93,29 @@ public class ExcelReader : IReader, IDisposable
string wbName = Path.GetFileNameWithoutExtension(
worksheet.Parent.Name);
Range AmountCell = worksheet.Cells.Find(headers["Amount"]),
SkuCell = worksheet.Cells.Find(headers["Sku"]),
ProductLineCell = worksheet.Cells.Find(headers["ProductLine"]),
NameCell = worksheet.Cells.Find(headers["Name"]),
MeasureCell = worksheet.Cells.Find(headers["Measure"]);
var lastRowIndex = worksheet.Cells[worksheet.Rows.Count, AmountCell.Column]
Range amountCell = worksheet.Cells.Find(headers["Amount"]);
Range headerRow = amountCell.EntireRow;
Range skuCell = headerRow.Find(headers["Sku"]),
productLineCell = headerRow.Find(headers["ProductLine"]),
nameCell = headerRow.Find(headers["Name"]),
measureCell = headerRow.Find(headers["Measure"]);
var lastRowIndex = worksheet.Cells[worksheet.Rows.Count, skuCell.Column]
.End[XlDirection.xlUp].Row;
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)
{
object productLine = worksheet.Cells[row, ProductLineCell.Column].Value2;
object name = worksheet.Cells[row, NameCell.Column].Value2;
object sku = worksheet.Cells[row, SkuCell.Column].Value2;
object measure = worksheet.Cells[row, MeasureCell.Column].Value2;
var productMeasure = measure.ToString() switch
object productLine = worksheet.Cells[row, productLineCell.Column].Value2;
object name = worksheet.Cells[row, nameCell.Column].Value2;
object sku = worksheet.Cells[row, skuCell.Column].Value2;
object measure = worksheet.Cells[row, measureCell.Column].Value2;
var productMeasure = measure?.ToString() switch
{
"м" => Measure.M,
"шт" => Measure.P,
@ -124,11 +129,10 @@ public class ExcelReader : IReader, IDisposable
if (!ProductSku.TryParse(sku.ToString(), out _))
continue;
Product p = new()
{
ProductSku = sku.ToString(),
ProductLine = productLine.ToString(),
Name = name.ToString(),
Product p = new(sku.ToString())
{
ProductLines = new List<string>() { productLine.ToString() },
Name = name.ToString().Split('\n').First(),
ProductMeasure = productMeasure
};
@ -147,11 +151,15 @@ public class ExcelReader : IReader, IDisposable
result.Add((wbName, readResult));
}
return result;
return result.OrderBy(x => x.Item1);
}
public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files)
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(string[] files)
{
HashSet<string> openedFiles = RhSolutionsAddIn.Excel.Workbooks
.Cast<Workbook>()
.Select(wb => wb.FullName)
.ToHashSet();
_progressBar = new("Открываю исходные файлы...", files.Length);
List<Worksheet> worksheets = new();
@ -166,7 +174,11 @@ public class ExcelReader : IReader, IDisposable
var result = ReadProducts(worksheets);
foreach (var ws in worksheets)
{
ws.Parent.Close();
string file = (string)ws.Parent.FullName;
if (!openedFiles.Contains(file))
{
ws.Parent.Close(SaveChanges: false);
}
}
return result;
}

View File

@ -1,255 +0,0 @@
#if !NET472
using System.Runtime.Versioning;
using RhSolutions.Tools;
#endif
namespace RhSolutions.Services;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public class ExcelWriter : IWriter, 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 ExcelWriter(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,267 @@
using System.Text.RegularExpressions;
namespace RhSolutions.Services
{
public abstract class ExcelWriterBase
{
protected Application _application;
protected Dictionary<string, string> _headers;
protected ResultBar _resultBar;
protected ProgressBar _progressBar;
protected Worksheet _worksheet;
protected Range _amountCell;
protected Range _nameCell;
protected Range _oldSkuCell;
protected Range _programLineCell;
protected Range _skuCell;
protected bool _appendValues = true;
public void Dispose()
{
_progressBar?.Dispose();
_resultBar?.Dispose();
}
public void WriteProducts(Dictionary<Product, double> products)
{
WriteProducts(new[] { (string.Empty, products) });
}
public void WriteProducts(IEnumerable<(string, Dictionary<Product, double>)> products)
{
if (!_worksheet.IsValidSource())
{
_application.ActiveWorkbook.Close();
throw new ArgumentException(
$"Целевой файл {_application.ActiveWorkbook.Name} не является прайс-листом.");
}
ShowFilteredData();
_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)
{
FillAmounts(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.NumberFormat = "@";
newColumnHeader.Value2 = $"{product.Item1}";
newColumnHeader.WrapText = true;
foreach (var kvp in product.Item2)
{
FillAmounts(kvp, _amountCell.Column - 1, _amountCell.Column);
_progressBar.Update();
}
}
FilterByAmount();
_resultBar.Update();
}
}
private void WriteOutMissing(KeyValuePair<Product, double> productAmount, params int[] columns)
{
Range worksheetCells = _worksheet.Cells;
Range worksheetRows = _worksheet.Rows;
int skuColumn = _skuCell.Column;
int groupColumn = _programLineCell.Column;
int nameColumn = _nameCell.Column;
Product product = productAmount.Key;
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 = product.ProductLines.FirstOrDefault() ?? string.Empty;
worksheetCells[row, nameColumn].Value2 = $"{product.Name} (не найден арт. {product.ProductSku})";
worksheetCells[row, skuColumn].Value2 = "???";
if (_oldSkuCell != null)
{
worksheetCells[row, _oldSkuCell.Column].Value2 = product.ProductSku;
}
foreach (int column in columns)
{
Range cell = worksheetCells[row, column];
cell.AddValue(productAmount.Value);
}
}
private void FillCells(double amount, int row, int[] columns)
{
foreach (int column in columns)
{
Range cell = _worksheet.Cells[row, column];
if (_appendValues)
{
cell.AddValue(amount);
}
else
{
if (amount == 0)
{
cell.Value2 = null;
}
else
{
cell.Value2 = amount;
}
}
}
}
private void FillAmounts(KeyValuePair<Product, double> positionAmount, params int[] columns)
{
Range range = _skuCell.EntireColumn;
ProductSku sku = positionAmount.Key.ProductSku;
string productLine = positionAmount.Key.ProductLines.FirstOrDefault();
int? row = GetPositionRow(range, sku, productLine);
if (row != null)
{
FillCells(positionAmount.Value, row.Value, columns);
_resultBar.IncrementSuccess();
}
else
{
if (_oldSkuCell != null)
{
range = _oldSkuCell.EntireColumn;
row = GetPositionRow(range, sku, productLine);
}
if (row == null)
{
range = _skuCell.EntireColumn;
row = GetPositionRow(range, sku, productLine, true);
}
if (row != null)
{
Range nameCell = _worksheet.Cells[row, _nameCell.Column];
if (!Regex.IsMatch(nameCell.Value2, @"арт. \d{11}"))
{
nameCell.AddValue($"(замена арт. {sku})");
}
FillCells(positionAmount.Value, row.Value, columns);
_resultBar.IncrementReplaced();
}
else
{
WriteOutMissing(positionAmount, columns);
_resultBar.IncrementNotFound();
}
}
}
private void FilterByAmount()
{
if (_worksheet.AutoFilterMode)
{
AutoFilter filter = _worksheet.AutoFilter;
int startColumn = filter.Range.Column;
filter.Range.AutoFilter(_amountCell.Column - startColumn + 1, "<>0", XlAutoFilterOperator.xlAnd, "<>");
_worksheet.Range["A1"].Activate();
}
}
private void ShowFilteredData()
{
if (_worksheet.AutoFilterMode)
{
AutoFilter filter = _worksheet.AutoFilter;
filter.ShowAllData();
}
}
private int? GetPositionRow(Range range, ProductSku sku, string productLine, bool justArticle = false)
{
string lookupString = justArticle ? sku.Article : sku.ToString();
Range found = range.Find(lookupString);
string foundGroupValue;
string foundSkuValue;
if (found == null)
{
return null;
}
int firstFoundRow = found.Row;
while (true)
{
foundSkuValue = _worksheet.Cells[found.Row, range.Column].Value2.ToString();
foundGroupValue = _worksheet.Cells[found.Row, _programLineCell.Column].Value2.ToString();
if (ProductSku.TryParse(foundSkuValue, out var skus))
{
if (skus.Any(s => s.Article == sku.Article) &&
(string.IsNullOrEmpty(productLine) || productLine.Equals(foundGroupValue)))
{
return found.Row;
}
else
{
found = range.FindNext(found);
if (found.Row == firstFoundRow)
{
return null;
}
}
}
else
{
found = range.FindNext(found);
if (found.Row == firstFoundRow)
{
return null;
}
}
}
}
}
}

View File

@ -0,0 +1,21 @@
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

@ -0,0 +1,234 @@
using System.IO;
namespace RhSolutions.Services;
public class GuessReader : IReader
{
private readonly Application _application;
private ProgressBar _progressBar;
public GuessReader(Application application)
{
_application = application;
}
public Dictionary<Product, double> ReadProducts(Range range)
{
_progressBar = new("Ищу колонку с артикулами...", range.Columns.Count);
int? productColumnIndex = null;
for (int column = 1; column < range.Columns.Count + 1; column++)
{
_progressBar.Update();
if (IsProductColumn(range.Columns[column]))
{
productColumnIndex = column;
break;
}
}
if (productColumnIndex == null)
{
throw new ArgumentException("Колонка с артикулом не определена");
}
int? amountColumnIndex = null;
int currentIndex = productColumnIndex.Value + 1;
_progressBar = new("Ищу колонку с количеством...", range.Columns.Count);
while (currentIndex > 0)
{
_progressBar.Update();
if (currentIndex > range.Columns.Count + 1)
{
currentIndex = productColumnIndex.Value - 1;
continue;
}
if (currentIndex > productColumnIndex)
{
if (IsAmountColumn(range.Columns[currentIndex]))
{
amountColumnIndex = currentIndex;
break;
}
else currentIndex++;
}
else
{
if (IsAmountColumn(range.Columns[currentIndex]))
{
amountColumnIndex = currentIndex;
break;
}
else currentIndex--;
}
}
if (amountColumnIndex == null)
{
throw new ArgumentException("Колонка с количеством не определена");
}
else
{
return GetDictionaryFromColumns(range.Columns[productColumnIndex],
range.Columns[amountColumnIndex]);
}
}
private bool IsProductColumn(Range column)
{
int successCounter = 0;
var cells = column.Value2;
if (cells == null)
{
return false;
}
for (int row = 1; row < column.Rows.Count + 1; row++)
{
object currentCell = column.Rows.Count == 1 ? cells : cells[row, 1];
if (currentCell == null)
{
continue;
}
if (ProductSku.TryParse(currentCell.ToString(), out _))
{
successCounter++;
}
if (successCounter > 2)
{
return true;
}
}
return successCounter > 0;
}
private bool IsAmountColumn(Range column)
{
int successCounter = 0;
var cells = column.Value2;
double maxValue = 30000;
if (cells == null)
{
return false;
}
if (column.Rows.Count == 1)
{
double? value = cells as double?;
return value != null
&& value != 0
&& value < maxValue;
}
else
{
for (int row = 1; row < column.Rows.Count + 1; row++)
{
object currentCell = cells[row, 1];
double? value = currentCell as double?;
if (value == null
|| value == 0
|| value > maxValue)
{
continue;
}
if (++successCounter > 3)
{
return true;
}
}
}
return successCounter > 1;
}
private Dictionary<Product, double> GetDictionaryFromColumns(Range productColumn, Range amountColumn)
{
Dictionary<Product, double> result = new();
var firstRowIndex = 1;
var lastRowIndex = amountColumn.Worksheet
.Cells[amountColumn.Worksheet.Rows.Count, amountColumn.Column]
.End[XlDirection.xlUp].Row;
_progressBar = new("Заполняю словарь значений...", lastRowIndex);
Worksheet worksheet = amountColumn.Worksheet;
for (int row = firstRowIndex; row < lastRowIndex + 1; row++)
{
_progressBar.Update();
object currentAmountCell = worksheet.Cells[row, amountColumn.Column].Value2;
object currentProductCell = worksheet.Cells[row, productColumn.Column].Value2;
double? amountValue = currentAmountCell as double?;
if (amountValue == null || amountValue == 0 || currentProductCell == null)
{
continue;
}
if (ProductSku.TryParse(currentProductCell.ToString(), out IEnumerable<ProductSku> skus))
{
Product p = new(skus.First())
{
Name = "Распознанный артикул"
};
if (result.ContainsKey(p))
{
result[p] += amountValue.Value;
}
else
{
result.Add(p, amountValue.Value);
}
}
}
return result;
}
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets)
{
List<(string, Dictionary<Product, double>)> result = new();
foreach (Worksheet worksheet in worksheets)
{
string wbName = Path.GetFileNameWithoutExtension(
worksheet.Parent.Name);
Range range = worksheet.UsedRange;
var productsDict = ReadProducts(range);
result.Add((wbName, productsDict));
}
return result;
}
public IEnumerable<(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

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

View File

@ -0,0 +1,9 @@
using Newtonsoft.Json;
using System.Threading.Tasks;
namespace RhSolutions.Services;
public interface ICurrencyClient
{
public Task<decimal?> GetExchangeRate(DateTime date);
}

View File

@ -0,0 +1,6 @@
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 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);
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets);
public IEnumerable<(string, Dictionary<Product, double>)> ReadProducts(string[] files);
new void Dispose();
}

View File

@ -0,0 +1,34 @@
#if !NET472
using System.Runtime.Versioning;
using RhSolutions.Tools;
#endif
using System.Text.RegularExpressions;
namespace RhSolutions.Services;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
public class NewPriceWriter : ExcelWriterBase, IWriter, IDisposable
{
public NewPriceWriter(Application application, IAddInConfiguration configuration)
{
_application = application;
_resultBar = new();
_headers = configuration.GetPriceListHeaders();
_worksheet = OpenNewPrice(configuration.GetPriceListPath());
}
private Worksheet OpenNewPrice(string pricelistPath)
{
if (_application.Workbooks
.Cast<Workbook>()
.FirstOrDefault(w => w.FullName == pricelistPath) != null)
{
throw new ArgumentException("Шаблонный файл редактируется в другом месте");
}
return _application.Workbooks.Open(pricelistPath, null, true).ActiveSheet;
}
}

View File

@ -0,0 +1,21 @@
namespace RhSolutions.Services;
public class ReaderFactory
{
private readonly IServiceProvider _serviceProvider;
public ReaderFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IReader GetReader(string readerName)
{
return readerName switch
{
"Guess" => (IReader)_serviceProvider.GetService(typeof(GuessReader)),
"Excel" => (IReader)_serviceProvider.GetService(typeof(ExcelReader)),
_ => throw new ArgumentException($"Незвестный интерфейс {nameof(IReader)}: {readerName}")
};
}
}

View File

@ -0,0 +1,87 @@
using System.Text.RegularExpressions;
namespace RhSolutions.Services;
public class SleevesCalculator : IFittingsCalculator
{
private const string doublePattern =
@"((?i)равнопроходная|угольник\s+90|угольник\s+45|Т-образная|Комплект\s+трубок(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+";
private const string singlePattern =
@"((?i)муфта|тройник|переходник|угольник|штуцер|Г-образная|заглушка(?i))(.+?\b(?<Sleeve>16|20|25|32|40|50|63)\b)+";
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,
["16PX"] = 0,
["20PX"] = 0,
["25PX"] = 0,
["32PX"] = 0,
["40PX"] = 0
};
var rautitanProducts = products.Where(kvp => kvp.Key.ProductLines.Contains("RAUTITAN"));
foreach (var kvp in rautitanProducts)
{
var doubleCollection = Regex.Matches(kvp.Key.Name, doublePattern);
if (doubleCollection.Count != 0)
{
CaptureCollection collection = doubleCollection[0].Groups["Sleeve"].Captures;
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;
}
}
continue;
}
var singleCollection = Regex.Matches(kvp.Key.Name, singlePattern);
if (singleCollection.Count != 0)
{
CaptureCollection collection = singleCollection[0].Groups["Sleeve"].Captures;
foreach (Capture sleeve in collection)
{
if (kvp.Key.Name.Contains("PX"))
{
result[$"{sleeve.Value}PX"] += kvp.Value;
}
else
{
result[sleeve.Value] += kvp.Value;
}
}
}
}
return result
.ToDictionary(kvp =>
kvp.Key switch
{
"16" => new Product("11080011001"),
"20" => new Product("11080021001"),
"25" => new Product("11080031001"),
"32" => new Product("11080041001"),
"40" => new Product("11080051001"),
"50" => new Product("11397713002"),
"63" => new Product("11397813002"),
"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}")
}, kvp => kvp.Value);
}
}

View File

@ -11,14 +11,12 @@ public class WriterFactory
public IWriter GetWriter(string writerName)
{
if (writerName.Equals("Dxf"))
return writerName switch
{
return (IWriter)_serviceProvider.GetService(typeof(DxfWriter));
}
else
{
return (IWriter)_serviceProvider.GetService(typeof(ExcelWriter));
}
"NewPrice" => (IWriter)_serviceProvider.GetService(typeof(NewPriceWriter)),
"CurrentPrice" => (IWriter)_serviceProvider.GetService(typeof(CurrentPriceWriter)),
"Dxf" => (IWriter)_serviceProvider.GetService(typeof(DxfWriter)),
_ => throw new ArgumentException($"Незвестный интерфейс {nameof(IWriter)}: {writerName}")
};
}
}
}

View File

@ -10,7 +10,7 @@ namespace RhSolutions.Tools;
#endif
internal class ConvertTool : Tool
{
public ConvertTool(IServiceProvider provider) : base(provider)
public ConvertTool(ReaderFactory readerFactory, WriterFactory writerFactory) : base(readerFactory, writerFactory)
{
}
@ -18,8 +18,9 @@ internal class ConvertTool : Tool
{
Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(new[] { worksheet });
_writer = _writerFactory.GetWriter("Excel");
_writer = _writerFactory.GetWriter("NewPrice");
_writer.WriteProducts(products);
}
}

View File

@ -9,7 +9,7 @@ namespace RhSolutions.Tools;
#endif
internal class DxfTool : Tool
{
public DxfTool(IServiceProvider provider) : base(provider)
public DxfTool(ReaderFactory readerFactory, WriterFactory writerFactory) : base(readerFactory, writerFactory)
{
}
@ -17,6 +17,7 @@ internal class DxfTool : Tool
{
Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(new[] { worksheet });
_writer = _writerFactory.GetWriter("Dxf");
_writer.WriteProducts(products);

View File

@ -1,56 +1,43 @@
using Microsoft.Office.Interop.Excel;
using RhSolutions.AddIn;
using RhSolutions.Controllers;
using System.Configuration;
#if !NET472
using System.Runtime.Versioning;
#endif
using RhSolutions.Controllers;
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()
{
RhSolutionsAddIn.Excel.SheetSelectionChange += RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate += RefreshConvertButton;
RhSolutionsAddIn.Excel.SheetActivate += RefreshDxfButton;
RhSolutionsAddIn.Excel.WorkbookActivate += RefreshConvertButton;
RhSolutionsAddIn.Excel.WorkbookActivate += RefreshDxfButton;
RhSolutionsAddIn.Configuration.OnSettingsChange += RefreshSettingTitle;
}
RibbonController.EnsurePriseListExists();
RhSolutionsAddIn.Excel.SheetSelectionChange += RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate += RefreshButtons;
RhSolutionsAddIn.Excel.WorkbookActivate += RefreshButtons;
RhSolutionsAddIn.Configuration.OnSettingsChange += RefreshSettingTitle;
}
public static void Uninitialize()
{
RhSolutionsAddIn.Excel.SheetSelectionChange -= RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate -= RefreshConvertButton;
RhSolutionsAddIn.Excel.SheetActivate -= RefreshDxfButton;
RhSolutionsAddIn.Excel.WorkbookActivate -= RefreshConvertButton;
RhSolutionsAddIn.Excel.WorkbookActivate -= RefreshDxfButton;
RhSolutionsAddIn.Configuration.OnSettingsChange -= RefreshSettingTitle;
}
public static void Uninitialize()
{
RhSolutionsAddIn.Excel.SheetSelectionChange -= RefreshExportButton;
RhSolutionsAddIn.Excel.SheetActivate -= RefreshButtons;
RhSolutionsAddIn.Excel.WorkbookActivate -= RefreshButtons;
RhSolutionsAddIn.Configuration.OnSettingsChange -= RefreshSettingTitle;
}
private static void RefreshConvertButton(object sh)
{
RibbonController.RefreshControl("convert");
}
private static void RefreshButtons(object sh)
{
RibbonController.UpdateWorkbookValidation();
RibbonController.RefreshControl("convert");
RibbonController.RefreshControl("dxfexport");
RibbonController.RefreshControl("guess");
RibbonController.RefreshControl("fillsleeves");
RibbonController.RefreshControl("fillcouplings");
}
private static void RefreshDxfButton(object sh)
{
RibbonController.RefreshControl("dxfexport");
}
private static void RefreshExportButton(object sh, Range target)
{
RibbonController.RefreshControl("export");
}
private static void RefreshExportButton(object sh, Range target)
{
RibbonController.RefreshControl("export");
}
private static void RefreshSettingTitle(object sender, SettingChangingEventArgs e)
{
RibbonController.RefreshControl("setPriceList");
}
private static void RefreshSettingTitle()
{
RibbonController.RefreshControl("setPriceList");
}
}

View File

@ -1,5 +1,4 @@
using RhSolutions.AddIn;
#if !NET472
#if !NET472
using System.Runtime.Versioning;
#endif
@ -10,15 +9,16 @@ namespace RhSolutions.Tools;
#endif
internal class ExportTool : Tool
{
public ExportTool(IServiceProvider provider) : base(provider)
public ExportTool(ReaderFactory readerFactory, WriterFactory writerFactory) : base(readerFactory, writerFactory)
{
}
public override void Execute()
{
Application app = RhSolutionsAddIn.Excel.Application;
_reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(app.Selection);
_writer = _writerFactory.GetWriter("Excel");
_writer = _writerFactory.GetWriter("NewPrice");
_writer.WriteProducts(products);
}
}

View File

@ -0,0 +1,25 @@
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

@ -0,0 +1,23 @@
#if !NET472
using System.Runtime.Versioning;
#endif
namespace RhSolutions.Tools;
internal class GuessTool : Tool
{
public GuessTool(ReaderFactory readerFactory, WriterFactory writerFactory) : base(readerFactory, writerFactory)
{
}
public override void Execute()
{
Application app = RhSolutionsAddIn.Excel.Application;
Worksheet worksheet = app.ActiveWorkbook.ActiveSheet;
_reader = _readerFactory.GetReader("Guess");
var products = _reader.ReadProducts(new[] { worksheet });
_writer = _writerFactory.GetWriter("NewPrice");
_writer.WriteProducts(products);
}
}

View File

@ -9,7 +9,7 @@ namespace RhSolutions.Tools;
#endif
internal class MergeTool : Tool
{
public MergeTool(IServiceProvider provider) : base(provider)
public MergeTool(ReaderFactory readerFactory, WriterFactory writerFactory) : base(readerFactory, writerFactory)
{
}
@ -17,8 +17,12 @@ internal class MergeTool : Tool
{
IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IFileDialog>();
string[] files = dialog.GetFiles();
var products = _reader.ReadProducts(files);
_writer = _writerFactory.GetWriter("Excel");
_writer.WriteProducts(products);
if (files.Length > 0)
{
_reader = _readerFactory.GetReader("Excel");
var products = _reader.ReadProducts(files);
_writer = _writerFactory.GetWriter("NewPrice");
_writer.WriteProducts(products);
}
}
}

View File

@ -7,7 +7,7 @@ namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class ProgressBar : StatusbarBase
public class ProgressBar : StatusbarBase
{
private double CurrentProgress { get; set; }
private readonly double TaskWeight;

View File

@ -8,7 +8,7 @@ namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal class ResultBar : StatusbarBase
public class ResultBar : StatusbarBase
{
private int Success { get; set; }
private int Replaced { get; set; }

View File

@ -11,10 +11,16 @@ namespace RhSolutions.Tools;
#if !NET472
[SupportedOSPlatform("windows")]
#endif
internal abstract class StatusbarBase : IDisposable
public abstract class StatusbarBase : IDisposable
{
protected Application Excel = RhSolutionsAddIn.Excel;
[ExcelFunction(IsHidden = true)]
public static void StatusBarReset()
{
RhSolutionsAddIn.Excel.StatusBar = false;
}
public abstract void Update();
public void Dispose()

View File

@ -9,20 +9,21 @@ namespace RhSolutions.Tools;
#endif
internal abstract class Tool : IDisposable
{
protected readonly IReader _reader;
protected readonly ReaderFactory _readerFactory;
protected readonly WriterFactory _writerFactory;
protected IReader _reader;
protected IWriter _writer;
public Tool(IServiceProvider provider)
public Tool(ReaderFactory readerFactory, WriterFactory writerFactory)
{
_reader = provider.GetRequiredService<IReader>();
_writerFactory = provider.GetRequiredService<WriterFactory>();
_readerFactory = readerFactory;
_writerFactory = writerFactory;
}
public void Dispose()
{
_reader.Dispose();
_writer.Dispose();
_reader?.Dispose();
_writer?.Dispose();
}
public abstract void Execute();

View File

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

View File

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

View File

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

View File

@ -0,0 +1,91 @@
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

@ -0,0 +1,194 @@
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

@ -0,0 +1,20 @@
<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,62 @@
using Microsoft.Extensions.DependencyInjection;
using RhSolutions.AddIn;
using System.IO;
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class CanDoGuess : IDisposable
{
private RhSolutionsAddIn _addIn;
private IReader _guessReader;
private IReader _reader;
public CanDoGuess()
{
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new();
_addIn.AutoOpen();
_guessReader = new GuessReader(Util.Application);
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationGuess.xlsx")]
public void CanWriteMultiplyRows()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecificationGuess.xlsx"));
var products = _guessReader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
var targetProducts = _reader.ReadProducts(new[] { targetSheet });
Assert.Equal("TestSpecificationGuess", products.First().Item1);
Assert.Equal("TargetSpecificationGuess", targetProducts.First().Item1);
Assert.Equal(products.First().Item2.Count(), targetProducts.First().Item2.Count());
Assert.Equal(products.First().Item2.Values.Sum(), targetProducts.First().Item2.Values.Sum());
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationGuessOneRow.xlsx")]
public void CanWriteOneRow()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecificationGuessOneRow.xlsx"));
var products = _guessReader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
var targetProducts = _reader.ReadProducts(new[] { targetSheet });
Assert.Equal("TestSpecificationGuessOneRow", products.First().Item1);
Assert.Equal("TargetSpecificationGuessOneRow", targetProducts.First().Item1);
Assert.Equal(products.First().Item2.Count(), targetProducts.First().Item2.Count());
Assert.Equal(products.First().Item2.Values.Sum(), targetProducts.First().Item2.Values.Sum());
}
public void Dispose()
{
_addIn.AutoClose();
Util.Application.ActiveWindow.Close(SaveChanges: false);
}
}

View File

@ -0,0 +1,41 @@
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

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

@ -12,10 +12,11 @@ public class CanReadProducts : IDisposable
public CanReadProducts()
{
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new();
_testWorkbook = Util.Application.Workbooks.Add();
_addIn.AutoOpen();
_reader = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IReader>();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);
}
[ExcelFact]
@ -39,20 +40,23 @@ public class CanReadProducts : IDisposable
Assert.NotNull(products);
Assert.NotEmpty(products);
Assert.Equal("11600011001", products.First().Key.ProductSku);
Assert.Equal("11600011001", products.First().Key.ProductSku?.ToString());
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")]
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecification.xlsx")]
public void CanReadWorkbook()
{
Worksheet worksheet = Util.Workbook.Worksheets[1];
var products = _reader.ReadProducts(new[] { worksheet });
Assert.NotNull(products);
Assert.NotEmpty(products);
var result = _reader.ReadProducts(new[] { worksheet });
Assert.NotNull(result);
Assert.NotEmpty(result);
Assert.Equal("TestSpecification", result.First().Item1);
var products = result.First().Item2;
Assert.Equal(46, products.Count());
Assert.Equal(29266, products.Sum(p => p.Value));
}
public void Dispose()

View File

@ -0,0 +1,116 @@
using Microsoft.Extensions.DependencyInjection;
using RhSolutions.AddIn;
using System.IO;
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class CanWriteProducts : IDisposable
{
private RhSolutionsAddIn _addIn;
private IReader _reader;
public CanWriteProducts()
{
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new();
_addIn.AutoOpen();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecification.xlsx")]
public void CanWriteSingle()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecification.xlsx"));
var products = _reader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
var targetProducts = _reader.ReadProducts(new[] { targetSheet });
Assert.Equal("TestSpecification", products.First().Item1);
Assert.Equal("TargetSpecification", targetProducts.First().Item1);
Assert.Equal(products.First().Item2.Count(), targetProducts.First().Item2.Count());
Assert.Equal(products.First().Item2.Values.Sum(), targetProducts.First().Item2.Values.Sum());
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationMultipleProductLines.xlsx")]
public void CanWriteMultipleProductLines()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecificationMultipleProductLines.xlsx"));
var products = _reader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
var targetProducts = _reader.ReadProducts(new[] { targetSheet });
Assert.Equal("TestSpecificationMultipleProductLines", products.First().Item1);
Assert.Equal("TargetSpecificationMultipleProductLines", targetProducts.First().Item1);
Assert.Equal(2, targetProducts.First().Item2.Count());
Assert.True(Enumerable.SequenceEqual(products.First().Item2, targetProducts.First().Item2));
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationNotFound.xlsx")]
public void CanWriteNotFound()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecificationNotFound.xlsx"));
var products = _reader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
Assert.Equal("???", targetSheet.Range["B4"].Value2);
Assert.Contains("Молот Тора", targetSheet.Range["C4"].Value2);
Assert.Contains("15555551555", targetSheet.Range["C4"].Value2);
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationReplaced.xlsx")]
public void CanWriteReplaced()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecificationReplaced.xlsx"));
var products = _reader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
var targetProducts = _reader.ReadProducts(new[] { targetSheet });
Assert.Equal("TestSpecificationReplaced", products.First().Item1);
Assert.Equal("TargetSpecificationReplaced", targetProducts.First().Item1);
var result = targetProducts.First().Item2.ToArray();
Assert.Contains("Молот Тора", result[0].Key.Name);
Assert.Contains("15555551555", result[0].Key.Name);
Assert.Equal(1, result[0].Value);
Assert.Contains("Нога Вирта", result[1].Key.Name);
Assert.Contains("17777771777", result[1].Key.Name);
Assert.Equal(1, result[1].Value);
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\TestSpecificationNewVariant.xlsx")]
public void CanWriteNewVariant()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\TargetSpecificationNewVariant.xlsx"));
var products = _reader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
var targetProducts = _reader.ReadProducts(new[] { targetSheet });
Assert.Equal("TestSpecificationNewVariant", products.First().Item1);
Assert.Equal("TargetSpecificationNewVariant", targetProducts.First().Item1);
var result = targetProducts.First().Item2.ToArray();
Assert.Contains("Молот Тора", result[0].Key.Name);
Assert.Contains("11201111555", result[0].Key.Name);
Assert.Equal(1, result[0].Value);
}
public void Dispose()
{
_addIn.AutoClose();
Util.Application.ActiveWindow.Close(SaveChanges: false);
}
}

View File

@ -0,0 +1,42 @@
using Microsoft.Extensions.DependencyInjection;
using RhSolutions.AddIn;
using System.IO;
namespace RhSolutions.Tests;
[ExcelTestSettings(OutOfProcess = true)]
public class RealPricelistTest : IDisposable
{
private RhSolutionsAddIn _addIn;
private IReader _reader;
public RealPricelistTest()
{
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new();
_addIn.AutoOpen();
_reader = new ExcelReader(Util.Application, RhSolutionsAddIn.Configuration);
}
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\RealTestSpecification.xlsm")]
public void CanWrite()
{
Worksheet sourceSheet = Util.Workbook.Worksheets[1];
RhSolutionsAddIn.Configuration.SetPriceListPath(Path.GetFullPath(@"..\..\..\TestWorkbooks\RealTargetSpecification.xlsx"));
var products = _reader.ReadProducts(new[] { sourceSheet });
var _writer = new NewPriceWriter(Util.Application, RhSolutionsAddIn.Configuration);
_writer.WriteProducts(products);
Worksheet targetSheet = Util.Application.ActiveWindow.ActiveSheet;
targetSheet.Range["A1"].Formula = "=SUM(H:H)";
Assert.Equal("RealTestSpecification", products.First().Item1);
Assert.Equal("RealTargetSpecification.xlsx", Util.Application.ActiveWorkbook.Name);
Assert.Equal(1188.0, targetSheet.Range["A1"].Value);
}
public void Dispose()
{
_addIn.AutoClose();
Util.Application.ActiveWindow.Close(SaveChanges: false);
}
}

View File

@ -10,26 +10,12 @@
<ItemGroup>
<PackageReference Include="ExcelDna.Interop" Version="15.0.1" />
<PackageReference Include="ExcelDna.Testing" Version="1.6.0" />
<PackageReference Include="ExcelDna.Testing" Version="1.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RhSolutions.AddIn\RhSolutions.AddIn.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="TestWorkbooks\EmptyTestTable.xlsx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestWorkbooks\EmptyWorkbook.xlsx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestWorkbooks\ExcelTableTest.xlsx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="TestWorkbooks\Specifications\HeatingFloor.xlsx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<ProjectReference Include="..\RhSolutions.ProductSku\RhSolutions.ProductSku.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,171 @@
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 _));
}
}

Binary file not shown.

View File

@ -10,19 +10,20 @@ public class WorkbookValidationTests : IDisposable
public WorkbookValidationTests()
{
Environment.SetEnvironmentVariable("ISTESTING", "true");
_addIn = new RhSolutionsAddIn();
_addIn.AutoOpen();
Util.Application.Workbooks.Add();
}
[ExcelFact(Workbook = @"TestWorkbooks\EmptyTestTable.xlsx")]
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\EmptyTestTable.xlsx")]
public void WorksheetIsCorrect()
{
Worksheet worksheet = Util.Workbook.Sheets[1];
Assert.True(worksheet.IsValidSource());
}
[ExcelFact(Workbook = @"TestWorkbooks\EmptyWorkbook.xlsx")]
[ExcelFact(Workbook = @"..\..\..\TestWorkbooks\EmptyWorkbook.xlsx")]
public void EmptyWorkbookIsNotCorrect()
{
Worksheet worksheet = Util.Workbook.Sheets[1];

View File

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