< Summary

Class:SVETA.Api.Controllers.UploadController
Assembly:SVETA.Api
File(s):/opt/dev/sveta_api_build/SVETA.Api/Controllers/UploadController.cs
Covered lines:0
Uncovered lines:532
Coverable lines:532
Total lines:880
Line coverage:0% (0 of 532)
Covered branches:0
Total branches:150
Branch coverage:0% (0 of 150)

Metrics

MethodLine coverage Branch coverage
.ctor(...)0%100%
LogLoadBegins(...)0%100%
LogLoadEnds(...)0%0%
LogLoadEnds(...)0%0%
GetGoodsFromUpload()0%0%
UploadGoods()0%0%
UploadPhotos()0%0%
UploadRests()0%0%
UploadGoodSettings()0%0%
DetectFileType(...)0%0%
ReturnFile(...)0%0%
GetGoodSettingsTemplate()0%100%
GetRestsTemplate()0%100%
GetGoodsTemplate()0%0%
RollbackGoods()0%100%
CommitGoods()0%100%
GetReport(...)0%100%
GetReportPriceTrend(...)0%100%
ToResult(...)0%100%
ToResult(...)0%100%
UploadPriceTrends()0%0%
GetPriceTrendsTemplate(...)0%0%
UploadWorkSchedule()0%0%
GetWorkSchedulerTemplate(...)0%0%
PhotoProcessing()0%0%
.cctor()0%100%
ToCatalogGoodDtoMapper(...)0%0%
CheckDepartment()0%0%

File(s)

/opt/dev/sveta_api_build/SVETA.Api/Controllers/UploadController.cs

#LineLine coverage
 1using AutoMapper;
 2using SkiaSharp;
 3using ExcelDataReader;
 4using Microsoft.AspNetCore.Authorization;
 5using Microsoft.AspNetCore.Http;
 6using Microsoft.AspNetCore.Mvc;
 7using Microsoft.Extensions.Logging;
 8using Microsoft.Extensions.Options;
 9using SVETA.Api.Data.Domain;
 10using SVETA.Api.Data.DTO;
 11using SVETA.Api.Data.DTO.Goods;
 12using SVETA.Api.Data.DTO.Prices.PriceTrend;
 13using SVETA.Api.Services.Interfaces;
 14using Swashbuckle.AspNetCore.Annotations;
 15using System;
 16using System.Collections.Generic;
 17using System.Data;
 18using System.IO;
 19using System.Linq;
 20using System.Text;
 21using System.Threading.Tasks;
 22using WinSolutions.Sveta.Common;
 23using WinSolutions.Sveta.Common.Extensions;
 24using WinSolutions.Sveta.Server.Data.DataLoading;
 25using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 26using WinSolutions.Sveta.Server.Services.Interfaces;
 27using DocumentFormat.OpenXml.Office2010.Excel;
 28using SVETA.Api.Services.Interfaces.ImportingExporting;
 29
 30namespace SVETA.Api.Controllers
 31{
 32    /// <summary>
 33    /// Загрузка данных из файлов
 34    /// </summary>
 35    /// <remarks>author: tochilin.o</remarks>
 36    [Route("api/v1/Upload")]
 37    [ApiController]
 38    [RequestSizeLimit(2_000_000_000)]
 39    [RequestFormLimits(MultipartBodyLengthLimit = 2_000_000_000)]
 40    public partial class UploadController : SvetaController
 41    {
 42        const string _routeUrl = "api/v1/Upload";
 43
 44        private readonly IUploadService service;
 45        private readonly ILogger<GoodsController> logger;
 46        private readonly IOptions<ImagesSettings> imagesSettings;
 47        private readonly IPriceWorker _priceWorker;
 48        private readonly IUploadWorker _uploadWorker;
 49        private readonly ImagesSettings _imageSettings;
 50        readonly IDepartmentService _departmentService;
 51        private readonly IAuthenticationService _authService;
 52        IDiskStorageService _diskStorage;
 53        CommonSettings _commonSettings;
 54        IImportService _importService;
 55
 56        public UploadController(IUploadService service,ILogger<GoodsController> logger,IOptions<ImagesSettings> imagesSe
 57            IAuthenticationService authService, IDepartmentService departmentService, IDiskStorageService diskStorage,
 58            IOptions<CommonSettings> commonSettings, IUploadWorker uploadWorker, IImportService importService)
 059            : base(logger)
 060        {
 061            this.service = service;
 062            this.logger = logger;
 063            this.imagesSettings = imagesSettings;
 064            _uploadWorker = uploadWorker;
 065            _priceWorker = priceWorker;
 066            _imageSettings = options.Value;
 067            _authService = authService;
 068            _departmentService = departmentService;
 069            _diskStorage = diskStorage;
 070            _commonSettings = commonSettings.Value;
 071            _importService = importService;
 072        }
 73
 74
 75        void LogLoadBegins(string methodName, string fileName)
 076        {
 077            logger.LogInformation($"{methodName}: пользователь {_authService.UserId} начал загружать файл '{fileName}'")
 078        }
 79
 80        void LogLoadEnds(string methodName, string fileName, LoadingResult res)
 081        {
 082            logger.LogInformation($"{methodName}: пользователь {_authService.UserId} загрузил файл '{fileName}', сохране
 083        }
 84
 85        void LogLoadEnds(string methodName, string fileName, bool hasErrors)
 086        {
 087            logger.LogInformation($"{methodName}: пользователь {_authService.UserId} загрузил файл '{fileName}', " +
 088                $", {(hasErrors ? "есть ошибки" : "ошибок нет")}");
 089        }
 90
 91        /// <summary>
 92        /// Возвращает товары из загрузки по ее id
 93        /// </summary>
 94        /// <param name="id">id загрузки</param>
 95        /// <param name="page">пейджинг: номер страницы (Любое значение ниже нуля изменится на 1)</param>
 96        /// <param name="limit">пейджинг: размер страницы (Любое значение ниже нуля изменится на 10)</param>
 97        /// <param name="filter">фильтр по значимым полям (Name, Barcode)</param>
 98        /// <param name="sort">сортировка по полям name,name|desc, brandName,brandName|desc, По умолчанию по name</param
 99        /// <remarks>author: oboligatov</remarks>
 100        [HttpGet("{id}/Goods")]
 101        [SwaggerResponse(200, "Успешно", typeof(BaseResponseDTO<GoodCatalogDTO>))]
 102        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 103        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator)]
 104        public async Task<IActionResult> GetGoodsFromUpload(long id, int page = 1, int limit = 10, string filter = null,
 0105        {
 0106            var upload = await service.GetUpload(id);
 0107            if(upload == null)
 0108            {
 0109                throw new KeyNotFoundException($"Загрузка {id} не найдена");
 110            }
 111
 0112            page = page < 1 ? 1 : page;
 0113            limit = limit < 1 ? 10 : limit;
 114
 0115            var goods = await service.GetGoodsFromUpload(upload, page - 1, limit, filter, sort);
 0116            var totalCount = await service.GetGoodsFromUploadCount(upload, null);
 0117            var totalFiltredCount = await service.GetGoodsFromUploadCount(upload, filter);
 0118            var response = new BaseResponseDTO<GoodCatalogDTO>(_routeUrl, page, limit, totalFiltredCount, totalCount, so
 0119            {
 0120                Data = ToCatalogGoodDtoMapper(_imageSettings).Map<List<GoodCatalogDTO>>(goods)
 0121            };
 122
 0123            return Ok(response);
 0124        }
 125
 126        /// <summary>
 127        /// Загрузка файла товаров
 128        /// </summary>
 129        /// <param name="file">Файл</param>
 130        /// <returns>succeed, errorCount, reportUrl, uploadId</returns>
 131        [HttpPost("UploadGoods")]
 132        [SwaggerResponse(200, "Успешно", typeof(UploadResultDTO))]
 133        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 134        [Authorize(Roles = Role.SystemAdmin)]
 135        public async Task<IActionResult> UploadGoods(IFormFile file)
 0136        {
 0137            LoadingResult res = null;
 0138            using (var stream = _diskStorage.SaveUpload(file, out string fileName))
 0139            {
 0140                LogLoadBegins("UploadGoods", fileName);
 141
 0142                var savePath = imagesSettings.Value.ImageSavePath;
 143
 0144                res = await service.UploadGoods(stream, r =>
 0145                {
 0146                    if(r is Photo p)
 0147                    {
 0148                        var full = p.FullSizeUrl;
 0149                        var prev = p.PreviewUrl;
 0150
 0151                        if(!string.IsNullOrEmpty(full))
 0152                        {
 0153                            p.FullSizePath = Path.Combine(savePath, full);
 0154                        }
 0155
 0156                        if (!string.IsNullOrEmpty(prev))
 0157                        {
 0158                            p.PreviewPath = Path.Combine(savePath, prev);
 0159                        }
 0160                    }
 0161                },
 0162                getPhotoSize =>
 0163                {
 0164                    var errors = new List<string>();
 0165
 0166                    int width = 0;
 0167                    int height = 0;
 0168
 0169                    if (!string.IsNullOrEmpty(getPhotoSize.FullSizeUrl))
 0170                    {
 0171                        if (_diskStorage.GetPictureSize(getPhotoSize.FullSizeUrl, out width, out height))
 0172                        {
 0173                            getPhotoSize.FullSizeWidth = width;
 0174                            getPhotoSize.FullSizeHeight = height;
 0175                        }
 0176                        else
 0177                        {
 0178                            errors.Add(getPhotoSize.FullSizeUrl);
 0179                            getPhotoSize.FullSizeUrl = null;
 0180                        }
 0181                    }
 0182
 0183                    if (!string.IsNullOrEmpty(getPhotoSize.PreviewUrl))
 0184                    {
 0185                        if (_diskStorage.GetPictureSize(getPhotoSize.PreviewUrl, out width, out height))
 0186                        {
 0187                            getPhotoSize.PreviewWidth = width;
 0188                            getPhotoSize.PreviewHeight = height;
 0189                        }
 0190                        else
 0191                        {
 0192                            if (!errors.Contains(getPhotoSize.PreviewUrl))
 0193                            {
 0194                                errors.Add(getPhotoSize.PreviewUrl);
 0195                            }
 0196                            getPhotoSize.PreviewUrl = null;
 0197                        }
 0198                    }
 0199
 0200                    if (errors.Any())
 0201                    {
 0202                        throw new Exception($"Файлы картинок не найдены: {string.Join(", ", errors.ToArray())}");
 0203                    }
 0204                }, DetectFileType(file));
 205
 0206                LogLoadEnds("UploadGoods", fileName, res);
 0207            }
 208
 0209            return ToResult(res);
 0210        }
 211
 212
 213        /// <summary>
 214        /// Загрузка ZIP файла с фотографиями
 215        /// </summary>
 216        /// <param name="file">Файл</param>
 217        /// <returns></returns>
 218        [HttpPost("UploadPhotos")]
 219        [SwaggerResponse(200, "Успешно", typeof(UploadResultDTO))]
 220        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 221        [Authorize(Roles = Role.SystemAdmin)]
 222        [Consumes("multipart/form-data")]
 223        public async Task<IActionResult> UploadPhotos(IFormFile file)
 0224        {
 0225            LoadingResult res = null;
 0226            using (var stream = _diskStorage.SaveUpload(file, out string fileName))
 0227            {
 0228                LogLoadBegins("UploadPhotos", fileName);
 229
 0230                var savePath = imagesSettings.Value.ImageSavePath;
 231
 0232                res = await service.UploadPhotos(stream, r =>
 0233                {
 0234                    if(r is Photo p)
 0235                    {
 0236                        var full = p.FullSizeUrl;
 0237                        var prev = p.PreviewUrl;
 0238                        prev = prev.Replace(Path.GetExtension(prev), "_prev.jpg"); //для превью всегда jpg
 0239
 0240                        p.FullSizeUrl = full;
 0241                        p.PreviewUrl = prev;
 0242                        p.PreviewHeight = imagesSettings.Value.ImagePreveiwHeight; //передаем максимальную высоту для пр
 0243                        p.PreviewWidth = imagesSettings.Value.ImagePreveiwWidth;//передаем максимальную ширину для превь
 0244                        p.FullSizePath = Path.Combine(savePath, full);
 0245                        p.PreviewPath = Path.Combine(savePath, prev);
 0246                    }
 0247                });
 248
 0249                LogLoadEnds("UploadPhotos", fileName, res);
 0250            }
 251
 0252            return ToResult(res);
 0253        }
 254
 255
 256        /// <summary>
 257        /// Загрузка файла остатков
 258        /// </summary>
 259        /// <param name="departmentId">id департмента</param>
 260        [HttpPost("UploadRests/{departmentId}")]
 261        [SwaggerResponse(200, "Успешно", typeof(UploadResultDTO))]
 262        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 263        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 264        public async Task<IActionResult> UploadRests(long departmentId, IFormFile file)
 0265        {
 0266            await CheckDepartment(departmentId);
 0267            var res = await _importService.ImportRests(departmentId, file);
 0268            return ToResult(res);
 0269        }
 270
 271        /// <summary>
 272        /// Загрузка сеттингов товара из файла
 273        /// </summary>
 274        /// <param name="departmentId">id департмента</param>
 275        [HttpPost("GoodSettings/{departmentId}")]
 276        [SwaggerResponse(200, "Успешно", typeof(UploadResultDTO))]
 277        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 278        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 279        public async Task<IActionResult> UploadGoodSettings(long departmentId, IFormFile file)
 0280        {
 0281            await CheckDepartment(departmentId);
 0282            var res = await _importService.ImportGoodSettings(departmentId, file);
 0283            return ToResult(res);
 0284        }
 285
 286        string DetectFileType(IFormFile file)
 0287        {
 0288            switch(file.ContentType)
 289            {
 0290                case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": return "excel";
 291                case "application/vnd.ms-excel":
 0292                case "text/csv": return "csv";
 0293                default: throw new ArgumentException($"File type {file.Name} not supported");
 294            }
 0295        }
 296
 297        FileContentResult ReturnFile(MemoryStream stream, string fileName, string fileType = "excel")
 0298        {
 0299            if (fileType.ToLower() == "csv")
 0300            {
 0301                return File(stream.ToArray(), "text/csv", fileName);
 302            }
 303            else
 0304            {
 0305                return File(stream.ToArray(),
 0306                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0307                    fileName);
 308            }
 0309        }
 310
 311        /// <summary>
 312        /// Получение файла шаблона для загрузки сеттингов
 313        /// </summary>
 314        /// <param name="fileType">формат выходного файла (excel, csv), по умолчанию excel</param>
 315        /// <returns>file</returns>
 316        [HttpGet("GoodSettingsTemplate")]
 317        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 318        public async Task<IActionResult> GetGoodSettingsTemplate(string fileType = "excel")
 0319        {
 0320            var stream = await _importService.GetGoodSettingsTemplate();
 0321            return ReturnFile(stream, "GoodSettings-template.xlsx");
 0322        }
 323
 324        /// <summary>
 325        /// Получение файла шаблона для загрузки остатков
 326        /// </summary>
 327        /// <param name="fileType">формат выходного файла (excel, csv), по умолчанию excel</param>
 328        /// <returns>file</returns>
 329        [HttpGet("GetRestsTemplate")]
 330        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 331        public async Task<IActionResult> GetRestsTemplate(string fileType = "excel")
 0332        {
 0333            var stream = await _importService.GetRestsTemplate();
 0334            return ReturnFile(stream, "Rests-template.xlsx");
 0335        }
 336
 337        /// <summary>
 338        /// Получение файла шаблона для загрузки товаров
 339        /// </summary>
 340        /// <param name="fileType">формат выходного файла (excel, csv), по умолчанию excel</param>
 341        /// <returns>file</returns>
 342        [HttpGet("GetGoodsTemplate")]
 343        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator)]
 344        public async Task<IActionResult> GetGoodsTemplate(string fileType = "excel")
 0345        {
 0346            fileType = fileType.ToLower();
 0347            var resStream = new MemoryStream();
 348
 0349            await service.GetGoodsTemplate(resStream, fileType);
 0350            resStream.Position = 0;
 351
 0352            if (fileType == "csv")
 0353            {
 0354                Response.Headers.Add("Content-Disposition", $"attachment;filename=goods-template.csv");
 0355                return File(resStream, "text/csv");
 356            }
 357            else
 0358            {
 0359                return File(resStream.ToArray(),
 0360                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0361                    "goods-template.xlsx");
 362            }
 0363        }
 364
 365        /// <summary>
 366        /// Отмена загрузки товаров
 367        /// </summary>
 368        /// <param name="id">Id загрузки</param>
 369        /// <returns>succeed, errorCount, reportUrl, uploadId</returns>
 370        [HttpPost("RollbackGoods")]
 371        [Authorize(Roles = Role.SystemAdmin)]
 372        public async Task<IActionResult> RollbackGoods(long id)
 0373        {
 0374            await service.RollbackGoods(id);
 375
 0376            return Ok();
 0377        }
 378
 379
 380        /// <summary>
 381        /// Подтверждение товаров
 382        /// </summary>
 383        /// <param name="id">Id загрузки</param>
 384        /// <returns>succeed, errorCount, reportUrl, uploadId</returns>
 385        [HttpPost("CommitGoods")]
 386        [Authorize(Roles = Role.SystemAdmin)]
 387        public async Task<IActionResult> CommitGoods(long id)
 0388        {
 0389            await service.CommitGoods(id);
 390
 0391            return Ok();
 0392        }
 393
 394
 395        /// <summary>
 396        /// Получение отчета по загрузке
 397        /// </summary>
 398        /// <param name="id">Id загрузки</param>
 399        /// <returns>Файл отчета</returns>
 400        [HttpGet("GetReport")]
 401        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 402        public IActionResult GetReport(long id)
 0403        {
 0404            var fileName = $"upload-report{id}.xlsx";
 0405            var stream = _diskStorage.ReadDownload(fileName);
 406
 0407            var ms = new MemoryStream();
 0408            stream.CopyTo(ms);
 409
 0410            return File(ms.ToArray(),
 0411                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0412                    fileName);
 0413        }
 414
 415        /// <summary>
 416        /// Получение отчета по загрузке переоценок
 417        /// </summary>
 418        /// <param name="id">Id загрузки</param>
 419        /// <returns>Файл отчета</returns>
 420        [HttpGet("GetReportPriceTrend")]
 421        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 422        public IActionResult GetReportPriceTrend(long id)
 0423        {
 0424            var fileName = $"price_trend_errors{id}.xlsx";
 0425            var stream = _diskStorage.ReadDownload(fileName);
 426
 0427            var ms = new MemoryStream();
 0428            stream.CopyTo(ms);
 429
 0430            return File(ms.ToArray(),
 0431                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0432                    fileName);
 0433        }
 434
 435        private IActionResult ToResult(LoadingResult res)
 0436        {
 0437            var fileName = $"upload-report{res.UploadId}.xlsx";
 0438            var stream = new MemoryStream();
 0439            service.GetReport(res.UploadId, stream);
 0440            _diskStorage.SaveDownload(fileName, stream, false);
 441
 0442            return Ok(new UploadResultDTO(res, _commonSettings.BaseUrl));
 0443        }
 444
 445        private IActionResult ToResult(IImportResult res)
 0446        {
 0447            return Ok(new UploadResultDTO
 0448            {
 0449                errorCount = res.errorCount,
 0450                reportUrl = res.reportUrl,
 0451                succeed = res.succeed,
 0452                uploadId = res.uploadId
 0453            });
 0454        }
 455
 456        /// <summary>
 457        /// Загружает изменение цен из файла.
 458        /// Файл должен содержать колонки "Название товара", "VendorCode товара", "Новая цена", "Старая цена"
 459        /// </summary>
 460        /// <param name="id">ключ к уже созданному элементу “переоценка”</param>
 461        /// <remarks>author: oboligatov</remarks>
 462        [HttpPost("UploadPriceTrends/{id}")]
 463        [SwaggerResponse(200, "Успешно", typeof(UploadResultDTO))]
 464        [SwaggerResponse(400, "Некорректная структура файла", typeof(ErrorDTO))]
 465        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 466        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 467        public async Task<IActionResult> UploadPriceTrends(long id, IFormFile file)
 0468        {
 469            DataTable dataTable;
 470            string fileName;
 471
 0472            using (var stream = _diskStorage.SaveUpload(file, out fileName))
 0473            {
 0474                LogLoadBegins($"UploadPriceTrends/{id}", fileName);
 475
 0476                System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
 0477                var readerConfig = new ExcelReaderConfiguration()
 0478                {
 0479                    AutodetectSeparators = new char[] { ',', ';', '\t', '|', '#' },
 0480                    AnalyzeInitialCsvRows = 0
 0481                };
 0482                using (var reader = file.ContentType switch
 0483                {
 0484                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => ExcelReaderFactory.CreateRead
 0485                    "application/vnd.ms-excel" => ExcelReaderFactory.CreateCsvReader(stream, readerConfig),
 0486                    "text/csv" => ExcelReaderFactory.CreateCsvReader(stream, readerConfig),
 0487                    _ => throw new ArgumentException($"File type {file.Name} not supported"),
 0488                })
 0489                {
 0490                    var conf = new ExcelDataSetConfiguration
 0491                    {
 0492                        ConfigureDataTable = _ => new ExcelDataTableConfiguration
 0493                        {
 0494                            UseHeaderRow = true,
 0495                            ReadHeaderRow = (rowReader) =>
 0496                            {
 0497                                rowReader.Read(); // пропускаем описание шаблона
 0498                                rowReader.Read(); // пропускаем русские колонки
 0499                            }
 0500                        }
 0501                    };
 0502                    dataTable = reader.AsDataSet(conf).Tables[0];
 0503                }
 0504            }
 505
 0506            if (!dataTable.Columns.Contains("UniqueCode"))
 0507            {
 0508                throw new ArgumentException("Поле 'UniqueCode' не найдено");
 509            }
 0510            if (!dataTable.Columns.Contains("VendorCode"))
 0511            {
 0512                throw new ArgumentException("Поле 'VendorCode' не найдено");
 513            }
 0514            if (!dataTable.Columns.Contains("BarCode"))
 0515            {
 0516                throw new ArgumentException("Поле 'BarCode' не найдено");
 517            }
 0518            if (!dataTable.Columns.Contains("GoodName"))
 0519            {
 0520                throw new ArgumentException("Поле 'GoodName' не найдено");
 521            }
 0522            if (!dataTable.Columns.Contains("Price"))
 0523            {
 0524                throw new ArgumentException("Поле 'Price' не найдено");
 525            }
 0526            if (!dataTable.Columns.Contains("OldPrice"))
 0527            {
 0528                throw new ArgumentException("Поле 'OldPrice' не найдено");
 529            }
 530
 0531            var list = new List<PriceTrendFromFileDTO>();
 0532            foreach (DataRow row in dataTable.Rows)
 0533            {
 0534                list.Add(new PriceTrendFromFileDTO
 0535                {
 0536                    UniqueCode = row["UniqueCode"].ToString().NormalizeName(),
 0537                    VendorCode = row["VendorCode"].ToString().NormalizeName(),
 0538                    BarCode = row["BarCode"].ToString().NormalizeName(),
 0539                    GoodName = row["GoodName"].ToString().NormalizeName(),
 0540                    Price = row["Price"].ToString().NormalizeName(),
 0541                    OldPrice = row["OldPrice"].ToString().NormalizeName()
 0542                });
 0543            }
 0544            var errors = await _priceWorker.Load(id, list);
 545
 0546            var result = new UploadResultDTO() { succeed = true };
 0547            if (errors.Any())
 0548            {
 0549                result.succeed = false;
 550
 0551                var rows = new List<string[]>();
 0552                rows.Add(new string[] { "Шаблон загрузки цен (* - поле обязательно для заполнения, ** - одно из полей об
 0553                rows.Add(new string[] { "**Уникальный код товара", "**Артикул", "**Штрихкод", "**Название товара", "*Акц
 0554                rows.Add(new string[] { "UniqueCode", "VendorCode", "BarCode", "GoodName", "Price", "OldPrice", "Error" 
 0555                errors.ForEach(x =>
 0556                {
 0557                    rows.Add(new string[]
 0558                    {
 0559                        x.UniqueCode,
 0560                        x.VendorCode,
 0561                        x.BarCode,
 0562                        x.GoodName,
 0563                        x.Price,
 0564                        x.OldPrice,
 0565                        x.Message
 0566                    });
 0567                });
 568
 0569                var excel = CsvUtil.ToExcelStream(rows);
 0570                var resultFileName = $"price_trend_errors{id}.xlsx";
 0571                _diskStorage.SaveDownload(resultFileName, excel, false);
 572
 0573                result.errorCount = errors.Count;
 0574                result.reportUrl = $"{_commonSettings.BaseUrl.TrimEnd('/')}/api/v1/Upload/GetReportPriceTrend?id={id}";
 0575            }
 576
 0577            LogLoadEnds($"UploadPriceTrends/{id}", fileName, !result.succeed);
 578
 0579            return Ok(result);
 0580        }
 581
 582        /// <summary>
 583        /// Получение файла шаблона для загрузки переоценки
 584        /// </summary>
 585        /// <param name="fileType">формат выходного файла (excel, csv), по умолчанию excel</param>
 586        /// <returns>file</returns>
 587        [HttpGet("GetPriceTrendsTemplate")]
 588        [SwaggerResponse(200, "Успешно", typeof(FileStreamResult))]
 589        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 590        //[Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Suppli
 591        public IActionResult GetPriceTrendsTemplate(string fileType = "excel")
 0592        {
 0593            var rows = new List<string[]>();
 0594            rows.Add(new string[] { "Шаблон загрузки цен (* - поле обязательно для заполнения, ** - одно из полей обязат
 0595            rows.Add(new string[] { "**Уникальный код товара", "**Артикул", "**Штрихкод", "**Название товара", "*Акционн
 0596            rows.Add(new string[] { "UniqueCode", "VendorCode", "BarCode", "GoodName", "Price", "OldPrice" });
 0597            rows.Add(new string[] { "", "", "", "Печенье \"Шоколадное\", 150 г", "123,55", "" });
 0598            rows.Add(new string[] { "", "", "", "влажный корм для кошек WHISKAS рагу с говядиной и ягненком 85г", "123,5
 599
 0600            var stream = new MemoryStream();
 0601            if (fileType == "csv")
 0602            {
 0603                var csv = CsvUtil.ToCsv(rows);
 0604                using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true))
 0605                {
 0606                    writer.Write(csv);
 0607                }
 0608                stream.Position = 0;
 609
 0610                Response.Headers.Add("Content-Disposition", $"attachment;filename=Price-template.csv");
 0611                return File(stream, "text/csv");
 612            }
 613            else
 0614            {
 0615                using (var book = CsvUtil.ToExcel(rows))
 0616                {
 0617                    book.SaveAs(stream);
 618
 0619                    return File(stream.ToArray(),
 0620                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0621                        "Price-template.xlsx");
 622                }
 623            }
 0624        }
 625
 626        /// <summary>
 627        /// Загрузка файла расписания работы платформы
 628        /// </summary>
 629        /// <remarks>author i.rebenok</remarks>
 630        /// <param name="file">файл с расписанием</param>
 631        /// <param name="id">id склада</param>
 632        [HttpPost("UploadWorkSchedule/{id}")]
 633        [SwaggerResponse(200, "Успешно", typeof(UploadResultDTO))]
 634        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 635        [Authorize(Roles = Role.SystemAdmin)]
 636        public async Task<IActionResult> UploadWorkSchedule(long id, IFormFile file)
 0637        {
 638            DataTable dataTable;
 639            string fileName;
 640
 0641            using (var stream = _diskStorage.SaveUpload(file, out fileName))
 0642            {
 0643                LogLoadBegins($"UploadWorkSchedule", fileName);
 644
 0645                System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
 0646                var readerConfig = new ExcelReaderConfiguration()
 0647                {
 0648                    AutodetectSeparators = new char[] { ',', ';', '\t', '|', '#' },
 0649                    AnalyzeInitialCsvRows = 0
 0650                };
 0651                using (var reader = file.ContentType switch
 0652                {
 0653                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => ExcelReaderFactory.CreateRead
 0654                    "application/vnd.ms-excel" => ExcelReaderFactory.CreateCsvReader(stream, readerConfig),
 0655                    "text/csv" => ExcelReaderFactory.CreateCsvReader(stream, readerConfig),
 0656                    _ => throw new ArgumentException($"File type {file.Name} not supported"),
 0657                })
 0658                {
 0659                    var conf = new ExcelDataSetConfiguration
 0660                    {
 0661                        ConfigureDataTable = _ => new ExcelDataTableConfiguration
 0662                        {
 0663                            UseHeaderRow = true,
 0664                            ReadHeaderRow = (rowReader) =>
 0665                            {
 0666                                rowReader.Read(); // пропускаем описание шаблона
 0667                                rowReader.Read(); // пропускаем русские колонки
 0668                            }
 0669                        }
 0670                    };
 0671                    dataTable = reader.AsDataSet(conf).Tables[0];
 0672                }
 0673            }
 674
 0675            if (!dataTable.Columns.Contains("BeginTime"))
 0676            {
 0677                throw new ArgumentException("Поле 'BeginTime' не найдено");
 678            }
 0679            if (!dataTable.Columns.Contains("EndTime"))
 0680            {
 0681                throw new ArgumentException("Поле 'EndTime' не найдено");
 682            }
 0683            if (!dataTable.Columns.Contains("Offset"))
 0684            {
 0685                throw new ArgumentException("Поле 'Offset' не найдено");
 686            }
 0687            if (!dataTable.Columns.Contains("IsWorkingDay"))
 0688            {
 0689                throw new ArgumentException("Поле 'IsWorkingDay' не найдено");
 690            }
 691
 0692            var list = new List<WorkScheduleFromFileDTO>();
 0693            foreach (DataRow row in dataTable.Rows)
 0694            {
 0695                list.Add(new WorkScheduleFromFileDTO
 0696                {
 0697                    BeginTime = (row["BeginTime"].ToString()?.Trim() ?? string.Empty).NormalizeName(),
 0698                    EndTime = (row["EndTime"].ToString()?.Trim() ?? string.Empty).NormalizeName(),
 0699                    Offset = (row["Offset"].ToString()?.Trim() ?? string.Empty).NormalizeName(),
 0700                    IsWorkingDay = (row["IsWorkingDay"].ToString()?.Trim() ?? string.Empty).NormalizeName()
 0701                });
 0702            }
 0703            var errors = await _uploadWorker.Load(id,list);
 704
 0705            var result = new UploadResultDTO() { succeed = true };
 0706            if (errors.Any())
 0707            {
 0708                result.succeed = false;
 709
 0710                var rows = new List<string[]>();
 0711                rows.Add(new string[] { "Шаблон загрузки расписания работы платформы (* - поле обязательно для заполнени
 0712                rows.Add(new string[] { "*Дата и время начала дня", "*Дата и время окончания дня", "*Часовой пояс склада
 0713                rows.Add(new string[] { "BeginTime", "EndTime", "Offset", "IsWorkingDay", "Error" });
 0714                errors.ForEach(x =>
 0715                {
 0716                    rows.Add(new string[]
 0717                    {
 0718                            x.BeginTime,
 0719                            x.EndTime,
 0720                            x.Offset,
 0721                            x.IsWorkingDay,
 0722                            x.Message
 0723                    });
 0724                });
 0725                var csv = CsvUtil.ToCsv(rows);
 726
 0727                result.errorCount = rows.Count;
 0728                result.reportUrl = _diskStorage.SaveDownload("work_schedule_errors.csv", csv);
 0729            }
 730
 0731            LogLoadEnds($"UploadWorkSchedule", fileName, !result.succeed);
 732
 0733            return Ok(result);
 0734        }
 735
 736        /// <summary>
 737        /// Получение файла шаблона для загрузки расписания работы платформы
 738        /// </summary>
 739        /// <remarks>author i.rebenok</remarks>
 740        /// <param name="fileType">формат выходного файла (excel, csv), по умолчанию excel</param>
 741        /// <returns>file</returns>
 742        [HttpGet("GetWorkSchedulerTemplate")]
 743        [SwaggerResponse(200, "Успешно", typeof(FileStreamResult))]
 744        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 745        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator)]
 746        public IActionResult GetWorkSchedulerTemplate(string fileType = "excel")
 0747        {
 0748            var rows = new List<string[]>();
 0749            rows.Add(new string[] { "Шаблон загрузки расписания работы платформы (* - поле обязательно для заполнения, *
 0750            rows.Add(new string[] { "*Дата и время начала дня", "*Дата и время окончания дня", "*Часовой пояс склада", "
 0751            rows.Add(new string[] { "BeginTime", "EndTime", "Offset", "IsWorkingDay" });
 0752            rows.Add(new string[] { "2020-08-01 09:00:00", "2020-08-01 19:00:00", "3", "true" });
 0753            rows.Add(new string[] { "02.08.2020 09:00:00", "02.08.2020 19:00:00", "3", "false" });
 754
 0755            var stream = new MemoryStream();
 0756            if (fileType == "csv")
 0757            {
 0758                var csv = CsvUtil.ToCsv(rows);
 0759                using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true))
 0760                {
 0761                    writer.Write(csv);
 0762                }
 0763                stream.Position = 0;
 764
 0765                Response.Headers.Add("Content-Disposition", $"attachment;filename=Schedule-template.csv");
 0766                return File(stream, "text/csv");
 767            }
 768            else
 0769            {
 0770                using (var book = CsvUtil.ToExcel(rows))
 0771                {
 0772                    book.SaveAs(stream);
 773
 0774                    return File(stream.ToArray(),
 0775                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 0776                        "Schedule-template.xlsx");
 777                }
 778            }
 0779        }
 780
 781        /// <summary>
 782        /// Временный метод для создания превью из оригиналов уже существующих картинок
 783        /// </summary>
 784        /// <remarks>author: i. rebenok</remarks>
 785        /// <returns></returns>
 786        [HttpGet("PhotoProcessing")]
 787        [SwaggerResponse(200, "Успешно", typeof(FileResult))]
 788        [SwaggerResponse(404, "Нет записей", typeof(ErrorDTO))]
 789        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 790        [Authorize(Roles = Role.SystemAdmin)]
 791        public async Task<IActionResult> PhotoProcessing()
 0792        {
 0793            var photos = new List<Photo>();
 0794            var rows = new List<string[]>();
 0795            rows.Add(new string[] { "id", "Оригинал", "Высота", "Ширина", "Превью", "Высота", "Ширина", "Статус", "Комме
 0796            foreach (var item in await this.service.GetAllPhotos())
 0797            {
 0798                int newPrevWidth = 0, newPrevHeight = 0;
 0799                var fullSizePath = _imageSettings.ImageSavePath.TrimEnd('/') + item.FullSizeUrl;
 0800                var previewSizePath = string.IsNullOrWhiteSpace(Path.GetExtension(fullSizePath)) ? fullSizePath + "_prev
 0801                if (!System.IO.File.Exists(fullSizePath))
 0802                {
 0803                    rows.Add(new string[] { item.Id.ToString(), item.FullSizeUrl, "", "", "", "", "", "Не обработано", "
 0804                    continue;
 805                }
 806                try
 0807                {
 0808                    using (var streamImg = System.IO.File.OpenRead(fullSizePath))
 0809                    using (var inputStream = new SKManagedStream(streamImg))
 0810                    using (var original = SKBitmap.Decode(inputStream))
 0811                    {
 0812                        if (original.Width <= _imageSettings.ImagePreveiwWidth && original.Height <= _imageSettings.Imag
 0813                        {
 0814                            rows.Add(new string[] { item.Id.ToString(), item.FullSizeUrl, original.Height.ToString(), or
 0815                            continue;
 816                        }
 0817                        if (original.Width > original.Height)  //сохраняем пропорции
 0818                        {
 0819                            newPrevWidth = _imageSettings.ImagePreveiwWidth;
 0820                            newPrevHeight = original.Height * _imageSettings.ImagePreveiwWidth / original.Width;
 0821                        }
 822                        else
 0823                        {
 0824                            newPrevWidth = original.Width * _imageSettings.ImagePreveiwHeight / original.Height;
 0825                            newPrevHeight = _imageSettings.ImagePreveiwHeight;
 0826                        }
 0827                        using (var resized = original.Resize(new SKImageInfo(newPrevWidth, newPrevHeight), SKFilterQuali
 0828                        using (var image = SKImage.FromBitmap(resized))
 0829                        using (var output = System.IO.File.OpenWrite(previewSizePath))
 0830                            image.Encode(SKEncodedImageFormat.Jpeg, 75).SaveTo(output);
 831
 0832                        item.FullSizeHeight = original.Height;
 0833                        item.FullSizeWidth = original.Width;
 0834                        item.PreviewUrl = Path.GetFileName(previewSizePath);
 0835                        item.PreviewHeight = newPrevHeight;
 0836                        item.PreviewWidth = newPrevWidth;
 0837                        photos.Add(item);
 0838                        rows.Add(new string[] { item.Id.ToString(), item.FullSizeUrl, original.Height.ToString(), origin
 0839                    }
 0840                }
 0841                catch (Exception ex)
 0842                {
 0843                    rows.Add(new string[] { item.Id.ToString(), item.FullSizeUrl, "", "", "", "", "", "Не обработано", e
 0844                    continue;
 845                }
 0846            }
 0847            await this.service.UpdatePhotos(photos);
 0848            var csv = CsvUtil.ToCsv(rows);
 0849            Response.Headers.Add("Content-Disposition", $"attachment;filename=PhotoProcessingReport.csv");
 0850            return File(Encoding.UTF8.GetBytes(csv), System.Net.Mime.MediaTypeNames.Text.Plain);
 0851        }
 852
 0853        static char[] trimChars = new char[] { '/', '\\' };
 854
 0855        private static IMapper ToCatalogGoodDtoMapper(ImagesSettings _imagesSettings) => new MapperConfiguration(cfg =>
 0856        {
 0857            cfg.CreateMap<Good, GoodCatalogDTO>()
 0858                .ForMember(d => d.Barcode, e =>
 0859                    e.MapFrom((s => s.GoodBarcodes.FirstOrDefault(g => g.IsPrimary) != null
 0860            ? s.GoodBarcodes.FirstOrDefault(g => g.IsPrimary).BarCode.Code
 0861            : s.DefaultBarCode.Code)))
 0862                .ForMember(d => d.BrandId, e => e.MapFrom(s => s.Brand.Id));
 0863            cfg.CreateMap<Photo, PhotoDTO>()
 0864                .ForMember(d => d.PreviewUrl, e => e.MapFrom(s => _imagesSettings.ImageUrl.TrimEnd(trimChars) + '/' + s.
 0865                .ForMember(d => d.FullSizeUrl, e => e.MapFrom(s => _imagesSettings.ImageUrl.TrimEnd(trimChars) + '/' + s
 0866            cfg.CreateMap<Brand, IdNameDTO>();
 0867        }).CreateMapper();
 868
 869        async Task CheckDepartment(long id)
 0870        {
 0871            var department = _authService.IsUserPlatform()
 0872                    ? await _departmentService.GetDepartment(id)
 0873                    : await _departmentService.GetDepartment(id, _authService.ContragentId);
 0874            if (department == null)
 0875            {
 0876                throw new KeyNotFoundException();
 877            }
 0878        }
 879    }
 880}