< Summary

Class:SVETA.Api.Services.Implements.MovementWorker
Assembly:SVETA.Api
File(s):/opt/dev/sveta_api_build/SVETA.Api/Services/Implements/MovementWorker.cs
Covered lines:498
Uncovered lines:580
Coverable lines:1078
Total lines:2324
Line coverage:46.1% (498 of 1078)
Covered branches:205
Total branches:548
Branch coverage:37.4% (205 of 548)

Metrics

MethodLine coverage Branch coverage
.ctor(...)100%100%
.cctor()100%100%
GetAnonymousMovements()0%0%
ClearAnonymousMovement()83.33%50%
GetShipmentStatusToExchange()100%75%
GetMovementListToExchange()93.75%75%
GetCountMovement()0%100%
GetMovement()78.26%66.66%
GetMovementItems()85.93%71.73%
CreateEmptyMovement()75%62.5%
CreateMovement()72.54%50%
CreateAnnonymMovement()80.55%59.09%
CreateLink(...)100%100%
CreatePostfixLink(...)100%25%
CreateMovementOnBase()92.5%64.28%
RemoveChildrenDraft()100%75%
PrintUpd()0%0%
GetFile()0%0%
PrintOrder()0%0%
CreateZip()0%0%
PrintSf()0%0%
PrintTtn()0%0%
PrintTorg()0%0%
PrintTn()0%0%
GetMovementHistory()0%0%
GetOrderDeliveryTypes()0%0%
SaveJournal()100%100%
GetRecordState()100%100%
GetMovementType()100%100%
FromAnnonymToDtoMapper()0%0%
ToDtoMapper(...)100%100%
ToItemDTOMapper(...)100%100%
GetAttachmentsInfo()0%0%
DownloadAttachment()0%0%
SetDeliveryType()87.5%50%
GetFullPath(...)0%0%
GetDiscountColor(...)80%16.66%
CheckSettings(...)66.66%50%
GetDraft()100%100%
PrepareStatusList()0%100%
SortMovement(...)0%0%

File(s)

/opt/dev/sveta_api_build/SVETA.Api/Services/Implements/MovementWorker.cs

#LineLine coverage
 1using AutoMapper;
 2using Microsoft.AspNetCore.Http;
 3using Microsoft.Extensions.Logging;
 4using SVETA.Api.Data.Domain;
 5using SVETA.Api.Data.DTO;
 6using SVETA.Api.Data.DTO.Movements;
 7using SVETA.Api.Helpers.Authorize;
 8using SVETA.Api.Services.Interfaces;
 9using System;
 10using System.Collections.Concurrent;
 11using System.Collections.Generic;
 12using System.Diagnostics;
 13using System.Globalization;
 14using System.IO;
 15using System.IO.Compression;
 16using System.Linq;
 17using System.Linq.Expressions;
 18using System.Text;
 19using System.Threading;
 20using System.Threading.Tasks;
 21using CrmVtbc;
 22using DocumentFormat.OpenXml.Wordprocessing;
 23using IdentityServer4.Extensions;
 24using Microsoft.AspNetCore.Mvc;
 25using Microsoft.AspNetCore.Routing;
 26using Microsoft.EntityFrameworkCore;
 27using Microsoft.EntityFrameworkCore.Internal;
 28using Microsoft.Extensions.Hosting;
 29using Microsoft.Extensions.Options;
 30using Newtonsoft.Json;
 31using SVETA.Api.Data.DTO.Cluster;
 32using System.Text.Json;
 33using Microsoft.Extensions.Configuration;
 34using SkiaSharp;
 35using SVETA.Api.Data.DTO.DepartmentDTO;
 36using SVETA.Api.Helpers;
 37using TidVtbc;
 38using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 39using WinSolutions.Sveta.Server.Data.DataModel.Kinds;
 40using WinSolutions.Sveta.Server.Domain;
 41using WinSolutions.Sveta.Server.Services.Interfaces;
 42using Wkhtmltopdf.NetCore;
 43using WinSolutions.Sveta.Common;
 44using WinSolutions.Sveta.Common.Extensions;
 45using WinSolutions.Sveta.Server.Data.DataModel.Extensions;
 46
 47namespace SVETA.Api.Services.Implements
 48{
 49    public class MovementWorker: IMovementWorker
 50    {
 51        private readonly ILogger<MovementWorker> _logger;
 52        private readonly IMovementService _movementService;
 53        private readonly IMovementTypeStatusService _movementTypeStatusService;
 54        private readonly IDepartmentService _departmentService;
 55        private readonly IDirectoriesService _dirService;
 56        private readonly ISupplyContractService _suplContractService;
 57        private readonly IUserService _userService;
 58        private readonly IGoodService _goodService;
 59        private readonly IContragentService _contragentService;
 60        private readonly IMovementStatusJournalService _movementStatusJournalService;
 61        private readonly IAuthenticationService _authUserService;
 62        private readonly IGeneratePdf _generatePdf;
 63        private readonly IEventService _eventService;
 64        private readonly INotificationWorker _notificationWorker;
 65        private readonly IExchangeTokenService _exchangeTokenService;
 66        private readonly IDiskStorageService _diskStorageService;
 67        private readonly CommonSettings _commonSettings;
 68        private readonly IMovementRouteActionsService _movementRouteActionsService;
 69        List<DiscountColor> discountColors;
 70        private Movement _currentMovement;
 71        private readonly IHostEnvironment _env;
 72        private readonly ImagesSettings _imagesSettings;
 73        private readonly IAnonymousMovementService _anonymousMovementService;
 74        private readonly ConfigurationsSettings _confSettings;
 11275        private long annonymContragentId = (long) -1;
 176        public static SemaphoreSlim _createAnonymMovementSlim = new SemaphoreSlim(1);
 177        public static SemaphoreSlim _createMovementSlim = new SemaphoreSlim(1);
 178        public static SemaphoreSlim _addItemMovementSlim = new SemaphoreSlim(1);
 179        public static SemaphoreSlim _changeQuantityMovementSlim = new SemaphoreSlim(1);
 80
 11281        public MovementWorker(IMovementService movementService,
 11282            IAuthenticationService authUserService,
 11283            IMovementTypeStatusService movementTypeStatusService,
 11284            IDepartmentService departmentService,
 11285            ISupplyContractService suplContractService,
 11286            IGoodService goodService,
 11287            IUserService userService,
 11288            IDirectoriesService dirService,
 11289            IContragentService contragentService,
 11290            IMovementStatusJournalService movementStatusJournalService,
 11291            IGeneratePdf generatePdf,
 11292            INotificationWorker notificationWorker,
 11293            IExchangeTokenService exchangeTokenService,
 11294            IDiscountColorService discountColorService,
 11295            IDiskStorageService diskStorageService,
 11296            IRestHoldService restHoldService,
 11297            IHostEnvironment env,
 11298            IOptions<CommonSettings> options,
 11299            IOptions<ImagesSettings> imageOptions,
 112100            IMovementRouteActionsService movementRouteActionsService,
 112101            IAnonymousMovementService anonymousMovementService,
 112102            IEventService eventService,
 112103            IOptions<ConfigurationsSettings> confSettings,
 112104            ILogger<MovementWorker> logger)
 112105        {
 112106            _env = env;
 112107            _eventService = eventService;
 112108            _movementService = movementService;
 112109            _authUserService = authUserService;
 112110            _movementTypeStatusService = movementTypeStatusService;
 112111            _suplContractService = suplContractService;
 112112            _departmentService = departmentService;
 112113            _goodService = goodService;
 112114            _dirService = dirService;
 112115            _userService = userService;
 112116            _contragentService = contragentService;
 112117            _logger = logger;
 112118            _movementStatusJournalService = movementStatusJournalService;
 112119            _notificationWorker = notificationWorker;
 112120            _exchangeTokenService = exchangeTokenService;
 112121            _generatePdf = generatePdf;
 112122            _diskStorageService = diskStorageService;
 112123            discountColors = discountColorService.GetDiscountColors(0, int.MaxValue, null, null).Result.Result;
 112124            _commonSettings = options.Value;
 112125            _imagesSettings = imageOptions.Value;
 112126            _movementRouteActionsService = movementRouteActionsService;
 112127            _anonymousMovementService = anonymousMovementService;
 112128            _confSettings = confSettings.Value;
 112129        }
 130
 131        ///  <summary>
 132        ///  Возвращает отфильтрованный и отсортированный список документов
 133        ///  </summary>
 134        /// <param name="inParam">Объект с параметрами для фильтрации</param>
 135        ///  <returns></returns>
 136        public async Task<PaginatedData<List<MovementDTO>>> GetMovements(MovementParam inParam)
 137        {
 138            //Проверим вошедшие параметры на возможность работы с ними, если что поменяем на пользовательские или ругнем
 139            inParam = await SetUserParam(inParam);
 140
 141            var state = await GetRecordState(inParam.StateId);
 142            var status = await _movementTypeStatusService.GetMovementStatus(inParam.StatusId);
 143            var movementType = await GetMovementType((long)inParam.Kind);
 144
 145            int total = 0, totalFiltered = 0;
 146            PaginatedData<List<Movement>> orders;
 147            IMapper mapper = ToDtoMapper();
 148
 149            var result = new PaginatedData<List<MovementDTO>>
 150            {
 151                TotalCount = 0,
 152                TotalFilteredCount = 0,
 153                Result = new List<MovementDTO>()
 154            };
 155
 156            var allActions = await _movementRouteActionsService.GetActions();
 157            switch (inParam.ShowAnonym.ToLower())
 158            {
 159                case "all":
 160                {
 161                    var localLimit = inParam.Limit;
 162                    var annonymList = await _anonymousMovementService.GetAnonymMovements(default, inParam.ReceiverId,
 163                        inParam.SupplierId, inParam.SenderId, inParam.FromDate, inParam.ToDate, inParam.Sort,
 164                        inParam.Page >= 1 ? inParam.Page - 1 : inParam.Page, inParam.Limit);
 165                    if (annonymList.TotalFilteredCount < inParam.Limit)
 166                    {
 167                        localLimit += inParam.Limit - annonymList.TotalFilteredCount;
 168                    }
 169                    orders = await _movementService.GetFilterMovements(inParam.CustomerId, inParam.ReceiverId,
 170                        inParam.SupplierId, inParam.FromDate, inParam.ToDate, movementType, status, state, inParam.Docum
 171                        inParam.Page >= 1 ? inParam.Page - 1 : inParam.Page,
 172                        localLimit, inParam.Sort, inParam.ExcludedStatuses, inParam.СontragentBuyerFilter, inParam.Sende
 173
 174                    total += orders.TotalCount + annonymList.TotalCount;
 175                    totalFiltered += orders.TotalFilteredCount + annonymList.TotalFilteredCount;
 176                    if (!inParam.GenerateExcel)
 177                    {
 0178                        orders.Result.ForEach(d => d.FillActions(allActions,
 0179                            _authUserService.ContragentKindId,
 0180                            _authUserService.Roles));
 181                        orders.Result = await PrepareStatusList(orders.Result, movementType);
 182                    }
 183
 184                    var list = mapper.Map<List<Movement>, List<MovementDTO>>(orders.Result);
 185                    var anonymMovementList = new List<Movement>();
 0186                    annonymList.Result.ForEach(d => anonymMovementList.Add((Movement)d));
 187                    var anonymMapper = ToDtoMapper(true);
 188                    list.AddRange(anonymMapper.Map<List<Movement>, List<MovementDTO>>(anonymMovementList));
 189                    var sortedList = SortMovement(list.AsQueryable(), inParam.Sort);
 190                    result.TotalCount = total;
 191                    result.TotalFilteredCount = totalFiltered;
 192                    result.Result = sortedList.Skip((inParam.Page >= 1 ? inParam.Page - 1 : inParam.Page) * inParam.Limi
 193                        .Take(inParam.Limit).ToList();
 194                    break;
 195                }
 196                case "anonymousonly":
 197                {
 198                    var userId = _authUserService.IsAnonym() ? _authUserService.AnonymousUuid : default;
 199                    if (_authUserService.IsAnonym() && userId == default)
 200                        return result;
 201                    var annonymList = await _anonymousMovementService.GetAnonymMovements(userId, inParam.ReceiverId,
 202                        inParam.SupplierId, inParam.SenderId, inParam.FromDate, inParam.ToDate, inParam.Sort,
 203                        inParam.Page,
 204                        inParam.Limit);
 205                    var anonymResult = new List<Movement>();
 0206                    annonymList.Result.ForEach(d => anonymResult.Add((Movement)d));
 207                    var anonymMapper = ToDtoMapper(true);
 208                    var anonymMovementDto = anonymMapper.Map<List<Movement>, List<MovementDTO>>(anonymResult);
 209                    result.Result =
 210                        anonymMovementDto;
 211                    result.TotalCount = annonymList.TotalCount;
 212                    result.TotalFilteredCount = annonymList.TotalFilteredCount;
 213                    break;
 214                }
 215                default:
 216                {
 217                    orders = await _movementService.GetFilterMovements(inParam.CustomerId, inParam.ReceiverId,
 218                        inParam.SupplierId, inParam.FromDate, inParam.ToDate, movementType, status, state, inParam.Docum
 219                        inParam.Limit, inParam.Sort, inParam.ExcludedStatuses, inParam.СontragentBuyerFilter, inParam.Se
 220                    result.TotalCount = orders.TotalCount;
 221                    result.TotalFilteredCount = orders.TotalFilteredCount;
 222                    if (orders.TotalFilteredCount > 0)
 223                    {
 224                        if (_authUserService.ContragentKindId == (long) ContragentKind.Retailer)
 225                        {
 226                            if (orders.Result.Any(d => d.MovementStatus.Id == (long) MovementsStatus.OrderDraft))
 227                            {
 228                                var unionsMovement = new List<Movement>();
 229                                for (int i = 0; i < orders.Result.Count; i++)
 230                                {
 231                                    var draft = orders.Result[i];
 232                                    if (draft.MovementStatus.Id == (long) MovementsStatus.OrderDraft)
 233                                    {
 234                                        if (draft.Items.Count > 0)
 235                                        {
 236                                            var inner = (_movementService.GetMovementReadonly(draft.Id, movementType)).R
 237                                            try
 238                                            {
 239                                                inner.UpdatePriceInDraft();
 240                                            }
 241                                            catch (Exception e)
 242                                            {
 243                                                _logger.LogError(e.Message);
 244                                                _logger.LogError($"Номер документа: {draft.Id}");
 245                                                break;
 246                                            }
 247
 248                                            inner.ReCalculate();
 249                                        }
 250                                    }
 251
 252                                    unionsMovement.Add(draft);
 253                                }
 254                                orders.Result = _movementService.SortMovement(unionsMovement.AsQueryable(), inParam.Sort
 255                                    .ToList();
 256                            }
 257                        }
 258
 259                        if (!inParam.GenerateExcel)
 260                        {
 0261                            orders.Result.ForEach(d => d.FillActions(allActions,
 0262                                _authUserService.ContragentKindId,
 0263                                _authUserService.Roles));
 264                            orders.Result = await PrepareStatusList(orders.Result, movementType);
 265                        }
 266                        result.Result = mapper.Map<List<Movement>, List<MovementDTO>>(orders.Result);
 267                    }
 268                    break;
 269                }
 270            }
 271            return result;
 272        }
 273
 274        /// <summary>
 275        /// Возвращает список анонимных заявок
 276        /// </summary>
 277        /// <param name="inParam">Параметры запроса</param>
 278        /// <returns></returns>
 279        public async Task<PaginatedData<List<MovementDTO>>> GetAnonymousMovements(MovementParam inParam)
 0280        {
 0281            inParam = await SetUserParam(inParam);
 282            PaginatedData<List<Movement>> orders;
 0283            IMapper mapper = ToDtoMapper();
 0284            var movements = await _anonymousMovementService.GetAnonymMovements(_authUserService.AnonymousUuid,
 0285                inParam.ReceiverId, inParam.SupplierId, inParam.SenderId, inParam.FromDate, inParam.ToDate, inParam.Sort
 0286                inParam.Page, inParam.Limit);
 0287            if (movements.TotalCount == 0)
 0288                return new PaginatedData<List<MovementDTO>> {Result = new List<MovementDTO>()};
 289
 0290            var data = new List<Movement>();
 0291            var allActions = await _movementRouteActionsService.GetActions();
 0292            movements.Result.ForEach(d =>
 0293            {
 0294                var movement = (Movement) d;
 0295                movement.FillActions(allActions, _authUserService.ContragentKindId,
 0296                    _authUserService.Roles);
 0297                data.Add(movement);
 0298            });
 299
 0300            return new PaginatedData<List<MovementDTO>>
 0301            {
 0302                TotalCount = movements.TotalCount,
 0303                TotalFilteredCount = movements.TotalFilteredCount,
 0304                Result = mapper.Map<List<Movement>, List<MovementDTO>>(data)
 0305            };
 0306        }
 307
 308        /// <summary>
 309        /// Возвращает кол-во корзин анонима с указанием товарного состава, складов и возможных магазинов для слияния
 310        /// </summary>
 311        /// <returns></returns>
 312        public async Task<AnonymousMovementMergeDto> GetAnonymousCache()
 313        {
 314            var list = await _anonymousMovementService.GetAnonymMovements(_authUserService.AnonymousUuid);
 315            var result = new AnonymousMovementMergeDto
 316            {
 317                Total = 0,
 318                Cache = new List<AnonymousCache>()
 319            };
 320            if (list.TotalFilteredCount == 0 || list.Result.All(d => d.Items.Count == 0))
 321                return result;
 322
 323            var departments =
 324                (await _departmentService.GetDepartmentsByUserAndKind(_authUserService.UserId, DepartmentKind.Shop,
 325                    default))
 326                .Where(d => d.Cluster != null)
 327                .Where(d => d.Contragent.ContractsAsBuyer
 328                    .Where(s => s.BeginDate <= DateTime.UtcNow && s.EndDate >= DateTime.UtcNow)
 329                    ?.Any()
 330                            ?? false)
 331                .ToList();
 332
 333            var listWithoutEmptyCache = list.Result.Where(x => x.Items.Count > 0).ToList();
 334
 335            result.Cache = listWithoutEmptyCache.Select(item =>
 2336            {
 2337                var itemMapper = ToItemDTOMapper(item.Sender.Id, discountColors);
 3338                item.Items.ForEach(g => g.Good.Photos.SetPhotoUrl(_imagesSettings));
 2339                var elem = new AnonymousCache
 2340                {
 2341
 2342                    Id = item.Id,
 2343                    Items = itemMapper.Map<List<MovementItem>, List<MovementItemResponseDTO>>(item.Items),
 2344                    Receiver = new DepartmentShortDTO(item.Receiver),
 2345                    Sender = new DepartmentShortDTO(item.Sender),
 2346                    AvailableReceiver = departments
 4347                        .Where(d => d.Cluster.WarehouseId == item.Sender.Id && d.Contragent.ContractsAsBuyer.FirstOrDefa
 348                        .Select(d => new DepartmentShortDTO(d)).ToList(),
 2349                    Reason = departments
 4350                        .Where(d => d.Cluster.WarehouseId == item.Sender.Id && d.Contragent.ContractsAsBuyer.FirstOrDefa
 2351                        ? "Товары из корзины не могут быть добавлены в заявку. Поставщик не обслуживает ваши подразделен
 2352                        : default,
 2353                    CanMerge = departments
 4354                        .Where(d => d.Cluster.WarehouseId == item.Sender.Id && d.Contragent.ContractsAsBuyer.FirstOrDefa
 2355                };
 2356                return elem;
 2357            }).ToList();
 358            result.Total = listWithoutEmptyCache.Count();
 359            return result;
 360        }
 361
 362        /// <summary>
 363        /// Добавляет товар из анонимной заявки в заявку магазина
 364        /// </summary>
 365        /// <param name="orderId">Идентификатор анонимной заявки</param>
 366        /// <param name="goodId">Идентификатор товара для добавления</param>
 367        /// <param name="receiverId">Идентификатор магазина для добавления</param>
 368        /// <returns></returns>
 369        public async Task<MessageDto> AddAnonymousGoodToOrder(long orderId, long goodId, long receiverId)
 370        {
 371            var anonymMovement = await _anonymousMovementService.GetAnonymMovement(orderId) ??
 372                                 throw new ArgumentException($"Не найдена анонимная заявка {orderId}");
 373            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 374            if (((Movement) anonymMovement).CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 375                _authUserService.ContragentId, _authUserService.IsOwner(), userDeps, false, _authUserService.AnonymousUu
 376            {
 377                throw new ForbidException();
 378            }
 379            var movement = await CreateMovement((long) MovementKind.Order, receiverId, 0);
 2380            var item = anonymMovement.Items.FirstOrDefault(d => d.Good.Id == goodId)
 381                       ?? throw new ArgumentException($"Не найден товар {goodId} в анонимной заявке");
 382            var activeState = await GetRecordState((long) RecordState.Active);
 383            movement.AddItem(item, activeState);
 384            movement.UpdatePriceInDraft();
 385            movement.ReCalculate();
 386            await _movementService.UpdateMovement(movement);
 2387            var addedItem = movement.Items.FirstOrDefault(d => d.Good.Id == goodId);
 388            if (item.Price != addedItem.Price)
 389                return new MessageDto { Body = $"Цена для товара \"{addedItem.Good.Name}\" изменилась." };
 390            return default;
 391        }
 392
 393        /// <summary>
 394        /// Очищает анонимную заявку
 395        /// </summary>
 396        /// <param name="movementId">Идентификатор анонимной заявки</param>
 397        /// <returns></returns>
 398        /// <exception cref="ForbidException"></exception>
 399        public async Task ClearAnonymousMovement(long movementId)
 1400        {
 1401            var anonymMovement = await _anonymousMovementService.GetAnonymMovement(movementId)
 1402                ?? throw new ArgumentException($"Не найдена анонимная заявка {movementId}");
 1403            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 1404            if (((Movement) anonymMovement).CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 1405                _authUserService.ContragentId, _authUserService.IsOwner(), userDeps, false, _authUserService.AnonymousUu
 0406            {
 0407                throw new ForbidException();
 408            }
 1409            anonymMovement.Items = new List<MovementItem>();
 1410            anonymMovement.PrepaimentSum = 0;
 1411            await _anonymousMovementService.UpdateAnonymMovement(anonymMovement);
 1412        }
 413
 414        /// <summary>
 415        /// Возвращает список документов со статусами для обмена с 1С
 416        /// </summary>
 417        /// <param name="tnx">Токен департамента в системе</param>
 418        /// <param name="from">Дата начала отбора - по умолчанию все</param>
 419        /// <param name="to">Дата окончания отбора - по умолчанию все</param>
 420        /// <param name="docNum">Номер документа для фильтрации</param>
 421        /// <returns></returns>
 422        public async Task<List<ExchangeMovementStatus>> GetShipmentStatusToExchange(Guid tnx, DateTime from, DateTime to
 2423        {
 2424            var token = await _exchangeTokenService.GetToken(tnx)
 2425                        ?? throw new ArgumentException("Токен не найден");
 1426            return await _movementService.GetMovementStatus(token.DepartmentId, from, to, docNum)
 1427                .Select(movement => new ExchangeMovementStatus
 1428                {
 1429                    GUID = movement.GUID,
 1430                    DocumentNumber = movement.DocumentNumber,
 1431                    DtCreated = movement.CreationDateTime,
 1432                    DtModified = movement.ModificationDateTime ?? DateTime.MinValue,
 1433                    Status = movement.MovementStatus.Code,
 1434                    DeliveryType = movement.DeliveryType == null ? default : movement.DeliveryType.Code
 1435                })
 1436                .OrderBy(movement => movement.DtCreated)
 1437                .ToListAsync();
 1438        }
 439
 440        /// <summary>
 441        /// Возвращает список документов для обмена с 1С
 442        /// </summary>
 443        /// <param name="tnx">Токен департамента в системе</param>
 444        /// <param name="from">Дата начала отбора - по умолчанию сутки назад</param>
 445        /// <param name="to">Дата окончания отбора - по умолчанию текущий день</param>
 446        /// <param name="kind">Тип документов</param>
 447        /// <returns>Task&lt;List&lt;MovementExchnageResponseDto&gt;&gt;</returns>
 448        /// <exception cref="ArgumentException"></exception>
 449        public async Task<List<MovementExchnageResponseDto>> GetMovementListToExchange(Guid tnx, DateTime from, DateTime
 2450        {
 2451            var token = await _exchangeTokenService.GetToken(tnx)
 2452                        ?? throw new ArgumentException("Токен не найден");
 1453            int diff = (to - from).Days;
 1454            if (diff > 10)
 0455                throw new ArgumentException("Интервал запроса слишком большой");
 1456            var type = await GetMovementType((long) kind) ??
 1457                       throw new ArgumentException($"Не найден тип документа {kind}");
 1458            var movements =
 1459                _movementService.GetFilterMovementsWithoutPagination(supplierId:token.Department.Contragent.Id, fromDate
 1460            _movementService.FillMovementsItems(movements);
 1461            var config = new MapperConfiguration(cfg =>
 462            {
 463                cfg.CreateMap<Movement, MovementExchnageResponseDto>()
 464                    .ForMember(d => d.DtModified, e => e.MapFrom(s => s.ModificationDateTime))
 465                    .ForMember(d => d.Status, e => e.MapFrom(s => s.MovementStatus.Code))
 466                    .ForMember(d => d.DtCreated, e => e.MapFrom(s => s.CreationDateTime))
 467                    .ForMember(d => d.Customer, e => e.MapFrom(s => s.Receiver))
 468                    .ForMember(d => d.Notes, e => e.MapFrom(s => s.Notes))
 469                    .ForMember(d => d.DeliveryType, e => e.MapFrom(s => s.DeliveryType.Code));
 470                cfg.CreateMap<MovementItem, MovementItemExchangeResponseDto>()
 471                    .ForMember(d => d.BarCode, e => e.MapFrom(s => s.Good.GetActualBarCode()))
 472                    .ForMember(d => d.VendorCode, e => e.MapFrom(s => s.Good.GetActualVendorCode(s.Movement.Sender.Id)))
 473                    .ForMember(d => d.UniqueCode, e => e.MapFrom(s => s.Good.UniqueCode));
 474                cfg.CreateMap<Department, DepartmentExchangeResponseDto>()
 475                    .ForMember(d => d.Inn, e => e.MapFrom(s => s.Contragent.Inn))
 476                    .ForMember(d => d.FullName, e => e.MapFrom(s => s.Name))
 477                    .ForMember(d => d.Phone, e => e.MapFrom(s => s.PhoneNumber))
 478                    .ForMember(d => d.Address, e => e.MapFrom(s => s.ActualAddress.FullAddress))
 479                    .ForMember(d => d.Kpp, e => e.MapFrom(s => s.Contragent.Kpp))
 480                    .ForMember(d => d.Ogrn, e => e.MapFrom(s => s.Contragent.Ogrn))
 481                    .ForMember(d => d.Okato, e => e.MapFrom(s => s.Contragent.Okato))
 482                    .ForMember(d => d.Okved, e => e.MapFrom(s => s.Contragent.Okved))
 483                    .ForMember(d => d.Okpo, e => e.MapFrom(s => s.Contragent.Okpo))
 484                    .ForMember(d => d.Taxsystem, e => e.MapFrom(s => s.Contragent.TaxSystemCRM))
 485                    .ForMember(d => d.Ownership, e => e.MapFrom(s => s.Contragent.Ownership))
 486                    .ForMember(d => d.JuridicAddress, e => e.MapFrom(s => s.Contragent.JuridicAddress.FullAddress));
 487                cfg.CreateMap<MovementNote, MovementNoteExchangeDto>()
 488                    .ForMember(d => d.DateTime, e => e.MapFrom(s => s.CreationDateTime))
 489                    .ForMember(d => d.User, e => e.MapFrom(s => s.CreatedByUser.Email));
 490            });
 1491            var mapper = config.CreateMapper();
 1492            var mp = mapper.Map<List<Movement>, List<MovementExchnageResponseDto>>(movements);
 1493            return await Task.FromResult(mp);
 1494        }
 495
 496        /// <summary>
 497        /// Возвращает количество документов определенного вида с фильтрацией
 498        /// </summary>
 499        /// <param name="inParam">Объект с параметрами для фильтрации</param>
 500        /// <returns></returns>
 501        public async Task<int> GetCountMovement(MovementParam inParam)
 0502        {
 0503            inParam = await SetUserParam(inParam);
 504
 0505            var state = await GetRecordState(inParam.StateId);
 0506            var status = await _movementTypeStatusService.GetMovementStatus(inParam.StatusId);
 0507            var movementType = await GetMovementType((long)inParam.Kind);
 508
 0509            return await _movementService.GetCountMovements(inParam.CustomerId, inParam.ReceiverId, inParam.SupplierId,
 0510                inParam.FromDate, inParam.ToDate, movementType, status, state, inParam.DocumentNumber, inParam.ExcludedS
 0511        }
 512
 513        /// <summary>
 514        /// Возвращает документ по его id проверяет возможность работы с этим документом
 515        /// </summary>
 516        /// <param name="id">Идентфикатор документа</param>
 517        /// <param name="kind">Тип документа</param>
 518        /// <returns>Task&lt;MovementWithNotesDTO&gt;</returns>
 519        public async Task<MovementDTO> GetMovement(long id, MovementKind kind, bool isAnonym = false)
 52520        {
 52521            var movementType = await GetMovementType((long) kind) ??
 52522                               throw new ArgumentException($"Не найден тип документа {(long) kind}");
 52523            Movement movement = _authUserService.IsAnonym() || isAnonym
 52524                ? await _anonymousMovementService.GetAnonymMovement(id)
 52525                : await _movementService.GetMovementReadonly(id, movementType)
 52526                ?? throw new KeyNotFoundException($"Документ не найден");
 527
 52528            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 52529            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 52530                _authUserService.ContragentId,
 52531                _authUserService.IsOwner(),
 52532                userDeps, _authUserService.IsAnonym(), _authUserService.AnonymousUuid))
 0533                throw new ForbidException();
 534
 51535            if (movement.MovementStatus.Id == (long) MovementsStatus.OrderDraft && _authUserService.IsUserRetailer())
 0536            {
 0537                movement.UpdatePriceInDraft();
 0538                movement.ReCalculate();
 0539            }
 51540            var allActions = await _movementRouteActionsService.GetActions();
 51541            movement.FillActions(allActions, _authUserService.ContragentKindId, _authUserService.Roles);
 51542            var result = ToDtoMapper(isAnonym).Map<Movement, MovementDTO>(movement);
 543
 51544            return await Task.FromResult(result);
 51545        }
 546
 547        /// <summary>
 548        /// Возвращает заказанные товары документа
 549        /// </summary>
 550        /// <param name="movementId">Идентификатор документа</param>
 551        /// <param name="sort">Сортировка элементов по умолчания по Id, name, name|desc, price, price|desc, created_in, 
 552        /// <param name="page">Номер страницы</param>
 553        /// <param name="limit">Количество для отбора</param>
 554        /// <param name="kind"></param>
 555        /// <param name="filter"></param>
 556        /// <returns>Task&lt;PaginatedData&lt;List&lt;MovementItemResponseDTO&gt;&gt;&gt;</returns>
 557        /// <exception cref="ForbidException">Нет прав для работы с эти документом</exception>
 558        public async Task<PaginatedData<List<MovementItemResponseDTO>>> GetMovementItems(long movementId, MovementKind k
 559            string filter, string sort, int page, int limit = 10)
 6560        {
 561
 6562            MovementType type = await GetMovementType((long) kind) ??
 6563                                throw new ArgumentException($"Тип документа {kind} не найден");
 6564            Movement movement = _authUserService.IsAnonym() ? await _anonymousMovementService.GetAnonymMovement(movement
 6565                await _movementService.GetMovementReadonly(movementId, type)
 6566                ?? throw new ArgumentException($"Документ {movementId} не найден");
 567
 6568            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 6569            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 6570                _authUserService.ContragentId,
 6571                _authUserService.IsOwner(),
 6572                userDeps))
 0573                throw new ForbidException();
 6574            _currentMovement = movement;
 6575            if (movement.Items == null || movement.Items.Count == 0)
 0576            {
 0577                return await Task.FromResult(new PaginatedData<List<MovementItemResponseDTO>> {Result = new List<Movemen
 578            }
 579
 6580            if (movement.MovementStatus.Id == (long) MovementsStatus.OrderDraft && _authUserService.IsUserRetailer())
 0581            {
 0582                movement.UpdatePriceInDraft();
 0583            }
 584
 12585            IQueryable<MovementItem> PrepareLocalItem() => movement.Items.Where(d =>
 32586                    string.IsNullOrWhiteSpace(filter) || d.Good.Name.ToLower().Contains(filter.ToLower()))
 12587                .AsQueryable();
 588
 6589            var slice = PrepareLocalItem().Select(d => new SliceMovementItem
 6590            {
 6591                Id = d.Id,
 6592                Good = d.Good,
 6593                Price = d.Price,
 6594                Quantity = d.Quantity,
 6595                Comment = d.Comment,
 6596                Sum = d.Price * d.Quantity,
 6597                RecState = d.RecState,
 6598                CreationDateTime = d.CreationDateTime
 6599            });
 600
 6601            Expression<Func<SliceMovementItem, object>> sortExpression = (sort?.Split("|").ElementAtOrDefault(0) ?? "").
 6602            {
 6603                "name" => e => e.Good.Name,
 6604                "price" => e => e.Good.Prices.Actual(movement.Sender.Id).PriceNew,
 6605                "created_on" => e => e.CreationDateTime,
 6606                "vendorcode" => e => e.Good.GetActualVendorCode(movement.Sender.Id),
 6607                "quantity" => e => e.Quantity,
 6608                "totalndssum" => e => e.Sum,
 6609                _ => e => e.Id
 6610            };
 6611            int totalCount = movement.Items.Count;
 612
 613
 6614            int filteredCount = PrepareLocalItem().Count();
 615
 6616            var result = (sort?.Split("|").ElementAtOrDefault(1) ?? "").ToLower() switch
 6617            {
 6618                "desc" => slice.OrderByDescending(sortExpression),
 6619                _ => slice.OrderBy(sortExpression)
 6620            };
 621
 6622            var itemsList = result.Skip((page < 2 ? 0 : page - 1) * limit).Take(limit).ToList();
 16623            itemsList.ForEach(g => g.Good.Photos.SetPhotoUrl(_imagesSettings));
 624
 6625            var paginatedData = new PaginatedData<List<MovementItemResponseDTO>>
 6626            {
 6627                TotalCount = totalCount,
 6628                TotalFilteredCount = filteredCount,
 6629                Result = null
 6630            };
 6631            var items = new List<MovementItem>();
 16632            itemsList.ForEach(d => items.Add((MovementItem)d));
 6633            var dtoItems = ToItemDTOMapper(movement.Sender.Id, discountColors).Map<List<MovementItemResponseDTO>>(items)
 6634            if (movement.Children != null)
 0635            {
 0636                Parallel.ForEach(dtoItems, item =>
 0637                {
 0638                    item.ShipmentQuantity =
 0639                        movement.Children.Items.FirstOrDefault(d => d.Good.Id == item.Good.Id)?.Quantity ?? 0;
 0640                });
 0641            }
 642
 6643            paginatedData.Result = dtoItems;
 6644            return await Task.FromResult(paginatedData);
 6645        }
 646
 647        /// <summary>
 648        /// Создание пустого документа
 649        /// </summary>
 650        /// <param name="mKind">Тип документа</param>
 651        /// <param name="departmentId">Подразделение на которое создается заявка</param>
 652        /// <param name="parentId">Родительский документ (например, для отгрузки это заявка)</param>
 653        /// <returns></returns>
 654        public async Task<MovementDTO> CreateEmptyMovement(long mKind, long departmentId, long parentId = 0)
 60655        {
 60656            await _createMovementSlim.WaitAsync();
 657            try
 60658            {
 60659                var movement = await CreateMovement(mKind, departmentId, parentId);
 60660                var allActions = await _movementRouteActionsService.GetActions();
 60661                movement.FillActions(allActions, _authUserService.ContragentKindId, _authUserService.Roles);
 60662                var result = ToDtoMapper().Map<Movement, MovementDTO>(movement);
 60663                return await Task.FromResult(result);
 664            }
 0665            catch (Exception e)
 0666            {
 0667                _logger.LogError(e.Message + " " + e.StackTrace);
 0668                throw;
 669            } finally
 60670            {
 60671                _createMovementSlim.Release();
 60672            }
 673
 60674        }
 675
 676        /// <summary>
 677        /// Создание документа
 678        /// </summary>
 679        /// <param name="mKind">Тип документа</param>
 680        /// <param name="departmentId">идентификатор подразделения для которого создается документ</param>
 681        /// <param name="parentId">Идентификатор родителя</param>
 682        /// <returns></returns>
 683        /// <exception cref="ArgumentException"></exception>
 684        private async Task<Movement> CreateMovement(long mKind, long departmentId, long parentId = 0)
 61685        {
 61686            Contragent customer = await _contragentService.GetContragent(_authUserService.ContragentId);
 687
 61688            var department = await _departmentService.GetDepartment(departmentId) ??
 61689                             throw new ArgumentException($"Подразделение #{departmentId} не найдено");
 690
 61691            var cluster = department.Cluster;
 61692            if (cluster == null || cluster.IsDeleted)
 0693                throw new ArgumentException($"Подразделение {department.Name} не включено в кластер");
 61694            if (cluster.Warehouse == null || cluster.Warehouse.IsDeleted)
 0695                throw new ArgumentException($"У кластера {cluster.Name} отсутствует склад владелец");
 696
 61697            if (department.Contragent.Id != customer.Id)
 0698                throw new ArgumentException($"Подразделение #{department.Name} не подчиняется контрагенту #{customer.Sho
 699
 61700            var movementType = await GetMovementType(mKind);
 61701            var mStatus = await GetDraft(movementType) ??
 61702                          throw new ArgumentException($"Не найден статус черновик для типа {movementType.Name}");
 703
 61704            var draft = await _movementService.GetDraft(_authUserService.ContragentId, department.Id, cluster.WarehouseI
 705
 61706            if (draft != null)
 0707            {
 0708                return draft;
 709            }
 710
 61711            var contracts = await _suplContractService.GetSupplyContracts(customer.Id, true);
 61712            if (contracts.Count == 0)
 0713                throw new ArgumentException("Не найдено активных контрактов");
 714
 61715            var supply = contracts
 716                         .Where(d => d.EndDate >= DateTime.UtcNow)
 717                         .Where(d => d.BeginDate <= DateTime.UtcNow)
 122718                         .FirstOrDefault(x=>x.Seller.Id == cluster.Warehouse.Contragent.Id) ??
 61719                         throw new ArgumentException("Не найдено активных контрактов");
 720
 61721            decimal prepaymentPercent = supply.PrepaimentPercent;
 722
 61723            Movement movement = new Movement()
 61724            {
 61725                DtCreated = DateTime.UtcNow,
 61726                MovementType = movementType,
 61727                RecState = await GetRecordState((long)RecordState.Active),
 61728                Customer = customer,
 61729                Supplier = supply.Seller,
 61730                Receiver = department,
 61731                SupplierTransferDate = null,
 61732                MovementStatus = mStatus,
 61733                PrepaimentPercent = prepaymentPercent,
 61734                Sender = cluster.Warehouse,
 61735                DocumentNumber = ""
 61736            };
 61737            if(parentId != 0)
 0738            {
 0739                Movement parent = await _movementService.GetMovement(parentId);
 0740                if (parent == null && mKind != (long)MovementKind.Order)
 0741                {
 0742                    throw new ArgumentException($"Родительский документ #{movementType.Name} не может быть найден");
 743                }
 0744                movement.ParentId = parentId;
 0745                movement.Parent = parent;
 0746            }
 61747            await _movementService.CreateMovement(movement, _env.EnvironmentName);
 61748            await SaveJournal(movement, movement.MovementStatus);
 61749            return movement;
 61750        }
 751
 752
 753        /// <summary>
 754        /// Создание или возврат анонимной заявки
 755        /// </summary>
 756        /// <param name="userId">Идентификатор анонимного пользователя</param>
 757        /// <param name="receiverId">Идентификатор магазина</param>
 758        /// <returns></returns>
 759        public async Task<MovementDTO> CreateAnnonymMovement(Guid userId, long receiverId)
 12760        {
 12761            await _createAnonymMovementSlim.WaitAsync();
 762            AnonymousMovement existsMovement;
 763            try
 12764            {
 12765                existsMovement = await _anonymousMovementService.GetAnonymMovement(userId, receiverId);
 12766                var mapper = ToDtoMapper();
 12767                if (existsMovement != null)
 0768                    return mapper.Map<Movement, MovementDTO>(existsMovement);
 769
 12770                var receiver = await _departmentService.GetDepartment(receiverId) ?? throw
 12771                    new ArgumentException($"Не найдено подразделение #{receiverId}");
 772
 12773                var sender = receiver.Cluster.Warehouse;
 12774                var contracts = await _suplContractService.GetSupplyContracts(receiver.Contragent.Id, true);
 12775                if (contracts.Count == 0)
 0776                    throw new ArgumentException($"Не найдено активных контрактов");
 777
 12778                var supply = contracts
 779                    .Where(d => d.EndDate >= DateTime.UtcNow)
 780                    .Where(d => d.BeginDate <= DateTime.UtcNow)
 24781                    .FirstOrDefault(x => x.Seller.Id == receiver.Cluster.Warehouse.Contragent.Id) ??
 12782                             throw new ArgumentException($"Не найдено активных контрактов");
 783
 12784                var annonym = new AnonymousMovement
 12785                {
 12786                    AnonymousKey = userId,
 12787                    Items = new List<MovementItem>(),
 12788                    Receiver = receiver,
 12789                    Supplier = sender.Contragent,
 12790                    Sender = sender,
 12791                    Customer = await _contragentService.GetContragent(annonymContragentId),
 12792                    PrepaimentPercent = supply.PrepaimentPercent
 12793                };
 12794                await _anonymousMovementService.CreateAnonymMovement(annonym);
 12795                return mapper.Map<Movement, MovementDTO>(annonym);
 796            }
 0797            catch (Exception e)
 0798            {
 0799                _logger.LogError($"Ошибка поиска анонимной заявки для userId {userId} и магазина {receiverId}");
 0800                _logger.LogError(e.Message + " " + e.StackTrace);
 0801                throw;
 802            }
 803            finally
 12804            {
 12805                _createAnonymMovementSlim.Release();
 12806            }
 12807        }
 808
 809        /// <summary>
 810        /// Удаление документа
 811        /// </summary>
 812        /// <param name="id">Идентификатор документа</param>
 813        /// <returns>Task</returns>
 814        public async Task DeleteMovement(long id)
 815        {
 816            Movement movement = await _movementService.GetMovement(id) ??
 817                                throw new KeyNotFoundException("Документ не найден");
 818            var draftStatus = await GetDraft(movement.MovementType);
 819            if (movement.MovementStatus.Id != draftStatus.Id)
 820                throw new ArgumentException($"Документ невозможно удалить из этого статуса");
 821
 822            if (!_authUserService.IsUserPlatform() && !(movement.MovementType.Id switch
 823            {
 824                (long)MovementKind.Order => movement.Customer.Id == _authUserService.ContragentId,
 825                (long)MovementKind.Shipment => movement.Supplier.Id == _authUserService.ContragentId,
 826                _ => false
 827            }))
 828            {
 829                throw new ForbidException();
 830            }
 831
 832            var listJournals = await _movementStatusJournalService.GetMovementStatusJournal(movement.Id);
 833            if (listJournals.Any(d => d.RecState.Id == (long) RecordState.Active))
 834            {
 835                listJournals.Where(d => d.RecState.Id == (long) RecordState.Active)
 836                    .Select(async d => await _movementStatusJournalService.SetRecordInactive(d));
 837            }
 838            await _movementService.DeleteMovement(movement);
 839        }
 840
 841        /// <summary>
 842        /// Создание черновика отгрузки
 843        /// </summary>
 844        /// <param name="parent">Объект родительской заявки</param>
 845        /// <returns></returns>
 846        /// <exception cref="ArgumentException"></exception>
 847        public async Task<Movement> CreateShipmentDraft(Movement parent)
 848        {
 849            try
 850            {
 851                var movementType = await GetMovementType((long) MovementKind.Shipment) ??
 852                                   throw new ArgumentException($"Не найден тип документа Отгрузка");
 853                var status = await GetDraft(movementType) ??
 854                             throw new ArgumentException($"Не найден статус черновик для типа {movementType.Name}");
 855                var state = await _dirService.GetRecordState((int) RecordState.Active);
 856                var existsShipments = await _movementService.GetMovementsByParent(parent.Id, movementType, 0);
 857                if (existsShipments.Count > 0 && existsShipments.Any(d => !d.IsDeleted))
 858                    throw new ArgumentException($"Найдена существующая отгрузка для заявки {parent.Id}");
 859                Movement movement = parent.CreateBaseOn(movementType, status, state);
 860                movement.DocumentNumber = "";
 861                await _movementService.CreateMovement(movement, _env.EnvironmentName);
 862                await SaveJournal(movement, status);
 863                return movement;
 864            }
 865            catch (Exception e)
 866            {
 867                _logger.LogError($"Ошибка создания черновика отгрузки для заявки {parent.DocumentNumber}");
 868                _logger.LogError(e.Message, e);
 869                throw;
 870            }
 871        }
 872
 873        /// <summary>
 874        /// Создание ссылки на документ
 875        /// </summary>
 876        /// <param name="movement">объект документа</param>
 877        /// <returns></returns>
 878        private string CreateLink(Movement movement)
 1879        {
 1880            string type = CreatePostfixLink(movement.MovementType);
 1881            return $"<a class='notification-link' href='{_commonSettings.BaseFrontUrl}/{type}/{movement.Id}'>{movement.D
 1882        }
 883
 884        /// <summary>
 885        /// Создание окончания ссылки
 886        /// </summary>
 887        /// <param name="type">Тип документа</param>
 888        /// <returns></returns>
 1889        private string CreatePostfixLink(MovementType type) =>  type.Id switch
 1890        {
 1891            (long) MovementKind.Order => $"orders",
 1892            (long) MovementKind.Shipment => $"shipments",
 1893            _ => default
 1894        };
 895
 896
 897        /// <summary>
 898        /// Создание документа на основании
 899        /// </summary>
 900        /// <param name="id">идентификатор документа</param>
 901        /// <param name="nextKind">Идентификатор Типа документа для создания - либо этот параметр либо объект типа докум
 902        /// <param name="nextType">Объект документа для создания - либо этот параметр либо идентификатор из перечисления
 903        /// <returns></returns>
 904        public async Task<MovementDTO> CreateMovementOnBase(long id, MovementKind nextKind, MovementType nextType = null
 1905        {
 1906            Movement parent = await _movementService.GetMovement(id) ??
 1907                              throw new KeyNotFoundException($"Документ #{id} не найден");
 908
 1909            if (_authUserService.ContragentKindId != (long) ContragentKind.Platform && parent.MovementType.Id switch
 1910            {
 1911                (long)MovementKind.Order => parent.Supplier.Id != _authUserService.ContragentId,
 1912                (long)MovementKind.Shipment => parent.Customer.Id != _authUserService.ContragentId,
 1913                _ => true
 1914            })
 0915            {
 0916                throw new ForbidException();
 917            }
 918
 1919            var movementType = nextType ?? await _dirService.GetMovementType((int) nextKind);
 1920            var status = await GetDraft(movementType) ??
 1921                         throw new ArgumentException($"Не найден статус черновик для типа {movementType?.Name}");
 1922            var state = await _dirService.GetRecordState((int)RecordState.Active);
 923
 1924            Movement movement = parent.CreateBaseOn(movementType, status, state);
 1925            if (movement.MovementType.Id == (long) MovementKind.Order)
 1926            {
 1927                movement.Parent = null;
 1928                movement.ParentId = null;
 1929                var contracts = await _suplContractService.GetSupplyContracts(movement.Customer.Id, true);
 1930                if (contracts.Count == 0)
 0931                    throw new ArgumentException("Не найдено активных контрактов");
 932
 1933                var receiver = await _departmentService.GetDepartment(movement.Receiver.Id) ??
 1934                                 throw new ArgumentException($"Подразделение #{movement.Receiver.Id} не найдено");
 935
 1936                var supply = contracts
 937                                 .Where(d => d.EndDate >= DateTime.UtcNow)
 938                                 .Where(d => d.BeginDate <= DateTime.UtcNow)
 2939                                 .FirstOrDefault(x=>x.Seller.Id == receiver.Cluster.Warehouse.Contragent.Id) ??
 1940                             throw new ArgumentException("Не найдено активных контрактов");
 941
 1942                movement.PrepaimentPercent = supply.PrepaimentPercent;
 1943                movement.UpdatePriceInDraft();
 1944                movement.ReCalculate();
 1945            }
 946
 1947            movement.DocumentNumber = "";
 1948            await _movementService.CreateMovement(movement, _env.EnvironmentName);
 1949            await SaveJournal(movement, status);
 1950            await _notificationWorker.CreateNotification(movement, "Создан новый документ", $"Создан новый документ {Cre
 1951            var allActions = await _movementRouteActionsService.GetActions();
 1952            movement.FillActions(allActions, _authUserService.ContragentKindId, _authUserService.Roles);
 1953            var result = ToDtoMapper().Map<Movement, MovementDTO>(movement);
 1954            return await Task.FromResult(result);
 1955        }
 956
 957        /// <summary>
 958        /// Клонирование документа
 959        /// </summary>
 960        /// <param name="id">Идентификтор документа донора</param>
 961        /// <param name="departmentId">Идентификтор магазина</param>
 962        /// <returns></returns>
 963        public async Task<MovementDTO> CloneMovement(long id, long departmentId)
 964        {
 965            Movement parent = await _movementService.GetMovementForItems(id) ??
 966                              throw new KeyNotFoundException("Документ не найден");
 967            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 968            if(!parent.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 969                _authUserService.ContragentId,
 970                _authUserService.IsOwner(),
 971                userDeps))
 972                throw new ForbidException();
 973
 974            if (parent.Customer.Id != _authUserService.ContragentId)
 975                throw new ForbidException();
 976            if (parent.MovementType.Id != (long) MovementKind.Order)
 977                throw new ArgumentException("Документ этого типа невозможно клонировать");
 978            var status = await GetDraft(parent.MovementType) ??
 979                         throw new ArgumentException($"Не найден статус черновик для типа {parent.MovementType?.Name}");
 980            if (parent.MovementStatus.Id == status.Id)
 981                throw new ArgumentException("Документ в статусе черновик невозможно клонировать");
 982
 983            var department = await _departmentService.GetDepartment(departmentId) ??
 984                             throw new ArgumentException($"Подразделение #{departmentId} не найдено");
 985
 986            var cluster = department.Cluster;
 987
 988            var currentDraft = await _movementService.GetDraft(_authUserService.ContragentId, departmentId, cluster.Ware
 989            Movement movement;
 2990            SupplyContract contract = parent.Customer.ContractsAsBuyer.FirstOrDefault(d => !d.IsDeleted
 2991                                                                                           && d.Seller.Id ==
 2992                                                                                           parent.Supplier.Id
 2993                                                                                           && d.BeginDate <=
 2994                                                                                           DateTime.UtcNow
 2995                                                                                           && d.EndDate >=
 2996                                                                                           DateTime.UtcNow)
 997                                      ?? throw new ArgumentException(
 998                                          $"Документ невозможно клонировать. Не найден контракт для покупателя {parent.C
 999            if (currentDraft != null)
 1000            {
 1001                movement = currentDraft;
 1002                if (parent.Items != null && parent.Items.Count > 0)
 1003                {
 1004                    var activeRecord = await GetRecordState((long) RecordState.Active);
 1005                    foreach (var item in parent.Items)
 1006                    {
 1007                        movement.AddItem(item, activeRecord);
 1008                    }
 1009                }
 1010                movement.UpdatePriceInDraft();
 1011                movement.ReCalculate();
 1012                await _movementService.UpdateMovement(movement);
 1013            }
 1014            else
 1015            {
 1016                var state = await _dirService.GetRecordState((int)RecordState.Active);
 1017                var receiver = await _departmentService.GetDepartment(departmentId) ??
 1018                               throw new ArgumentException($"Подразделение {departmentId} не найдено");
 1019                if (receiver.Contragent.Id != _authUserService.ContragentId)
 1020                    throw new ForbidException();
 1021                movement = parent.CloneThis(status, state);
 1022                movement.PrepaimentSum = 0;
 1023                movement.Receiver = receiver;
 1024                movement.DocumentNumber = "";
 1025                movement.UpdatePriceInDraft();
 1026                movement.ReCalculate();
 1027                await _movementService.CreateMovement(movement, _env.EnvironmentName);
 1028            }
 1029            await SaveJournal(movement, movement.MovementStatus);
 1030
 1031            var allActions = await _movementRouteActionsService.GetActions();
 1032            movement.FillActions(allActions, _authUserService.ContragentKindId, _authUserService.Roles);
 1033            var result = ToDtoMapper().Map<Movement, MovementDTO>(movement);
 1034            return await Task.FromResult(result);
 1035        }
 1036
 1037        /// <summary>
 1038        /// Редактирование отгрузки через обмен с 1С
 1039        /// </summary>
 1040        /// <param name="movementGuid">guid отгрузки</param>
 1041        /// <param name="tnx">токен в системе</param>
 1042        /// <param name="items">массив объектов для редактирования</param>
 1043        /// <returns></returns>
 1044        /// <exception cref="ForbidException"></exception>
 1045        /// <exception cref="ArgumentException"></exception>
 1046        public async Task ExchangeCorretionShipment(Guid movementGuid, Guid tnx,
 1047            ExchangeShipmentCorrectionRequestDto items)
 1048        {
 1049            var token = await _exchangeTokenService.GetToken(tnx) ?? throw new ArgumentException($"Токен не найден");
 1050            Movement movement = await _movementService.GetMovementForItems(movementGuid) ??
 1051                                throw new KeyNotFoundException($"Документ #{movementGuid} не найден");
 1052            if (!(token.Department.Contragent.ContragentsKind.Id switch
 1053            {
 1054                (long) ContragentKind.Wholesaler => movement.Supplier.Id == token.DepartmentId,
 1055                _ => false
 1056            }))
 1057            {
 1058                throw new ForbidException();
 1059            }
 1060
 1061            if (movement.MovementStatus.Id != (long) MovementsStatus.ShipmentDraft
 1062                && movement.MovementStatus.Id != (long) MovementsStatus.Correction)
 1063            {
 1064                throw new ArgumentException("Документ не может быть измененен");
 1065            }
 1066            var activeRecord = await GetRecordState((long) RecordState.Active);
 1067            foreach (var item in items.items)
 1068            {
 1069                var good = !string.IsNullOrEmpty(item.VendorCode)
 1070                    ? (await _goodService.FindGoodsByVendorsCodes(new List<string> {item.VendorCode.Trim().ToLower()}, t
 1071                    : await _goodService.GetGood(item.BarCode);
 1072                if (good == null)
 1073                    return;
 21074                if (movement.Items.All(d => d.Good.Id != good.Id)
 21075                    || (item.Quantity > movement.Items.FirstOrDefault(d => d.Good.Id == good.Id)?.Quantity)
 1076                         && movement.MovementStatus.Id == (long)MovementsStatus.Correction)
 1077                    return;
 1078                if (item.Quantity == 0)
 1079                {
 01080                    await _movementService.DeleteMovementItem(movement.Items.FirstOrDefault(d => d.GoodId == good.Id));
 1081                    return;
 1082                }
 1083                movement.AddItem(new MovementItem {Good = good, Quantity = item.Quantity}, activeRecord);
 1084            }
 1085            movement.ReCalculate();
 1086            await _movementService.UpdateMovement(movement);
 1087        }
 1088
 1089        /// <summary>
 1090        /// Удаляет дочерние документы
 1091        /// </summary>
 1092        /// <param name="movementId">Идентификатор документа родителя</param>
 1093        /// <param name="kind">Тип удаляемых документов</param>
 1094        /// <param name="status">Статус удаляемых документов</param>
 1095        /// <returns></returns>
 1096        public async Task RemoveChildrenDraft(long movementId, MovementKind kind, MovementsStatus status)
 31097        {
 31098            var shipmentType =
 31099                await GetMovementType((long) kind);
 31100            var children = await _movementService.GetMovementsByParent(movementId,
 31101                shipmentType, (long)status);
 1102            // удалим все дочерние черновики
 31103            if (children != null && children.Count > 0)
 21104            {
 101105                foreach (Movement movement in children)
 21106                {
 21107                    await _movementService.DeleteMovement(movement);
 21108                }
 21109            }
 31110        }
 1111
 1112        /// <summary>
 1113        /// Изменение количества товара в документе
 1114        /// </summary>
 1115        /// <param name="id">Идентификатор документа</param>
 1116        /// <param name="itemId">Идентификатор позиции</param>
 1117        /// <param name="quantity">Количество товара</param>
 1118        /// <param name="comment">Причрина изменения количества - для отгрузки</param>
 1119        /// <returns></returns>
 1120        public async Task ChangeQuantityInMovement(long id, long itemId, int quantity, string comment = default)
 1121        {
 1122            try
 1123            {
 1124                await _changeQuantityMovementSlim.WaitAsync();
 1125                Movement movement = _authUserService.IsAnonym() ? await _anonymousMovementService.GetAnonymMovement(id)
 1126                : await _movementService.GetMovementForItems(id)
 1127                             ?? throw new KeyNotFoundException($"Документ #{id} не найден");
 1128
 1129                var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 1130                if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 1131                    _authUserService.ContragentId,
 1132                    _authUserService.IsOwner(),
 1133                    userDeps))
 1134                    throw new ForbidException();
 1135
 1136                var status = await GetDraft(movement.MovementType);
 1137                if (!movement.CheckEditable(status))
 1138                {
 1139                    throw new ArgumentException($"Документ {movement.Id} невозможно изменить");
 1140                }
 1141
 41142                MovementItem item = movement.Items.FirstOrDefault(d => d.Id == itemId) ??
 1143                                    throw new KeyNotFoundException($"Позиция {itemId} в документе {id} не найдена");
 1144
 01145                var settings = item.Good.DepartmentGoodSettings.FirstOrDefault(d => !d.IsDeleted && d.Department.Id == m
 1146                               ?? new DepartmentGoodSetting {MinQuantity = 1, PickingQuantum = 1};
 1147                CheckSettings(quantity, settings);
 1148                if (movement.MovementStatus.Id == (long) MovementsStatus.Correction)
 1149                {
 1150                    var note = new MovementNote
 1151                    {
 1152                        Body = $"Товар \"{item.Good.Name}\" - изменено количество с {(long)item.Quantity} на {quantity}"
 1153                    };
 1154                    movement.Notes.Add(note);
 1155                }
 1156                movement.AddItem(new MovementItem{Good = item.Good, Quantity = quantity}, await GetRecordState((long)Rec
 1157                if (movement.MovementStatus.Id == (long)MovementsStatus.OrderDraft)
 1158                    movement.UpdatePriceInDraft();
 1159                movement.ReCalculate();
 1160                if (_authUserService.IsAnonym())
 1161                    await _anonymousMovementService.UpdateAnonymMovement((AnonymousMovement) movement);
 1162                else
 1163                    await _movementService.UpdateMovement(movement);
 1164            }
 1165            catch (Exception e)
 1166            {
 1167                _logger.LogError($"Ошибка изменения количества позиции {itemId} в заявке {id}");
 1168                _logger.LogError($"{e.Message} {e.StackTrace}");
 1169                throw;
 1170            }
 1171            finally
 1172            {
 1173                _changeQuantityMovementSlim.Release();
 1174            }
 1175
 1176        }
 1177
 1178        /// <summary>
 1179        /// Добавляет позицию в документ
 1180        /// </summary>
 1181        /// <param name="id">id документа для обработки</param>
 1182        /// <param name="mItemDto">Позиция для добавления</param>
 1183        /// <returns></returns>
 1184        /// <exception cref="ArgumentException"></exception>
 1185        public async Task<MovementItemResponseDTO> AddItemToMovement(long id, MovementItemRequestDTO  mItemDto)
 1186        {
 1187            try
 1188            {
 1189                await _addItemMovementSlim.WaitAsync();
 1190                Movement movement = _authUserService.IsAnonym() ? await _anonymousMovementService.GetAnonymMovementForIt
 1191                : movement = await _movementService.GetMovementForItems(id) ?? throw new KeyNotFoundException($"Документ
 1192
 1193                var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 1194
 1195                if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 1196                    _authUserService.ContragentId,
 1197                    _authUserService.IsOwner(),
 1198                    userDeps))
 1199                    throw new ForbidException();
 1200
 1201                var status = await GetDraft(movement.MovementType);
 1202                if (!movement.CheckEditable(status))
 1203                {
 1204                    throw new ArgumentException($"Документ {movement.Id} невозможно изменить");
 1205                }
 1206                var active = await GetRecordState((long) RecordState.Active);
 1207                //Обновим дату создания заявки при первом добавлении товара в нее
 1208                if (movement.MovementType?.Id == (long) MovementKind.Order
 1209                    && movement.Items?.Count == 0)
 1210                {
 1211                    movement.CreationDateTime = DateTime.UtcNow;
 1212                    //Изменим статус документа при первом добавлении позиции в документ
 1213                    movement.RecState = movement.RecState?.Id == (long)RecordState.Empty ?  active : movement.RecState;
 1214                }
 1215                Good good = await _goodService.GetGood(mItemDto.GoodId) ??
 1216                            throw new ArgumentException($"Товар {mItemDto.GoodId} не найден");
 1217
 1218                if (good.RecState?.Id != (long)RecordState.Active)
 1219                    throw new ArgumentException($"Товар {good.Id} не может быть добавлен в документ");
 1220
 01221                var settings = good.DepartmentGoodSettings.FirstOrDefault(d => !d.IsDeleted && d.Department.Id == moveme
 1222                               ?? new DepartmentGoodSetting {MinQuantity = 1, PickingQuantum = 1};
 1223                CheckSettings(mItemDto.Quantity, settings);
 1224                movement.AddItem(new MovementItem{Good = good, Quantity = mItemDto.Quantity}, active);
 1225
 1226                if (movement.MovementStatus.Id == (long)MovementsStatus.OrderDraft)
 1227                    movement.UpdatePriceInDraft();
 1228
 1229                movement.ReCalculate();
 1230                if (_authUserService.IsAnonym())
 1231                {
 1232                    await _anonymousMovementService.UpdateAnonymMovement((AnonymousMovement)movement);
 1233                }
 1234                else
 1235                {
 1236                    await _movementService.UpdateMovement(movement);
 1237                }
 1361238                MovementItem movementItem = movement.Items.FirstOrDefault(d => d.Good.Id == mItemDto.GoodId);
 1239                var itemDto = ToItemDTOMapper(movement.Sender.Id, discountColors).Map<MovementItemResponseDTO>(movementI
 1240                return itemDto;
 1241            }
 1242            catch (Exception e)
 1243            {
 1244                _logger.LogError($"Ошибка добавления товара {mItemDto.GoodId} в документ {id}");
 1245                _logger.LogError($"{e.Message} {e.StackTrace}");
 1246                throw;
 1247            }
 1248            finally
 1249            {
 1250                _addItemMovementSlim.Release();
 1251            }
 1252        }
 1253
 1254        /// <summary>
 1255        /// Удаление позиции из документа
 1256        /// </summary>
 1257        /// <param name="id">Идентификатор документа</param>
 1258        /// <param name="itemId">Идентификатор позиции</param>
 1259        /// <returns></returns>
 1260        public async Task RemoveItemFromMovement(long id, long itemId)
 1261        {
 1262            Movement movement = _authUserService.IsAnonym()
 1263                ? await _anonymousMovementService.GetAnonymMovementForItems(id)
 1264                : await _movementService.GetMovementForItems(id)
 1265                ?? throw new ArgumentException($"Документ {id} не найден");
 1266
 1267            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 1268            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 1269                _authUserService.ContragentId,
 1270                _authUserService.IsOwner(),
 1271                userDeps))
 1272                throw new ForbidException();
 1273
 1274            var status = await GetDraft(movement.MovementType);
 1275            if (!movement.CheckEditable(status))
 1276            {
 1277                throw new ArgumentException($"Документ {movement.Id} невозможно изменить");
 1278            }
 1279
 41280            var item = movement.Items.FirstOrDefault(d => d.Id == itemId) ??
 1281                       throw new ArgumentException($"Позиция #{itemId} не найдена");
 1282
 1283
 1284            await _movementService.DeleteMovementItem(item);
 1285            movement.ReCalculate();
 1286
 1287            if (movement.MovementStatus.Id == (long) MovementsStatus.Correction)
 1288            {
 1289                var note = new MovementNote
 1290                {
 1291                    Body = $"Товар \"{item.Good.Name}\" удален"
 1292                };
 1293                movement.Notes.Add(note);
 1294            }
 1295
 1296            if (_authUserService.IsAnonym())
 1297                await _anonymousMovementService.UpdateAnonymMovement((AnonymousMovement) movement);
 1298            else
 1299                await _movementService.UpdateMovement(movement);
 1300        }
 1301
 1302        /// <summary>
 1303        /// Печать универсального передаточного документа (УПД)
 1304        /// </summary>
 1305        /// <param name="id">Идентификатор документа</param>
 1306        /// <returns></returns>
 1307        public async Task<MemoryStream> PrintUpd(long id)
 01308        {
 01309            var type = await GetMovementType((long) MovementKind.Shipment);
 01310            Movement movement = await _movementService.GetMovementReadonly(id, type) ??
 01311            throw new KeyNotFoundException($"Документ #{id} не найден");
 01312            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01313            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01314                _authUserService.ContragentId,
 01315                _authUserService.IsOwner(),
 01316                userDeps))
 01317                throw new ForbidException();
 01318            if (movement.MovementType.Id != (int)MovementKind.Shipment)
 01319                throw new ArgumentException("Этот тип документа не доступен для печати");
 01320            if (!(movement.MovementStatus.Id switch
 01321            {
 01322                (long) MovementsStatus.ReadyForShipment => true,
 01323                (long) MovementsStatus.Shipped => true,
 01324                (long) MovementsStatus.Received => true,
 01325                (long) MovementsStatus.CustomerReject => true,
 01326                (long) MovementsStatus.ClaimAccepted => true,
 01327                (long) MovementsStatus.ClaimDeclined => true,
 01328                (long) MovementsStatus.ClaimInProgress => true,
 01329                _ => false
 01330            }))
 01331            {
 01332                throw new ArgumentException($"Статус документа не подходит для печати");
 1333            }
 1334
 01335            var options = new ConvertOptions
 01336            {
 01337                PageOrientation = Wkhtmltopdf.NetCore.Options.Orientation.Landscape,
 01338                PageMargins = { Left = 10, Bottom = 15, Right = 10, Top = 10 }
 01339            };
 01340            _generatePdf.SetConvertOptions(options);
 1341
 01342            var receiver = await _departmentService.GetDepartment(movement.Receiver.Id) ??
 01343                             throw new ArgumentException($"Подразделение #{movement.Receiver.Id} не найдено");
 1344
 01345            var contracts = await _suplContractService.GetSupplyContracts(movement.Customer.Id, false);
 01346            if (contracts.Count == 0)
 01347                throw new ArgumentException("Не найдены контракты");
 1348
 01349            var supply = contracts
 01350                             .Where(d => d.EndDate >= movement.CreationDateTime)
 01351                             .Where(d => d.BeginDate <= movement.CreationDateTime)
 01352                             .FirstOrDefault(x=>x.Seller.Id == receiver.Cluster.Warehouse.Contragent.Id) ??
 01353                         throw new ArgumentException("Не найден контракт действовавший на момент создания документа");
 1354
 01355            var pdf = await _generatePdf.GetByteArray("PrintForms/Upd/upd", new MovementUpd{Movement = movement, SupplyC
 01356            var pdfStream = new MemoryStream();
 01357            pdfStream.Write(pdf, 0, pdf.Length);
 01358            pdfStream.Position = 0;
 01359            return pdfStream;
 01360        }
 1361
 1362        /// <summary>
 1363        /// Возвращение вложения из документа
 1364        /// </summary>
 1365        /// <param name="id">Идентификатор документа</param>
 1366        /// <param name="kind">тип документа</param>
 1367        /// <param name="fileType">Тип файла</param>
 1368        /// <returns></returns>
 1369        /// <exception cref="ForbidException">Нет прав доступа к вложению</exception>
 1370        public async Task<MemoryStream> GetFile(long id, MovementKind kind, string fileType)
 01371        {
 01372            MovementType type = await GetMovementType((long) kind);
 01373            Movement order = await _movementService.GetMovementReadonly(id, type) ??
 01374                             throw new KeyNotFoundException($"Документ #{id} не найден");
 1375
 01376            if (_authUserService.IsUserRetailer() && order.Customer.Id != _authUserService.ContragentId)
 01377                throw new ForbidException();
 01378            string delim = ";";
 01379            var rows = new List<string[]>();
 01380            rows.Add(new string[] { "ШК", "Название", "Цена", "Кол-во", "СуммаСНдс", "СуммаБезНдс" });
 1381            try
 01382            {
 01383                order.Items.ForEach(item =>
 1384                {
 1385                    var line = new List<string>();
 1386                    line.Add(item.Good.GoodBarcodes.FirstOrDefault(d => d.IsPrimary) != null
 1387                        ? item.Good.GoodBarcodes.FirstOrDefault(d => d.IsPrimary).BarCode.Code
 1388                        : item.Good.DefaultBarCode.Code);
 1389                    line.Add(item.Good.Name);
 1390                    line.Add(item.Price.ToString(CultureInfo.CurrentCulture));
 1391                    line.Add(item.Quantity.ToString(CultureInfo.CurrentCulture));
 1392                    line.Add((item.Price * item.Quantity).ToString());
 1393                    line.Add((Math.Ceiling(item.Price / (item.Good.VatsKind.Value == 1 ? 1 : (decimal)item.Good.VatsKind
 1394                    rows.Add(line.ToArray());
 1395                });
 01396            }
 01397            catch (Exception e)
 01398            {
 01399                _logger.LogError(e.Message, e.StackTrace);
 01400                throw;
 1401            }
 1402
 01403            var resStream = new MemoryStream();
 01404            if (fileType == "csv")
 01405            {
 01406                using (var writer = new StreamWriter(resStream, encoding: Encoding.UTF8, leaveOpen: true))
 01407                {
 01408                    rows.ForEach(x => writer.WriteLine(string.Join(delim, x)));
 01409                }
 01410            }
 1411            else
 01412            {
 01413                using (var book = CsvUtil.ToExcel(rows))
 01414                {
 01415                    book.SaveAs(resStream);
 01416                }
 01417            }
 01418            return resStream;
 01419        }
 1420
 1421        /// <summary>
 1422        /// Печать заказа
 1423        /// </summary>
 1424        /// <param name="id">Идентификатор документа</param>
 1425        /// <param name="kind">Тип документа</param>
 1426        /// <returns></returns>
 1427        public async Task<MemoryStream> PrintOrder(long id, MovementKind kind)
 01428        {
 01429            var movementType = await GetMovementType((long) kind) ??
 01430                               throw new ArgumentException($"Не найден тип документа {kind}");
 01431            var movement = await _movementService.GetMovementReadonly(id, movementType)??
 01432                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1433
 01434            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01435            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01436                _authUserService.ContragentId,
 01437                _authUserService.IsOwner(),
 01438                userDeps))
 01439                throw new ForbidException();
 1440
 01441            var options = new ConvertOptions
 01442            {
 01443                PageOrientation = Wkhtmltopdf.NetCore.Options.Orientation.Landscape,
 01444                PageMargins = { Left = 10, Bottom = 10, Right = 10, Top = 10 }
 01445            };
 01446            _generatePdf.SetConvertOptions(options);
 1447
 01448            var pdf = await _generatePdf.GetByteArray("PrintForms/Order/order", movement);
 01449            var pdfStream = new MemoryStream();
 01450            pdfStream.Write(pdf, 0, pdf.Length);
 01451            pdfStream.Position = 0;
 01452            return pdfStream;
 01453        }
 1454
 1455        /// <summary>
 1456        /// Создает архив с документами
 1457        /// </summary>
 1458        /// <param name="id">Идентификатор отгрузки</param>
 1459        /// <param name="kind">Тип</param>
 1460        /// <returns></returns>
 1461        public async Task<MemoryStream> CreateZip(long id)
 01462        {
 01463            var kind = MovementKind.Shipment;
 01464            var movementType = await GetMovementType((long) kind) ??
 01465                               throw new ArgumentException($"Не найден тип документа {kind}");
 01466            var movement = await _movementService.GetMovementReadonly(id, movementType)??
 01467                throw new KeyNotFoundException($"Документ #{id} не найден");
 01468            var torgStream = await PrintTorg(id, MovementKind.Shipment);
 01469            var ttnStream = await PrintTtn(id, MovementKind.Shipment);
 01470            var sfStream = await PrintSf(id, MovementKind.Shipment);
 01471            var tnStream = await PrintTn(id, MovementKind.Shipment);
 01472            MemoryStream[] streams = new MemoryStream[] {torgStream, ttnStream, sfStream, tnStream};
 01473            string[] names = new string[] {"torg.pdf", "ttn.pdf", "sf.pdf", "tn.pdf"};
 1474            byte[] compressedBytes;
 1475
 01476            var stream = new MemoryStream();
 01477            using (var arch = new ZipArchive(stream, ZipArchiveMode.Create, true))
 01478            {
 01479                for (int i = 0; i < names.Length; i++)
 01480                {
 01481                    var file = arch.CreateEntry(names[i]);
 01482                    using (var entryStream = file.Open())
 01483                        streams[i].WriteTo(entryStream);
 01484                }
 01485            }
 01486            stream.Position = 0;
 01487            return stream;
 01488        }
 1489
 1490        /// <summary>
 1491        /// Печать счет фактуры
 1492        /// </summary>
 1493        /// <param name="id">Идентификатор документа</param>
 1494        /// <param name="kind">Тип документа</param>
 1495        /// <returns></returns>
 1496        public async Task<MemoryStream> PrintSf(long id, MovementKind kind, Movement mov = null)
 01497        {
 01498            var movementType = await GetMovementType((long) kind) ??
 01499                               throw new ArgumentException($"Не найден тип документа {kind}");
 01500            var movement = mov ?? await _movementService.GetMovementReadonly(id, movementType)??
 01501                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1502
 01503            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01504            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01505                _authUserService.ContragentId,
 01506                _authUserService.IsOwner(),
 01507                userDeps))
 01508                throw new ForbidException();
 1509
 01510            var options = new ConvertOptions
 01511            {
 01512                PageOrientation = Wkhtmltopdf.NetCore.Options.Orientation.Landscape,
 01513                PageMargins = { Left = 10, Bottom = 10, Right = 10, Top = 10 }
 01514            };
 01515            _generatePdf.SetConvertOptions(options);
 1516
 01517            var pdf = await _generatePdf.GetByteArray("PrintForms/SF/sf", movement);
 01518            var pdfStream = new MemoryStream();
 01519            pdfStream.Write(pdf, 0, pdf.Length);
 01520            pdfStream.Position = 0;
 01521            return pdfStream;
 01522        }
 1523
 1524        /// <summary>
 1525        /// Печать ТТН
 1526        /// </summary>
 1527        /// <param name="id">Идентификатор документа</param>
 1528        /// <param name="kind">Тип документа</param>
 1529        /// <returns></returns>
 1530        public async Task<MemoryStream> PrintTtn(long id, MovementKind kind, Movement mov = null)
 01531        {
 01532            var movementType = await GetMovementType((long) kind) ??
 01533                               throw new ArgumentException($"Не найден тип документа {kind}");
 01534            var movement = mov ?? await _movementService.GetMovementReadonly(id, movementType)??
 01535                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1536
 01537            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01538            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01539                _authUserService.ContragentId,
 01540                _authUserService.IsOwner(),
 01541                userDeps))
 01542                throw new ForbidException();
 1543
 01544            var options = new ConvertOptions
 01545            {
 01546                PageOrientation = Wkhtmltopdf.NetCore.Options.Orientation.Landscape,
 01547                PageMargins = { Left = 10, Bottom = 10, Right = 10, Top = 10 }
 01548            };
 01549            _generatePdf.SetConvertOptions(options);
 1550
 01551            var pdf = await _generatePdf.GetByteArray("PrintForms/TTN/Ttn", movement);
 01552            var pdfStream = new MemoryStream();
 01553            pdfStream.Write(pdf, 0, pdf.Length);
 01554            pdfStream.Position = 0;
 01555            return pdfStream;
 01556        }
 1557
 1558        /// <summary>
 1559        /// Печать торг12
 1560        /// </summary>
 1561        /// <param name="id">Идентификатор документа</param>
 1562        /// <param name="kind">Тип документа</param>
 1563        /// <returns></returns>
 1564        public async Task<MemoryStream> PrintTorg(long id, MovementKind kind, Movement mov = null)
 01565        {
 01566            var movementType = await GetMovementType((long) kind) ??
 01567                               throw new ArgumentException($"Не найден тип документа {kind}");
 01568            var movement = mov ?? await _movementService.GetMovementReadonly(id, movementType)??
 01569                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1570
 01571            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01572            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01573                _authUserService.ContragentId,
 01574                _authUserService.IsOwner(),
 01575                userDeps))
 01576                throw new ForbidException();
 1577
 01578            var options = new ConvertOptions
 01579            {
 01580                PageOrientation = Wkhtmltopdf.NetCore.Options.Orientation.Landscape,
 01581                PageMargins = { Left = 10, Bottom = 10, Right = 10, Top = 10 }
 01582            };
 01583            _generatePdf.SetConvertOptions(options);
 1584
 01585            var pdf = await _generatePdf.GetByteArray("PrintForms/TORG/Torg12", movement);
 01586            var pdfStream = new MemoryStream();
 01587            pdfStream.Write(pdf, 0, pdf.Length);
 01588            pdfStream.Position = 0;
 01589            return pdfStream;
 01590        }
 1591
 1592        /// <summary>
 1593        /// Печать транспортная накладная
 1594        /// </summary>
 1595        /// <param name="id">Идентификатор документа</param>
 1596        /// <param name="kind">Тип документа</param>
 1597        /// <returns></returns>
 1598        public async Task<MemoryStream> PrintTn(long id, MovementKind kind, Movement mov = null)
 01599        {
 01600            var movementType = await GetMovementType((long) kind) ??
 01601                               throw new ArgumentException($"Не найден тип документа {kind}");
 01602            var movement = mov ?? await _movementService.GetMovementReadonly(id, movementType)??
 01603                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1604
 01605            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01606            if(!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01607                _authUserService.ContragentId,
 01608                _authUserService.IsOwner(),
 01609                userDeps))
 01610                throw new ForbidException();
 1611
 01612            var options = new ConvertOptions
 01613            {
 01614                PageOrientation = Wkhtmltopdf.NetCore.Options.Orientation.Portrait,
 01615                PageMargins = { Left = 10, Bottom = 10, Right = 10, Top = 10 }
 01616            };
 01617            _generatePdf.SetConvertOptions(options);
 1618
 01619            var pdf = await _generatePdf.GetByteArray("PrintForms/TN/Tn", movement);
 01620            var pdfStream = new MemoryStream();
 01621            pdfStream.Write(pdf, 0, pdf.Length);
 01622            pdfStream.Position = 0;
 01623            return pdfStream;
 01624        }
 1625
 1626        /// <summary>
 1627        /// Получает историю мувмента
 1628        /// </summary>
 1629        /// <param name="id">Идентификатор документа</param>
 1630        /// <returns></returns>
 1631        public async Task<List<MovementHistoryResponseDTO>> GetMovementHistory(long id)
 01632        {
 01633            var movement = await _movementService.GetMovement(id) ??
 01634                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1635
 01636            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01637            if (!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01638                _authUserService.ContragentId,
 01639                _authUserService.IsOwner(),
 01640                userDeps))
 01641                throw new ForbidException();
 01642            var events = await _eventService.GetEvents(movement.GUID, nameof(Movement));
 01643            var history = new List<MovementHistoryResponseDTO>();
 01644            var jsonSettings = new JsonSerializerSettings
 01645            {
 01646                Culture = new System.Globalization.CultureInfo("ru-RU")  //чтобы нормально было преобразование prepaimen
 01647            };
 01648            foreach (var _event in events)
 01649            {
 01650                var json = JsonConvert.DeserializeObject<MovementHistoryJsonDTO>(_event.ReasonJson, jsonSettings);
 01651                if (json.New == null)
 01652                    continue;
 01653                var movementStatus = await _movementTypeStatusService.GetMovementStatus(json.New.MovementStatusId);
 01654                history.Add(new MovementHistoryResponseDTO
 01655                {
 01656                    Kind = new IdNameDTO(_event.EventsKind.Id, _event.EventsKind.Name),
 01657                    ModifyDateTime = _event.CreationDateTime,
 01658                    MovementStatusId = new IdNameDTO(movementStatus.Id, movementStatus.Name),
 01659                    PrepaimentSum = json.New.PrepaimentSum,
 01660                    User = new IdNameDTO(_event.User.Id, _event.User.FirstName + " " + _event.User.LastName)
 01661                });
 01662            }
 01663            return history;
 01664        }
 1665
 1666        /// <summary>
 1667        /// Определяет тип доставки заявки
 1668        /// </summary>
 1669        /// <param name="id">id заявки</param>
 1670        /// <returns></returns>
 1671        public async Task<ClusterDeliveryTypesResponseDTO> GetOrderDeliveryTypes(long id)
 01672        {
 01673            var movement = await _movementService.GetMovement(id) ??
 01674                           throw new KeyNotFoundException($"Документ #{id} не найден");
 1675
 01676            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 01677            if (!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 01678                _authUserService.ContragentId,
 01679                _authUserService.IsOwner(),
 01680                userDeps))
 01681                throw new ForbidException();
 01682            var result = new ClusterDeliveryTypesResponseDTO()
 01683            {
 01684                 Cluster = new IdNameDTO(movement.Receiver.Cluster.Id, movement.Receiver.Cluster.Name),
 1685                 DeliveryTypes = new List<DeliveryTypeResponseDto>(movement.Receiver.Cluster.ClusterDeliveryTypes.Select
 01686            };
 1687
 01688            return result;
 01689        }
 1690
 1691        /// <summary>
 1692        /// Делает запись в журнал изменения статуса документов
 1693        /// </summary>
 1694        /// <param name="movement">Изменяемый документ</param>
 1695        /// <param name="mStatus">Статус после изменения</param>
 1696        /// <returns></returns>
 1021697        private async Task SaveJournal(Movement movement, MovementStatus mStatus) => await _movementStatusJournalService
 1021698        {
 1021699            Movement = movement,
 1021700            StatusCurrent = mStatus
 1021701        });
 1702
 1703        /// <summary>
 1704        /// Возвращает объект состояния записи
 1705        /// </summary>
 1706        /// <param name="id"></param>
 1707        /// <returns></returns>
 1708        private async Task<RecordsState> GetRecordState(long id) =>
 1331709            (await _dirService.GetRecordState(id));
 1710
 1711        /// <summary>
 1712        /// Возвращает объект типа документа по ид
 1713        /// </summary>
 1714        /// <param name="id"></param>
 1715        /// <returns></returns>
 1716        public async Task<MovementType> GetMovementType(long id) =>
 1641717            await _movementTypeStatusService.GetMovementType(id);
 1718
 1719        /// <summary>
 1720        /// Установка параметров запроса
 1721        /// </summary>
 1722        /// <param name="inParam">Объект запроса</param>
 1723        /// <returns></returns>
 1724        /// <exception cref="ForbidException"></exception>
 1725        private async Task<MovementParam> SetUserParam(MovementParam inParam)
 1726        {
 1727            inParam.StatusId = (_authUserService.ContragentKindId, inParam.Kind) switch
 1728            {
 1729                ((long) ContragentKind.Wholesaler, MovementKind.Order) => throw new ForbidException(),
 1730                ((long) ContragentKind.Retailer, MovementKind.Shipment) => inParam.StatusId == (long)MovementsStatus.Shi
 1731                    ? throw new ForbidException()
 1732                    : inParam.StatusId,
 1733                _ => inParam.StatusId
 1734            };
 1735
 1736            inParam.ExcludedStatuses = (inParam.Kind, _authUserService.ContragentKindId) switch
 1737            {
 1738                (MovementKind.Shipment, (long)ContragentKind.Retailer) => new []{(long)MovementsStatus.ShipmentDraft},
 1739                (MovementKind.Order, (long)ContragentKind.Platform) => new []{(long)MovementsStatus.OrderDraft},
 1740                _ => null
 1741            };
 1742
 1743            switch (_authUserService.ContragentKindId)
 1744            {
 1745                case ((long)ContragentKind.Retailer):
 1746                {
 01747                    if (inParam.ReceiverId != 0 && (await _departmentService.GetDepartmentsByUserAndKind(_authUserServic
 1748                        throw new ForbidException();
 1749                    inParam.CustomerId = _authUserService.ContragentId;
 1750                    inParam.СontragentBuyerFilter = default;
 1751                    break;
 1752                }
 1753                case ((long)ContragentKind.Wholesaler):
 1754                {
 1755                    inParam.SupplierId = _authUserService.ContragentId;
 1756                    break;
 1757                }
 1758                case ((long)ContragentKind.Manufacturer):
 1759                {
 1760                    inParam.SupplierId = _authUserService.ContragentId;
 1761                    break;
 1762                }
 1763            }
 1764
 1765            inParam.ShowAnonym = _authUserService.IsAnonym() ? "anonymousonly" : _authUserService.IsUserPlatform()  ? in
 1766            inParam.Limit = inParam.Limit <= 0 ? 10 : inParam.Limit;
 1767            return inParam;
 1768        }
 1769
 1770        /// <summary>
 1771        /// Маппер для анонимной заявки
 1772        /// </summary>
 1773        /// <returns></returns>
 1774        private IMapper FromAnnonymToDtoMapper()
 01775        {
 01776            var config = new MapperConfiguration(cfg =>
 01777            {
 01778                cfg.CreateMap<AnonymousMovement, MovementDTO>()
 01779                    .ForMember(d => d.ItemsCount,
 01780                        e => e.MapFrom(s => s.Items.Count))
 01781                    .ForMember(d => d.Sum, e =>
 01782                        e.MapFrom(s => s.Items.Sum(g => g.Price * g.Quantity)))
 01783                    .ForMember(d => d.SumWithoutVat, e => e.MapFrom(s => Math.Round(s.Items.Sum(x => x.Price / (x.Good.V
 01784                    .ForMember(d => d.MovementType, e => e.MapFrom(s => (long)MovementKind.Order))
 01785                    .ForMember(d => d.MovementStatus, e => e.MapFrom(s => new EnumDB_DTO { Id = (long)MovementsStatus.Or
 01786                cfg.CreateMap<Department, DepartmentShortWithAddressDTO>()
 01787                    .ForMember(d => d.Address, e => e.MapFrom(s => s.ActualAddress.FullAddress));
 01788                cfg.CreateMap<Contragent, ContragentShortDTO>()
 01789                    .ForMember(d => d.KindId, e => e.MapFrom(s => (int)s.ContragentsKind.Id));
 01790                cfg.CreateMap<MovementItem, MovementItemResponseDTO>();
 01791                cfg.CreateMap<Good, GoodShortDTO>()
 01792                    .ForMember(d => d.Barcode, e => e.AllowNull());
 01793            });
 01794            return config.CreateMapper();
 01795        }
 1796
 1797        /// <summary>
 1798        /// Маппер для документа
 1799        /// </summary>
 1800        /// <param name="anonymMapper"></param>
 1801        /// <returns></returns>
 1802        private IMapper ToDtoMapper(bool anonymMapper = false)
 1271803        {
 1271804            var config = new MapperConfiguration(cfg =>
 2541805            {
 2541806                cfg.CreateMap<Movement, MovementDTO>()
 3811807                    .ForMember(d => d.ItemsCount, e => e.MapFrom(s => s.Items.Count))
 3811808                    .ForMember(d => d.Sum, e => e.MapFrom(s => s.Items.Sum(x => x.Price * x.Quantity)))
 2541809                    //стобы найти цену без НДС, надо цену разделить на 1.${НДС}. например при НДС 13% и цене 113, цена б
 2541810                    .ForMember(d => d.SumWithoutVat,
 3811811                        e => e.MapFrom(s =>
 3811812                            Math.Round(
 3811813                                s.Items.Sum(x =>
 3811814                                    x.Price / (x.Good.VatsKind.Value == 1
 3811815                                        ? 1
 3811816                                        : (decimal)x.Good.VatsKind.Value / 100 + 1) * x.Quantity), 2)))
 3811817                    .ForMember(d => d.MovementType, e => e.MapFrom(s => s.MovementType.Id))
 2541818                    .ForMember(d => d.MovementStatus,
 3811819                        e => e.MapFrom(s => new EnumDB_DTO
 3811820                        { Id = s.MovementStatus.Id, Code = s.MovementStatus.Code, Name = s.MovementStatus.Name }))
 3811821                    .ForMember(d => d.DemoId, e => e.MapFrom(s => anonymMapper ? s.GUID : Guid.Empty))
 3811822                    .ForMember(d => d.IsDemo, e => e.MapFrom(s => anonymMapper))
 3811823                    .ForMember(d => d.InQueueTransferDate, e => e.MapFrom(s =>
 3811824                        s.MovementStatusJournals.Count > 0 && s.MovementStatusJournals
 3811825                            .Any(d => d.StatusCurrent.Id == (long)MovementsStatus.InQueue)
 3811826                            ? s.MovementStatusJournals
 3811827                                .FirstOrDefault(d => d.StatusCurrent.Id == (long)MovementsStatus.InQueue)
 3811828                                .CreationDateTime
 3811829                            : DateTime.MinValue))
 3811830                    .ForMember(d => d.WasEdited, e => e.MapFrom(s => s.MovementType.Id == (long)MovementKind.Order
 3811831                        ? s.Children != null
 3811832                            ? !s.Children.Items.Select(j => new { j.Good, j.Quantity })
 3811833                                .Equals(s.Items.Select(j => new { j.Good, j.Quantity }))
 3811834                            : false
 3811835                        : false))
 3811836                    .ForMember(d => d.MinOrderSum, e => e.MapFrom(s =>
 3811837                        s.MovementStatus.Id == (long)MovementsStatus.OrderDraft
 3811838                            ? s.Receiver.Cluster.MinOrderSum
 3811839                            : s.MinOrderSum))
 3811840                     .ForMember(d => d.AllowedDeliveryTypes, e => e.MapFrom(
 3811841                        s => s.Receiver.Cluster.ClusterDeliveryTypes.Select(b => new DeliveryTypeResponseDto
 3811842                        {
 3811843                            Id = b.DeliveryType.Id,
 3811844                            Name = b.DeliveryType.Name
 3811845                        })));
 1271846
 2541847                cfg.CreateMap<Department, DepartmentShortWithAddressDTO>()
 3811848                    .ForMember(d => d.Address, e => e.MapFrom(s => s.ActualAddress.FullAddress));
 2541849                cfg.CreateMap<Contragent, ContragentShortDTO>()
 3811850                    .ForMember(d => d.KindId, e => e.MapFrom(s => (int)s.ContragentsKind.Id));
 2541851                cfg.CreateMap<MovementItem, MovementItemResponseDTO>();
 2541852                cfg.CreateMap<Good, GoodShortDTO>()
 3811853                    .ForMember(d => d.Barcode, e => e.MapFrom(s => s.GoodBarcodes.FirstOrDefault(d => d.IsPrimary) != nu
 3811854                        ? s.GoodBarcodes.FirstOrDefault(d => d.IsPrimary).BarCode.Code
 3811855                        : s.DefaultBarCode.Code));
 2541856                cfg.CreateMap<MovementNote, MovementNoteDTO>()
 3811857                    .ForMember(d => d.User, e => e.MapFrom(s => new UserNoteDTO { Id = s.CreatedByUser.Id, FullName = $"
 2541858                cfg.CreateMap<DeliveryType, IdNameDTO>();
 2541859            });
 1271860            return config.CreateMapper();
 1271861        }
 1862
 1863        /// <summary>
 1864        /// Маппер для итемов заказа
 1865        /// </summary>
 1866        /// <param name="departmentId"></param>
 1867        /// <param name="discountColors"></param>
 1868        /// <returns></returns>
 1869        private IMapper ToItemDTOMapper(long departmentId, List<DiscountColor> discountColors)
 731870        {
 731871            var config = new MapperConfiguration(cfg =>
 1461872            {
 1461873                cfg.CreateMap<MovementItem, MovementItemResponseDTO>()
 1461874                    .ForMember(d => d.PriceWithoutVat,
 2191875                        e => e.MapFrom(s =>
 2191876                            Math.Round(s.Price / (s.Good.VatsKind.Value == 1
 2191877                                ? 1
 2191878                                : (decimal) s.Good.VatsKind.Value / 100 + 1), 2)))
 1461879                    .ForMember(d => d.Discount,
 2191880                        e => e.MapFrom(x => x.Good.Prices.Actual(departmentId).DiscountForDisplay()))
 1461881                    .ForMember(d => d.LabelColor,
 2191882                        e => e.MapFrom(x =>
 2191883                            GetDiscountColor(x.Good.Prices.Actual(departmentId).DiscountForDisplay(), discountColors)))
 2191884                    .ForMember(d => d.OldPrice, e => e.MapFrom(x =>
 2191885                        Math.Ceiling(
 2191886                            //Текущая цена товара
 2191887                            x.Good.Prices.Actual(departmentId).PriceOld.GetValueOrDefault(0)
 2191888                            // умножаем на коофициент контракта
 2191889                            * _currentMovement.Customer.ContractsAsBuyer.FirstOrDefault(d => !d.IsDeleted
 2191890                                                                                             && d.BeginDate <=
 2191891                                                                                             DateTime.UtcNow
 2191892                                                                                             && d.EndDate >
 2191893                                                                                             DateTime.UtcNow)
 2191894                                .RatioForCalculations
 2191895                            // умножаем на коофициент кластера
 2191896                            * _currentMovement.Receiver.Cluster.RatioForCalculations
 2191897                            // умножаем на коофициент категории
 2191898                            * (x.Good.Category.DepartmentCategoryRatios.Any(r =>
 2191899                                !r.IsDeleted && r.DepartmentId == _currentMovement.Receiver.Cluster.WarehouseId)
 2191900                                ? x.Good.Category.DepartmentCategoryRatios.First(r =>
 2191901                                        !r.IsDeleted && r.DepartmentId == _currentMovement.Receiver.Cluster.WarehouseId)
 2191902                                    .RatioForCalculations
 2191903                                : 1)
 2191904                        )
 2191905                    ))
 2191906                    .ForMember(d => d.Good, e => e.MapFrom(s => s.Good))
 1461907                    .ForPath(d => d.Good.MinQuantity,
 2191908                        e => e.MapFrom(s =>
 2191909                            (s.Good.DepartmentGoodSettings.FirstOrDefault(g =>
 2191910                                !g.IsDeleted && g.DepartmentId == departmentId) == null
 2191911                                ? 1
 2191912                                : s.Good.DepartmentGoodSettings
 2191913                                    .FirstOrDefault(g => !g.IsDeleted && g.DepartmentId == departmentId)
 2191914                                    .MinQuantity)))
 1461915                    .ForPath(d => d.Good.PickingQuantum,
 2191916                        e => e.MapFrom(s =>
 2191917                            s.Good.DepartmentGoodSettings.FirstOrDefault(g =>
 2191918                                !g.IsDeleted && g.DepartmentId == departmentId) == null
 2191919                                ? 1
 2191920                                : s.Good.DepartmentGoodSettings
 2191921                                    .FirstOrDefault(g => !g.IsDeleted && g.DepartmentId == departmentId)
 2191922                                    .PickingQuantum));
 731923
 1461924                cfg.CreateMap<Good, ItemGoodDTO>()
 2191925                    .ForMember(d => d.VendorCode, e => e.MapFrom(s => s.GetActualVendorCode(departmentId)))
 2191926                    .ForMember(d => d.VatKindValue, e => e.MapFrom(s => s.VatsKind.Name))
 2191927                    .ForMember(d => d.UnitKindName, e => e.MapFrom(s => s.UnitsKind.Name))
 1461928                    .ForMember(d => d.Category,
 2191929                        e => e.MapFrom(s => new IdNameDTO {Id = s.Category.Id, Name = s.Category.Name}))
 2191930                    .ForMember(d => d.Brand, e => e.MapFrom(s => new IdNameDTO {Id = s.Brand.Id, Name = s.Brand.Name}))
 2191931                    .ForMember(d => d.Photo, e => e.MapFrom(g => (g.Photos.FirstOrDefault())))
 2191932                    .ForMember(d => d.BarCode, e => e.MapFrom(g => g.GetActualBarCode()));
 1461933            });
 731934            return config.CreateMapper();
 731935        }
 1936
 1937        /// <summary>
 1938        /// Добавляет вложение к мувменту
 1939        /// </summary>
 1940        /// <param name="movementId">id мувмента</param>
 1941        /// <param name="file">вложение</param>
 1942        /// <returns></returns>
 1943        public async Task<UploadResultDTO> AddAttachment(long movementId, List<IFormFile> files)
 1944        {
 1945            var movement = await _movementService.GetMovementWithAttachment(movementId)
 1946                           ?? throw new KeyNotFoundException($"Документ {movementId} не найден");
 1947            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 1948            if (!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 1949                _authUserService.ContragentId, _authUserService.IsOwner(),
 1950                userDeps))
 1951            {
 1952                throw new ForbidException();
 1953            }
 1954
 1955            long size = files.Sum(d => d.Length);
 1956            long maxSettingsTotalSize = Int64.Parse(_confSettings.GetConfValue(nameof(MovementAttachmentSettings), nameo
 1957            if (size + movement.TotalAttachmentSize > maxSettingsTotalSize)
 1958            {
 1959                throw new SvetaException($"Занимаемый объем загружаемых файлов {size} и " +
 1960                                         $"общий объем уже сохраненных файлов {movement.TotalAttachmentSize} превышает "
 1961                                         $"разрешенный суммарный размер файлов для вложений {maxSettingsTotalSize}", (in
 1962            }
 1963            List<string> errors = new List<string>();
 1964            long maxSettingsFileSize = Int64.Parse(_confSettings.GetConfValue(nameof(MovementAttachmentSettings),
 1965                nameof(MovementAttachmentSettings.MaxFileSize)));
 1966            foreach (var formFile in files)
 1967            {
 1968                try
 1969                {
 1970                    if (formFile.Length > maxSettingsFileSize)
 1971                    {
 1972                        errors.Add($"Размер файла {formFile.FileName} превышает разрешенный размер файлов {maxSettingsFi
 1973                                   $"для вложений и не будет загружен");
 1974                        continue ;
 1975                    }
 1976                    string filename = null;
 1977                    filename = await _diskStorageService.SaveUploadToDownloadDir(formFile);
 1978                    movement.MovementAttachments.Add(new MovementAttachment
 1979                    {
 1980                        FileName = filename,
 1981                        Size = (int)formFile.Length
 1982                    });
 1983                }
 1984                catch (Exception e)
 1985                {
 1986                    _logger.LogError("Ошибка загрузки файла вложения" + " " +e.Message + " " + e.StackTrace);
 1987                    errors.Add($"Во время загрузки файла {formFile.FileName} произошла ошибка. Файл не был загружен");
 1988                }
 1989            }
 1990
 1991            movement.TotalAttachmentSize = movement.MovementAttachments.Sum(d => d.Size);
 1992            await _movementService.UpdateMovement(movement);
 1993            return new UploadStringResultDto(){ succeed = true , errorCount = errors.Count, Result = errors};
 1994        }
 1995
 1996        /// <summary>
 1997        /// Возвращает информацию о вложениях в документ
 1998        /// </summary>
 1999        /// <param name="movementId">Идентификатор документа</param>
 2000        /// <returns></returns>
 2001        public async Task<PaginatedData<List<MovementAttachmentInfoDto>>> GetAttachmentsInfo(long movementId, int page =
 2002            int limit = 10, string filter = default, string sort = default)
 02003        {
 02004            page = page < 0 ? 0 : page;
 02005            limit = limit <= 0 ? 10 : limit;
 02006            var movement = await _movementService.GetMovementWithAttachment(movementId) ??
 02007                           throw new ArgumentException($"Документ {movementId} не найден");
 02008            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 02009            if (!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 02010                _authUserService.ContragentId, _authUserService.IsOwner(),
 02011                userDeps))
 02012            {
 02013                throw new ForbidException();
 2014            }
 2015
 02016            var result = new PaginatedData<List<MovementAttachmentInfoDto>>
 02017            {
 02018                TotalCount = 0,
 02019                TotalFilteredCount = 0,
 02020                Result = new List<MovementAttachmentInfoDto>()
 02021            };
 02022            var list = new List<MovementAttachmentInfoDto>();
 02023            movement.MovementAttachments
 02024                .Where(d => string.IsNullOrEmpty(filter) || d.FileName.ToLower().Contains(filter.Trim().ToLower()))
 02025                .ToList()
 02026                .ForEach(attachment =>
 02027            {
 02028                list.Add((MovementAttachmentInfoDto) attachment);
 02029            });
 2030
 2031            Expression<Func<MovementAttachmentInfoDto, object>> sortingExpression;
 2032
 02033            if (!string.IsNullOrEmpty(sort))
 02034            {
 02035                switch ((sort.Split("|").FirstOrDefault() ?? "").ToLower())
 2036                {
 2037                    case "name":
 02038                        sortingExpression = e => e.Name;
 02039                        break;
 2040                    case "created_on":
 02041                        sortingExpression = e => e.CreatedDateTime;
 02042                        break;
 2043                    case "extension":
 02044                        sortingExpression = e => e.Extension;
 02045                        break;
 2046                    case "size":
 02047                        sortingExpression = e => e.Size;
 02048                        break;
 2049                    case "contenttype":
 02050                        sortingExpression = e => e.ContentType;
 02051                        break;
 2052                    default:
 02053                        sortingExpression = e => e.Id;
 02054                        break;
 2055                }
 2056
 02057                if((sort.Split("|").LastOrDefault() ?? "").ToLower().Equals("desc"))
 02058                    list = list.AsQueryable().OrderByDescending(sortingExpression).ToList();
 2059                else
 02060                    list = list.AsQueryable().OrderBy(sortingExpression).ToList();
 02061            }
 2062
 02063            result.TotalCount = movement.MovementAttachments.Count;
 02064            result.TotalFilteredCount = list.Count;
 02065            result.Result = list.Skip((page >= 1 ? page - 1 : page) * limit).Take(limit).ToList();
 02066            return result;
 02067        }
 2068
 2069        /// <summary>
 2070        /// Загружает вложение из мувмента
 2071        /// </summary>
 2072        /// <param name="movementId">id мувмента</param>
 2073        /// <returns></returns>
 2074        public async Task<MovementAttachmentReadResult> DownloadAttachment(long movementId, List<long> attachments)
 02075        {
 02076            var movement = await _movementService.GetMovementWithAttachment(movementId) ?? throw new KeyNotFoundExceptio
 02077            var userDeps = await _userService.GetUserDepartments(_authUserService.UserId);
 02078            if (!movement.CheckCanWorkWithMovement(_authUserService.ContragentKindId,
 02079                _authUserService.ContragentId, _authUserService.IsOwner(),
 02080                userDeps))
 02081            {
 02082                throw new ForbidException();
 2083            }
 2084
 02085            if (movement.MovementAttachments.Count == 0)
 02086            {
 02087                throw new SvetaException("Вложения отсутствуют", (int)ErrorCode.Warning);
 2088            }
 2089
 02090            var result = new MovementAttachmentReadResult();
 02091            if (attachments.Count == 1)
 02092            {
 02093                var stream = new MemoryStream();
 02094                var file = movement.MovementAttachments.FirstOrDefault(d =>
 02095                    d.Id == attachments.First()) ?? throw new ArgumentException(
 02096                        $"Файл с идентификатором {attachments.First()} не найден" +
 02097                        $"во вложениях");
 02098                var info = (MovementAttachmentInfoDto) file;
 02099                var path = _diskStorageService.GetDownloadPath(info.Name);
 2100                try
 02101                {
 02102                    using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
 02103                    {
 02104                        fs.CopyTo(stream);
 02105                    }
 02106                }
 02107                catch (FileNotFoundException fe)
 02108                {
 02109                    throw new ArgumentException($"Файл {info.Name} не найден на диске");
 2110                }
 02111                catch (Exception e)
 02112                {
 02113                    _logger.LogError("Ошибка чтения файла "+ info.Name + " " + e.Message + " " + e.StackTrace);
 02114                }
 2115
 02116                result.Stream = stream;
 02117                result.ContentType = info.ContentType;
 02118                result.FileName = info.Name;
 02119            }
 2120            else
 02121            {
 02122                var stream = new MemoryStream();
 02123                using (var arch = new ZipArchive(stream, ZipArchiveMode.Create, true))
 02124                {
 02125                    movement.MovementAttachments
 02126                        .Where(d => attachments.Contains(d.Id))
 02127                        .ToList()
 02128                        .ForEach(attachment =>
 02129                    {
 02130                        var info = (MovementAttachmentInfoDto) attachment;
 02131                        var path = _diskStorageService.GetDownloadPath(info.Name);
 02132                        var file = arch.CreateEntry(info.Name);
 02133                        using (var entryStream = file.Open())
 02134                            try
 02135                            {
 02136                                using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
 02137                                {
 02138                                    fs.CopyTo(entryStream);
 02139                                }
 02140                            }
 02141                            catch (FileNotFoundException fe)
 02142                            {
 02143                                throw new ArgumentException($"Файл {info.Name} не найден на диске");
 02144                            }
 02145                            catch (Exception e)
 02146                            {
 02147                                _logger.LogError("Ошибка чтения файла "+ info.Name + " " + e.Message + " " + e.StackTrac
 02148                            }
 02149                    });
 02150                }
 02151                result.Stream = stream;
 02152                result.ContentType = ContentTypeChecker.CheckContentType("zip");
 02153                result.FileName = "archive.zip";
 02154            }
 2155
 02156            result.Stream.Position = 0;
 02157            return result;
 02158        }
 2159
 2160        /// <summary>
 2161        /// Удаляет вложение из мувмента
 2162        /// </summary>
 2163        /// <param name="movementId">id мувмента</param>
 2164        /// <param name="attachmentId">идентификатор вложения</param>
 2165        /// <returns></returns>
 2166        public async Task DeleteAttachment(long movementId, long attachmentId)
 2167        {
 2168            var movement = await _movementService.GetMovementWithAttachment(movementId)
 2169                           ?? throw new KeyNotFoundException($"Документ не найден");
 2170            if (movement.Supplier.Id != _authUserService.ContragentId)
 2171                throw new ForbidException();
 02172            var attachment = movement.MovementAttachments.FirstOrDefault(d => d.Id == attachmentId)
 2173                             ?? throw new SvetaException($"Документ с идентификатором {attachmentId} не найден", (int)Er
 2174            attachment.IsDeleted = true;
 02175            movement.MovementAttachments[movement.MovementAttachments.FindIndex(d => d.Id == attachmentId)] =
 2176                attachment;
 2177            await _movementService.UpdateMovement(movement);
 2178        }
 2179
 2180        /// <summary>
 2181        /// Назначает заявке тип доставки
 2182        /// </summary>
 2183        /// <param name="orderId">id заявки</param>
 2184        /// <param name="deliveryTypeId">id типа доставки</param>
 2185        /// <returns></returns>
 2186        public async Task SetDeliveryType(long orderId, long deliveryTypeId)
 12187        {
 12188            var movement = await _movementService.GetLightMovement(orderId) ?? throw new KeyNotFoundException($"Документ
 12189            if (movement.Customer.Id != _authUserService.ContragentId)
 02190                throw new ForbidException();
 12191            var deliveryType = await _dirService.GetDeliveryType(deliveryTypeId) ?? throw new KeyNotFoundException($"Тип
 12192            movement.DeliveryType = deliveryType;
 12193            await _movementService.UpdateMovement(movement);
 12194        }
 2195
 2196        /// <summary>
 2197        /// Заполняет полный путь к вложению
 2198        /// </summary>
 2199        /// <param name="filename">Имя файла</param>
 2200        /// <returns></returns>
 2201        private List<AttachmentDTO> GetFullPath(List<string> filenames)
 02202        {
 02203            var res = new List<AttachmentDTO>();
 02204            if (filenames == null || filenames.Count == 0)
 02205                return res;
 02206            filenames.ForEach(filename =>
 02207            {
 02208                if (!string.IsNullOrEmpty(filename))
 02209                {
 02210                    res.Add(new AttachmentDTO { FileName = filename, FilePath = _diskStorageService.GetDownloadPath(file
 02211                }
 02212            });
 02213            return res;
 02214        }
 2215
 2216        /// <summary>
 2217        /// Возвращает цвет ярлыка для скидки
 2218        /// </summary>
 2219        /// <param name="discount">сумма скидки</param>
 2220        /// <param name="discountColors">массив цветов</param>
 2221        /// <returns></returns>
 2222        private string GetDiscountColor(int? discount, List<DiscountColor> discountColors)
 772223        {
 772224            return discount.HasValue
 02225                ? discountColors.Where(x => x.DiscountLevel <= discount.Value).OrderByDescending(x => x.DiscountLevel).F
 772226                : null;
 772227        }
 2228
 2229        /// <summary>
 2230        /// Проверка соответствия изменяемого количества настрокам департамента для товара
 2231        /// </summary>
 2232        /// <param name="quantity">количество заказанного товара</param>
 2233        /// <param name="goodSettings">объект настроек товара</param>
 2234        /// <returns></returns>
 2235        /// <exception cref="ArgumentException"></exception>
 2236        private void CheckSettings(decimal quantity, DepartmentGoodSetting goodSettings)
 682237        {
 682238            if (quantity < goodSettings.MinQuantity)
 02239                throw new ArgumentException($"Минимальное количество заказанного товара {goodSettings.Good.Name} должно 
 682240            if (quantity % goodSettings.PickingQuantum != 0)
 02241                throw new ArgumentException($"Количество заказанного товара {quantity} не кратно кванту поставки {goodSe
 682242        }
 2243
 2244        /// <summary>
 2245        /// Получение черновика для типа документа
 2246        /// </summary>
 2247        /// <param name="type">тип документа</param>
 2248        /// <returns></returns>
 1742249        private async Task<MovementStatus> GetDraft(MovementType type) => await _movementTypeStatusService
 1742250            .GetStatus(type, "draft");
 2251
 2252        /// <summary>
 2253        /// Подготовливает список документов
 2254        /// </summary>
 2255        /// <param name="result">Список документов для обработки</param>
 2256        /// <param name="movementType">Тип документов</param>
 2257        /// <returns></returns>
 2258        private async Task<List<Movement>> PrepareStatusList(List<Movement> result, MovementType movementType)
 02259        {
 02260            var allActions = await _movementRouteActionsService.GetActions();
 02261            Parallel.For(0, result.Count, elem =>
 02262            {
 02263                var item = result[elem];
 02264                item.Statuses = new long[5];
 02265                item.Actions = new List<MovementAction>();
 02266                item.FillActions(allActions, _authUserService.ContragentKindId, _authUserService.Roles);
 02267                switch (movementType.Id)
 02268                {
 02269                    case (long) MovementKind.Order:
 02270                    {
 02271                        // если статус родителя отказ то можно не продолжать
 02272                        if ((item.Statuses[0] = item.GetPositionStatus(0)) == -1) break;
 02273                        var children = item.Children;
 02274                        if (children == null) return;
 02275                        for (int i = 1; i < item.Statuses.Length; i++)
 02276                        {
 02277                            item.Statuses[i] = children.GetPositionStatus(i);
 02278                            if (item.Statuses[1] == -1) break;
 02279                        }
 02280                        break;
 02281                    }
 02282                    case (long) MovementKind.Shipment:
 02283                    {
 02284                        var parent = item.Parent;
 02285                        //Почему то не нашли родителя отгрузки
 02286                        if (parent == null) return;
 02287
 02288                        // если статус родителя отказ то можно не продолжать
 02289                        if ((item.Statuses[0] = parent.GetPositionStatus(0)) == -1) break;
 02290                        for (int i = 1; i < item.Statuses.Length; i++)
 02291                        {
 02292                            item.Statuses[i] = item.GetPositionStatus(i);
 02293                            if (item.Statuses[1] == -1) break;
 02294                        }
 02295                        break;
 02296                    }
 02297                }
 02298            });
 02299            return result;
 02300        }
 2301
 2302        /// <summary>
 2303        /// Сортировка документов
 2304        /// </summary>
 2305        /// <param name="movements">Массив документов</param>
 2306        /// <param name="sort">тип сортировки</param>
 2307        /// <returns></returns>
 02308        public IQueryable<MovementDTO> SortMovement(IQueryable<MovementDTO> movements, string sort = default) => (sort ?
 02309        {
 02310            "created_on" => movements.OrderBy(d => d.CreationDateTime),
 02311            "created_on|desc" => movements.OrderByDescending(d => d.CreationDateTime),
 02312            "status" => movements.OrderBy(d => d.MovementStatus.Id),
 02313            "status|desc" => movements.OrderByDescending(d => d.MovementStatus.Id),
 02314            "customer" => movements.OrderBy(d => d.Customer),
 02315            "customer|desc" => movements.OrderByDescending(d => d.Customer),
 02316            "supplier" => movements.OrderBy(d => d.Supplier),
 02317            "supplier|desc" => movements.OrderByDescending(d => d.Supplier),
 02318            "receiver" => movements.OrderBy(d => d.Receiver),
 02319            "receiver|desc" => movements.OrderByDescending(d => d.Receiver),
 02320            "id|desc" => movements.OrderByDescending(d => d.Id),
 02321            _ => movements.OrderBy(d => d.Id),
 02322        };
 2323    }
 2324}