< Summary

Class:SVETA.Api.Controllers.PriceTrendsController
Assembly:SVETA.Api
File(s):/opt/dev/sveta_api_build/SVETA.Api/Controllers/PriceTrendsController.cs
Covered lines:0
Uncovered lines:153
Coverable lines:153
Total lines:447
Line coverage:0% (0 of 153)
Covered branches:0
Total branches:68
Branch coverage:0% (0 of 68)

Metrics

MethodLine coverage Branch coverage
.ctor(...)0%100%
GetPriceTrends()0%0%
GetPriceTrend()0%0%
GetSourceGoods()0%0%
CreatePriceTrend()0%0%
UpdatePriceTrend()0%0%
CreatePriceTrendDetail()0%0%
UpdatePriceTrendDetail()0%0%
DeletePriceTrend()0%100%
DeletePriceTrendDetail()0%100%
UploadFromFile()0%100%
UploadFromFileTemplate()0%100%

File(s)

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

#LineLine coverage
 1using System;
 2using Microsoft.AspNetCore.Authorization;
 3using System.IO;
 4using System.Data;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Threading.Tasks;
 8using AutoMapper;
 9using Microsoft.AspNetCore.Mvc;
 10using Microsoft.Extensions.Logging;
 11using Newtonsoft.Json;
 12using SVETA.Api.Data.DTO;
 13using SVETA.Api.Data.DTO.DepartmentDTO;
 14using SVETA.Api.Data.DTO.Prices.PriceTrend;
 15using Swashbuckle.AspNetCore.Annotations;
 16using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 17using WinSolutions.Sveta.Server.Services.Interfaces;
 18using SVETA.Api.Services.Interfaces;
 19using Microsoft.AspNetCore.Http;
 20using ExcelDataReader;
 21using SVETA.Api.Data.Domain;
 22using Microsoft.Extensions.Options;
 23using WinSolutions.Sveta.Common.Extensions;
 24using SVETA.Api.Services.Implements;
 25using WinSolutions.Sveta.Common;
 26using WinSolutions.Sveta.Server.Data.DataModel.Extensions;
 27using SVETA.Api.Helpers;
 28using System.Text;
 29
 30namespace SVETA.Api.Controllers
 31{
 32    [Authorize]
 33    [Route("api/v1/PriceTrends")]
 34    [ApiController]
 35    public class PriceTrendsController : SvetaController
 36    {
 37        const string _routeUrl = "api/v1/PriceTrends";
 38        private readonly IPriceTrendService _service;
 39        private readonly IGoodService _goodService;
 40        private readonly IDepartmentService _departmentService;
 41        private readonly IPriceWorker _priceWorker;
 42        private readonly IAuthenticationService _authService;
 43        private readonly ILogger<PriceTrendsController> _logger;
 44        private readonly ImagesSettings _imageSettings;
 45        IDiskStorageService _diskStorage;
 46
 47        public PriceTrendsController(IPriceTrendService service,
 48            IDepartmentService departmentService,
 49            IGoodService goodService,
 50            IAuthenticationService authService,
 51            IPriceWorker priceWorker,
 52            IDiskStorageService diskStorage,
 53            IOptions<ImagesSettings> option,
 054            ILogger<PriceTrendsController> logger) : base(logger)
 055        {
 056            _goodService = goodService;
 057            _priceWorker = priceWorker;
 058            _authService = authService;
 059            _service = service;
 060            _departmentService = departmentService;
 061            _logger = logger;
 062            _imageSettings = option.Value;
 063            _diskStorage = diskStorage;
 064        }
 65
 66        /// <summary>
 67        /// Возвращает шапки переоценок
 68        /// </summary>
 69        /// <remarks>author: i.rebenok</remarks>
 70        /// <param name="page">Любое значение ниже нуля изменится на 1, пагинация: номер страницы</param>
 71        /// <param name="limit">Любое значение ниже нуля изменится на 10, пагинация: размер страницы</param>
 72        /// <param name="filter">фильтр по товарам, входящим в переоценку</param>
 73        /// <param name="departmentId">id склада поставщика. Если 0, то все переоценки выводить, если >0,то только с эти
 74        /// <param name="sort">сортировка по одному из полей
 75        /// по docnumber|desc, creationdate,creationdate|desc, begindate,begindate|desc, goodscount,goodscount|desc. Сор
 76        /// <param name="beginDate">Дата начала дейсвтия. По умолчанию null = выводим все</param>
 77        /// <param name="endDate">Дата окончания дейсвтия. По умолчанию null = выводим все</param>
 78        [HttpGet("")]
 79        [SwaggerResponse(200, "Успешно", typeof(BaseResponseDTO<PriceTrendsResponseDTO>))]
 80        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 81        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 82        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 83        public async Task<IActionResult> GetPriceTrends(int page = 1, int limit = 10, long departmentId = 0, DateTimeOff
 084        {
 085            filter = filter.NormalizeName();
 086            page = page < 1 ? 1 : page;
 087            limit = limit < 1 ? 10 : limit;
 088            var result = await _service.GetPriceTrends(page - 1, limit, departmentId, beginDate.GetDate(), endDate.GetDa
 089            var param = $"sort={sort}&departmentId={departmentId}&beginDate={beginDate.ToISOString()}&endDate={endDate.T
 090            var response = new BaseResponseDTO<PriceTrendsResponseDTO>(_routeUrl, page, (int)limit, result.TotalFiltered
 091            {
 092                Data = result.Result.Select(x => new PriceTrendsResponseDTO(x)).ToList(),
 093            };
 094            return Ok(response);
 095        }
 96
 97        /// <summary>
 98        /// Возвращает изменение цены по id переоценки
 99        /// </summary>
 100        /// <remarks>author: i.rebenok</remarks>
 101        /// <param name="page">Любое значение ниже нуля изменится на 1, пагинация: номер страницы</param>
 102        /// <param name="limit">Любое значение ниже нуля изменится на 10, пагинация: размер страницы</param>
 103        /// <param name="id">Идентификатор записи</param>
 104        /// <param name="filter">фильтр по названиям товаров, входящих в переоценку</param>
 105        /// <returns></returns>
 106        [HttpGet("{id}")]
 107        [SwaggerResponse(200, "Успешно", typeof(PriceTrendResponseDTO))]
 108        [SwaggerResponse(404, "Нет записей", typeof(EmptyResult))]
 109        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 110        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(string))]
 111        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 112        public async Task<IActionResult> GetPriceTrend([SwaggerParameter(Required = true)] long id, int page = 1, int li
 0113        {
 0114            filter = filter.NormalizeName();
 0115            page = page < 1 ? 1 : page;
 0116            limit = limit < 1 ? 10 : limit;
 0117            var ptrend = _authService.IsUserPlatform() ? await _service.GetPriceTrend(id, null) : await _service.GetPric
 0118            if (ptrend == null)
 0119                return NotFoundResult($"Переоценка с id={id} не найдена");
 0120            var details = await _service.GetPriceTrendDetails(ptrend.Id, page - 1, limit, filter);
 0121            details.Result.ForEach(x => x.Good.Photos.SetPhotoUrl(_imageSettings));
 0122            var param = $"";
 0123            var trendDTO = new PriceTrendResponseDTO(ptrend);
 0124            trendDTO.PriceTrendDetails = new BaseResponseDTO<PriceTrendGoodsDTO>(_routeUrl, page, (int)limit, details.To
 0125            {
 0126                Data = details.Result.Select(x => new PriceTrendGoodsDTO(x)).ToList(),
 0127            };
 128            //цена ближайшей переоценки , которая уже начала действовать, по этом товару и департаменту для каждого това
 0129            foreach (var item in trendDTO.PriceTrendDetails.Data)
 0130                item.PriceCurrent = (await _service.GetCurrentPriceFromDetail(item.GoodId, trendDTO.SupplierDepartment.I
 0131            return Ok(trendDTO);
 132
 0133        }
 134
 135        /// <summary>
 136        /// Список-источник товаров для добавления в переоценку
 137        /// </summary>
 138        /// <remarks>author: i.rebenok</remarks>
 139        /// <param name="page">Любое значение ниже нуля изменится на 1, пагинация: номер страницы</param>
 140        /// <param name="limit">Любое значение ниже нуля изменится на 10, пагинация: размер страницы</param>
 141        /// <param name="id">Идентификатор переоценки</param>
 142        /// <param name="filter">фильтр по названиям или коду товара</param>
 143        /// <param name="categoryId">0 - все категории, иначе конкретная. 0 по умолчанию</param>
 144        /// <param name="sort">сортировка по одному из полей
 145        /// по code,code|desc, name,name|desc. Сортировка по умолчанию по name</param>
 146        /// <returns></returns>
 147        [HttpGet("{id}/SourceGoods")]
 148        [SwaggerResponse(200, "Успешно", typeof(BaseResponseDTO<PriceTrendsSourceGoodsResponseDTO>))]
 149        [SwaggerResponse(404, "Нет записей", typeof(EmptyResult))]
 150        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 151        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(string))]
 152        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 153        public async Task<IActionResult> GetSourceGoods(long id, int page = 1, int limit = 10, long categoryId = 0, stri
 0154        {
 0155            filter = filter.NormalizeName();
 0156            var ptrend = _authService.IsUserPlatform() ? await _service.GetPriceTrendWithDetails(id, null) : await _serv
 0157            if (ptrend == null)
 0158                return NotFoundResult($"Переоценка с id={id} не найдена");
 0159            var goods = await _goodService.GetActiveGoods(page-1, limit, ptrend.SupplierDepartmentId, categoryId, sort, 
 0160            var param = $"id={id}&categoryId={categoryId}&sort={sort}";
 0161            var response = new BaseResponseDTO<PriceTrendsSourceGoodsResponseDTO>(_routeUrl + "/" + id + "/SourceGoods",
 0162            {
 0163                Data = goods.Result.Select(x => new PriceTrendsSourceGoodsResponseDTO()
 0164                {
 0165                    Id = x.Id,
 0166                    VendorCode = x.GetActualVendorCode(ptrend.SupplierDepartmentId),
 0167                    Name = x.Name,
 0168                    UniqueCode = x.UniqueCode,
 0169                    IsInTrend = (ptrend.PriceTrendDetails.Any(d => d.Good.Id == x.Id && !d.IsDeleted) ? true : false),
 0170                    TrendDetailId = ptrend.PriceTrendDetails.FirstOrDefault(d => d.Good.Id == x.Id)?.Id
 0171                }).ToList(),
 0172            };
 0173            return Ok(response);
 0174        }
 175
 176        /// <summary>
 177        /// Создает шапку переоценки
 178        /// </summary>
 179        /// <remarks>author: i.rebenok</remarks>
 180        /// <param name="data">PriceTrendRequestDTO</param>
 181        [HttpPost]
 182        [SwaggerResponse(201, "Успешно", typeof(PriceTrendsResponseDTO))]
 183        [SwaggerResponse(400, "Ошибка в параметрах запроса", typeof(ErrorDTO))]
 184        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 185        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 186        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 187        public async Task<IActionResult> CreatePriceTrend([FromBody] [SwaggerParameter(Required = true)] PriceTrendReque
 0188        {
 189
 0190            if (!ModelState.IsValid)
 0191            {
 0192                return BadRequestResult("Некорректные входные данные");
 193            }
 0194            var ptrend = new PriceTrend()
 0195            {
 0196                BeginDate = data.BeginDate.UtcDateTime.Date,
 0197                SupplierDepartment = _authService.IsUserPlatform() ? await _departmentService.GetDepartment(data.Departm
 0198                DocNumber = "0"
 0199            };
 0200            if (ptrend.SupplierDepartment == null)
 0201                return BadRequestResult(new ErrorDTO() { error = $"Подразделение с id={data.DepartmentId} не найдено" })
 0202            await _service.CreatePriceTrend(ptrend);
 0203            return StatusCode(201, new PriceTrendsResponseDTO(ptrend));
 0204        }
 205
 206        /// <summary>
 207        /// Обновляет шапку переоценки
 208        /// </summary>
 209        /// <remarks>author i.rebenok</remarks>
 210        /// <param name="id">id переоценки</param>
 211        /// <param name="data">PriceTrendRequestDTO</param>
 212        [HttpPut("{id}")]
 213        [SwaggerResponse(200, "Успешно обновлено", typeof(PriceTrendsResponseDTO))]
 214        [SwaggerResponse(400, "Некорректные входные данные", typeof(EmptyResult))]
 215        [SwaggerResponse(404, "Нет записей", typeof(EmptyResult))]
 216        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 217        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(string))]
 218        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 219        public async Task<IActionResult> UpdatePriceTrend([SwaggerParameter(Required = true)] long id, [FromBody] [Swagg
 0220        {
 0221            if (!ModelState.IsValid)
 0222            {
 0223                return BadRequestResult("Некорректные входные данные");
 224            }
 0225            var ptrend = _authService.IsUserPlatform() ? await _service.GetPriceTrend(id, null) : await _service.GetPric
 0226            if (ptrend == null)
 0227                return NotFoundResult($"Переоценка с id={id} не найдена");
 0228            ptrend.BeginDate = data.BeginDate.Date;
 0229            ptrend.SupplierDepartment = _authService.IsUserPlatform() ? await _departmentService.GetDepartment(data.Depa
 0230            if (ptrend.SupplierDepartment == null)
 0231                return BadRequest(new ErrorDTO() { error = $"Подразделение с id={data.DepartmentId} не найдено" });
 0232            await _service.UpdatePriceTrend(ptrend);
 0233            return Ok(new PriceTrendsResponseDTO(ptrend));
 0234        }
 235
 236        /// <summary>
 237        /// Создает позицию в переоценке
 238        /// </summary>
 239        /// <remarks>author: i.rebenok</remarks>
 240        /// <param name="id">id переоценки</param>
 241        /// <param name="data">PriceTrendDetailRequestDTO</param>
 242        [HttpPost("{id}/Items")]
 243        [SwaggerResponse(201, "Успешно", typeof(EmptyResult))]
 244        [SwaggerResponse(400, "Ошибка в параметрах запроса", typeof(ErrorDTO))]
 245        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 246        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 247        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 248        public async Task<IActionResult> CreatePriceTrendDetail([SwaggerParameter(Required = true)] long id, [FromBody] 
 0249        {
 0250            if (!ModelState.IsValid)
 0251            {
 0252                return BadRequestResult("Некорректные входные данные");
 253            }
 254
 0255            var ptrend = _authService.IsUserPlatform() ? await _service.GetPriceTrend(id, null) : await _service.GetPric
 0256            if (ptrend == null)
 0257                return NotFoundResult($"Переоценка с id={id} не найдена");
 0258            var ptrendDetail = new PriceTrendDetail()
 0259            {
 0260                PriceNew = 0,
 0261                PriceOld = 0,
 0262                PriceTrend = ptrend,
 0263                Good = await _goodService.GetGood(data.GoodId)
 0264            };
 0265            ptrendDetail.Discount = ptrendDetail.Discount();
 0266            if (ptrendDetail.Good == null)
 0267                return BadRequestResult($"Не найден товар с id={data.GoodId}");
 0268            await _service.CreatePriceTrendDetail(ptrendDetail);
 0269            return StatusCode(201);
 0270        }
 271
 272        /// <summary>
 273        /// Обновляет позицию в переоценке
 274        /// </summary>
 275        /// <remarks>author i.rebenok</remarks>
 276        /// <param name="id">id позиции в переоценке</param>
 277        /// <param name="data">PriceTrendDetailRequestDTO</param>
 278        [HttpPut("Items/{id}")]
 279        [SwaggerResponse(200, "Успешно обновлено", typeof(EmptyResult))]
 280        [SwaggerResponse(400, "Некорректные входные данные", typeof(EmptyResult))]
 281        [SwaggerResponse(404, "Нет записей", typeof(EmptyResult))]
 282        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 283        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(string))]
 284        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 285        public async Task<IActionResult> UpdatePriceTrendDetail([SwaggerParameter(Required = true)] long id, [FromBody] 
 0286        {
 0287            if (!ModelState.IsValid)
 0288            {
 0289                return BadRequestResult("");
 290            }
 291            /*if (data.PriceNew < 0)
 292                return BadRequestResult("Новая цена не может быть меньше 0");
 293            if (data.PriceOld < 0)
 294                return BadRequestResult("Старая цена не может быть меньше 0");*/
 295
 0296            var ptrend = _authService.IsUserPlatform() ? await _service.GetPriceTrendDetail(id, null) : await _service.G
 0297            if (ptrend == null)
 0298                return NotFoundResult($"Переоценка с id={id} не найдена");
 0299            ptrend.PriceNew = data.PriceNew;
 0300            ptrend.PriceOld = data.PriceOld;
 0301            ptrend.Good = await _goodService.GetGood(data.GoodId);
 0302            ptrend.Discount = ptrend.Discount();
 0303            if (ptrend.Good == null)
 0304                return BadRequestResult($"Не найден товар с id={data.GoodId}");
 0305            await _service.UpdatePriceTrendDetail(ptrend);
 0306            return Ok();
 0307        }
 308
 309        /// <summary>
 310        /// Удаляет переоценку
 311        /// </summary>
 312        /// <remarks>author i.rebenok</remarks>
 313        /// <param name="id">id переоценки</param>
 314        [HttpDelete("{id}")]
 315        [SwaggerResponse(200, "Успешно удалено", typeof(EmptyResult))]
 316        [SwaggerResponse(404, "Нет записей", typeof(EmptyResult))]
 317        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 318        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(string))]
 319        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 320        public async Task<IActionResult> DeletePriceTrend([SwaggerParameter(Required = true)] long id)
 0321        {
 0322            await _service.DeletePriceTrend(id);
 0323            return Ok();
 0324        }
 325
 326        /// <summary>
 327        /// Удаляет позицию в переоценке
 328        /// </summary>
 329        /// <remarks>author i.rebenok</remarks>
 330        /// <param name="id">id позиции в переоценке</param>
 331        [HttpDelete("Items/{id}")]
 332        [SwaggerResponse(200, "Успешно удалено", typeof(EmptyResult))]
 333        [SwaggerResponse(404, "Нет записей", typeof(EmptyResult))]
 334        [SwaggerResponse(403, "Не разрешено для этого пользователя")]
 335        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(string))]
 336        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 337        public async Task<IActionResult> DeletePriceTrendDetail([SwaggerParameter(Required = true)] long id)
 0338        {
 0339            await _service.DeletePriceTrendDetail(id);
 0340            return Ok();
 0341        }
 342
 343        /// <summary>
 344        /// Загружает изменение цен из файла.
 345        /// Файл должен содержать колонки DepartmentId, Department, Good, VendorCode, Price, OldPrice
 346        /// </summary>
 347        /// <param name="file"></param>
 348        /// <remarks>author: oboligatov</remarks>
 349        [Obsolete]
 350        [HttpPost("UploadFromFile")]
 351        [SwaggerResponse(200, "Успешно", typeof(PriceTrendUploadResultDTO))]
 352        [SwaggerResponse(400, "Некорректная структура файла", typeof(ErrorDTO))]
 353        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 354        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 355        public async Task<IActionResult> UploadFromFile(IFormFile file)
 0356        {
 0357            throw new NotImplementedException();
 358        }
 359
 360        /// <summary>
 361        /// Загружает изменение цен из файла.
 362        /// Файл должен содержать колонки DepartmentId, Department, Good, VendorCode, Price, OldPrice
 363        /// </summary>
 364        /// <param name="file"></param>
 365        /// <remarks>author: oboligatov</remarks>
 366        [HttpGet("UploadFromFile/Template")]
 367        [SwaggerResponse(302, "Успешно", typeof(RedirectResult))]
 368        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 369        [Obsolete]
 370        [Authorize(Roles = Role.SystemAdmin + "," + Role.SupplierOwner + "," + Role.SupplierOperator)]
 371        public IActionResult UploadFromFileTemplate()
 0372        {
 0373            throw new NotImplementedException();
 374        }
 375
 376
 377        /// <summary>
 378        /// Экспортирует в файл детали переоценки
 379        /// </summary>
 380        /// <param name="id">id переоценки</param>
 381        /// <param name="fileType">формат выходного файла (excel, csv), по умолчанию excel</param>
 382        /// <returns></returns>
 383        [HttpGet("Export/{id}")]
 384        [SwaggerResponse(200, "Успешно", typeof(File))]
 385        [SwaggerResponse(500, "Ошибка на стороне сервера", typeof(ErrorDTO))]
 386        [SwaggerResponse(403, "Не разрешено для этого пользователя", typeof(ErrorDTO))]
 387        [Authorize(Roles = Role.SystemAdmin + "," + Role.SystemOperator + "," + Role.SupplierOwner + "," + Role.Supplier
 388        public async Task<IActionResult> Export([SwaggerParameter(Required = true)]long id, string fileType = "excel")
 389        {
 390            PriceTrend priceTrendDetails = await _service.GetPriceTrendWithDetails(id, null);
 391            if(priceTrendDetails == null)
 392                return NotFound($"Переоценка с id={id} не найдена");
 393
 394            var department = await _departmentService.GetDepartment(priceTrendDetails.SupplierDepartment.Id);
 395
 396            //только повелитель или местный юзер могут смотреть переоценки
 0397            if(!_authService.IsUserPlatform() && !department.UsersDepartments.Any(x => x.UserId == _authService.UserId))
 398                return Forbid("Доступ к переоценкам чужого департамента запрещён");
 399
 400            var rows = new List<string[]>();
 401            rows.Add(new string[] { "Шаблон загрузки цен (* - поле обязательно для заполнения, ** - одно из полей обязат
 402            rows.Add(new string[] { "**Уникальный код товара", "**Артикул", "**Штрихкод", "**Название товара", "*Акционн
 403            rows.Add(new string[] { "UniqueCode", "VendorCode", "BarCode", "GoodName", "Price", "OldPrice" });
 404
 405            priceTrendDetails.PriceTrendDetails
 0406                .Where(x => !x.Good.IsDeleted)
 407                .ToList()
 408                .ForEach(x =>
 0409                {
 0410                    rows.Add(new string[]
 0411                    {
 0412                        x.Good.UniqueCode,
 0413                        x.Good.GetActualVendorCode(priceTrendDetails.SupplierDepartmentId),
 0414                        x.Good.GetActualBarCode(),
 0415                        x.Good.Name,
 0416                        x.PriceNew.ToString(),
 0417                        x.PriceOld.ToString()
 0418                    });
 0419                });
 420
 421            var stream = new MemoryStream();
 422            if (fileType == "csv")
 423            {
 424                var csv = CsvUtil.ToCsv(rows);
 425                using (var writer = new StreamWriter(stream, encoding: Encoding.UTF8, leaveOpen: true))
 426                {
 427                    writer.Write(csv);
 428                }
 429                stream.Position = 0;
 430
 431                Response.Headers.Add("Content-Disposition", $"attachment;filename=price_trends_{priceTrendDetails.DocNum
 432                return File(stream, "text/csv");
 433            }
 434            else
 435            {
 436                using (var book = CsvUtil.ToExcel(rows))
 437                {
 438                    book.SaveAs(stream);
 439
 440                    return File(stream.ToArray(),
 441                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 442                        $"price_trends_{priceTrendDetails.DocNumber}.xlsx");
 443                }
 444            }
 445        }
 446    }
 447}