< Summary

Class:SVETA.Api.Helpers.MovementStatusRouter
Assembly:SVETA.Api
File(s):/opt/dev/sveta_api_build/SVETA.Api/Services/Implements/MovementStatusRouter.cs
Covered lines:162
Uncovered lines:63
Coverable lines:225
Total lines:1089
Line coverage:72% (162 of 225)
Covered branches:92
Total branches:152
Branch coverage:60.5% (92 of 152)

Metrics

MethodLine coverage Branch coverage
.cctor()100%100%
.ctor(...)100%100%
GetNextStatus()100%100%
SetNextExchangeStatus()78.94%55.55%
SetNextStatus()71.87%58.33%
UpdateStatusMovement()95%62.5%
CreateLink(...)100%100%

File(s)

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

#LineLine coverage
 1using System;
 2using SVETA.Api.Data.DTO.Sms;
 3using System.Collections.Generic;
 4using System.Globalization;
 5using System.Linq;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Microsoft.AspNetCore.Builder;
 9using Microsoft.CodeAnalysis.Options;
 10using Microsoft.EntityFrameworkCore;
 11using Microsoft.Extensions.Logging;
 12using Microsoft.Extensions.Options;
 13using SVETA.Api.Data.Domain;
 14using SVETA.Api.Helpers.Authorize;
 15using SVETA.Api.Services.Implements;
 16using SVETA.Api.Services.Interfaces;
 17using WinSolutions.Sveta.Common;
 18using WinSolutions.Sveta.Server.Data.DataModel.Entities;
 19using WinSolutions.Sveta.Server.Data.DataModel.Extensions;
 20using WinSolutions.Sveta.Server.Data.DataModel.Kinds;
 21using WinSolutions.Sveta.Server.Services.Interfaces;
 22using SVETA.Api.Data.DTO.Wallet;
 23
 24namespace SVETA.Api.Helpers
 25{
 26    public class MovementStatusRouter : IMovementStatusRouter
 27    {
 28        private readonly ILogger<MovementStatusRouter> _logger;
 29        private readonly IMovementTypeStatusService _movementTypeStatusService;
 30        private readonly IMovementRouteService _routeService;
 31        private readonly IMovementRouteActionsService _routeActionsService;
 32
 33        private readonly ISmsWorker _smsWorker;
 34        private readonly IDirectoriesService _dirService;
 35        private readonly IEmailWorker _emailWorker;
 36        private readonly IWorkScheduleService _scheduleService;
 37        private readonly IMovementService _movementService;
 38        private readonly IExchangeTokenService _exchangeTokenService;
 39        private readonly IAuthenticationService _authUserService;
 40        private readonly INotificationWorker _notificationWorker;
 41        private readonly IMovementWorker _movementWorker;
 42        private readonly IRestService _restService;
 43        private readonly IRestHoldService _restHoldService;
 44        private readonly IWalletPaymentService _walletPaymentService;
 45        private readonly IMovementStatusJournalService _movementStatusJournalService;
 46        private readonly IUserService _userService;
 47        private readonly IAddressService _addressService;
 48        private readonly CommonSettings _commonSettings;
 49        private readonly IClusterService _clusterService;
 50        private readonly IGoodService _goodService;
 51
 152        private static SemaphoreSlim createShipmentSlim = new SemaphoreSlim(1);
 153        private static SemaphoreSlim releaseRestSlim = new SemaphoreSlim(1);
 154        private static SemaphoreSlim createRestSlim = new SemaphoreSlim(1);
 155        private static SemaphoreSlim supplierRejectSlim = new SemaphoreSlim(1);
 156        private static SemaphoreSlim claimAcceptedSlim = new SemaphoreSlim(1);
 157        private static SemaphoreSlim claimDeclinedSlim = new SemaphoreSlim(1);
 158        private static SemaphoreSlim customerRejectdSlim = new SemaphoreSlim(1);
 59
 8360        public MovementStatusRouter(ILogger<MovementStatusRouter> logger,
 8361            IMovementTypeStatusService movementTypeStatusService,
 8362            IMovementRouteActionsService movementRouteActionsService,
 8363            IMovementRouteService routeService,
 8364            ISmsWorker smsWorker,
 8365            IEmailWorker emailWorker,
 8366            IWorkScheduleService scheduleService,
 8367            IMovementService movementService,
 8368            IExchangeTokenService exchangeTokenService,
 8369            IAuthenticationService authenticationService,
 8370            INotificationWorker notificationWorker,
 8371            IMovementWorker movementWorker,
 8372            IRestService restService,
 8373             IDirectoriesService dirService,
 8374            IRestHoldService restHoldService,
 8375            IWalletPaymentService walletPaymentService,
 8376            IMovementStatusJournalService movementStatusJournalService,
 8377            IUserService userService,
 8378            IClusterService clusterService,
 8379            IAddressService addressService,
 8380            IGoodService goodService,
 8381            IOptions<CommonSettings> commonSettingsOption
 8382        )
 8383        {
 8384            _emailWorker = emailWorker;
 8385            _smsWorker = smsWorker;
 8386            _dirService = dirService;
 8387            _scheduleService = scheduleService;
 8388            _logger = logger;
 8389            _movementTypeStatusService = movementTypeStatusService;
 8390            _routeService = routeService;
 8391            _routeActionsService = movementRouteActionsService;
 8392            _movementService = movementService;
 8393            _exchangeTokenService = exchangeTokenService;
 8394            _authUserService = authenticationService;
 8395            _commonSettings = commonSettingsOption.Value;
 8396            _notificationWorker = notificationWorker;
 8397            _movementWorker = movementWorker;
 8398            _restService = restService;
 8399            _restHoldService = restHoldService;
 83100            _walletPaymentService = walletPaymentService;
 83101            _movementStatusJournalService = movementStatusJournalService;
 83102            _userService = userService;
 83103            _addressService = addressService;
 83104            _clusterService = clusterService;
 83105            _goodService = goodService;
 83106        }
 107
 108        /// <summary>
 109        /// Возвращает следующий статус
 110        /// </summary>
 111        /// <param name="statusId">Идентификатор текущео статуса</param>
 112        /// <param name="key">Ключ для перехода</param>
 113        /// <param name="warehouseId">Идентификатор склада</param>
 114        /// <returns></returns>
 115        public async Task<MovementStatus> GetNextStatus(long statusId, string key, long warehouseId)
 282116        {
 282117            var routes = await _routeService.GetRoutes(statusId, warehouseId);
 961118            return routes.FirstOrDefault(d => d.WarehouseId == warehouseId
 961119                && (string.IsNullOrEmpty(key) || d.RouteKey.Equals(key, StringComparison.OrdinalIgnoreCase)))?.StatusNex
 282120        }
 121
 122        /// <summary>
 123        /// Установка следующего статуса через обмен с 1С
 124        /// </summary>
 125        /// <param name="movementGuid">Внешний идентификатор документа</param>
 126        /// <param name="tnx">Токен для обмена</param>
 127        /// <param name="key">Ключ для перехода</param>
 128        /// <param name="comment">Комментарий</param>
 129        /// <returns></returns>
 130        /// <exception cref="ForbidException"></exception>
 131        public async Task SetNextExchangeStatus(Guid movementGuid, Guid tnx, string key = default, string comment = defa
 23132        {
 23133            var token = await _exchangeTokenService.GetToken(tnx) ?? throw new ArgumentException($"Токен не найден");
 23134            Movement movement = await _movementService.GetMovementForNextStatus(movementGuid) ??
 23135                                throw new KeyNotFoundException($"Документ #{movementGuid} не найден");
 23136            if (!(token.Department.Contragent.ContragentsKind.Id switch
 23137            {
 23138                (long) ContragentKind.Wholesaler => movement.Supplier.Id == token.DepartmentId,
 23139                _ => false
 23140            }))
 0141            {
 0142                throw new ForbidException();
 143            }
 23144            if (key.Equals(MovementStatusKeys.correctionShipment) && movement.MovementStatus.Id == (long)MovementsStatus
 0145                return;
 146
 23147            if (key.Equals(MovementStatusKeys.deliver) && movement.DeliveryType.Id != (long)DeliveryTypeKind.Delivery)
 0148                throw new ArgumentException($"ТПЗ документа {movement.GUID} - {movement.DeliveryType.Name}");
 149
 23150            MovementStatus status = await GetNextStatus(movement.MovementStatus.Id, key, movement.Sender.Id) ??
 23151                                    throw new SvetaException($"Переход из статуса {movement.MovementStatus.Name} по ключ
 152
 23153            await BeforeUpdateStatus(movement, status, comment, false).ConfigureAwait(true);
 23154        }
 155
 156        /// <summary>
 157        /// Установка следующего статуса для документа
 158        /// </summary>
 159        /// <param name="movementId">Идентификатор документа</param>
 160        /// <param name="key">Ключ для перехода</param>
 161        /// <param name="comment">Комментарий</param>
 162        /// <returns></returns>
 163        /// <exception cref="ForbidException"></exception>
 164        /// <exception cref="SvetaException"></exception>
 165        public async Task SetNextStatus(long movementId, string key = default, string comment = default)
 225166        {
 167            try
 225168            {
 225169                Movement movement = await _movementService.GetMovementForNextStatus(movementId) ??
 225170                                    throw new KeyNotFoundException($"Документ #{movementId} не найден");
 225171                MovementStatus status = await GetNextStatus(movement.MovementStatus.Id, key, movement.Sender.Id)
 225172                         ?? throw new SvetaException($"Переход из статуса {movement.MovementStatus.Name} по ключу {key} 
 173
 225174                if (!(movement.MovementStatus.StatusOwner.Id switch
 225175                {
 225176                    (long) MovementStatusOwners.Customer => movement.Customer.Id == _authUserService.ContragentId ||
 225177                                                            _authUserService.IsUserPlatform(),
 225178                    (long) MovementStatusOwners.Supplier => movement.Supplier.Id == _authUserService.ContragentId ||
 225179                                                            _authUserService.IsUserPlatform(),
 225180                    (long) MovementStatusOwners.Platform => _authUserService.IsUserPlatform(),
 225181                    _ => false
 225182                }) && !(new[]
 225183                {
 225184                    (long) MovementsStatus.Reject, (long) MovementsStatus.CustomerReject,
 225185                    (long) MovementsStatus.SupplierReject
 225186                }).Contains(status.Id))
 0187                {
 0188                    throw new ForbidException();
 189                }
 225190                if (movement.Items.Count == 0)
 0191                    throw new SvetaException(
 0192                        "Документ пуст. Переход в следующий статус невозможен. Добавьте позицию в документ.", (int) Erro
 193
 225194                await BeforeUpdateStatus(movement, status, comment).ConfigureAwait(true);
 225195            }
 0196            catch (Exception e)
 0197            {
 0198                _logger.LogError("Ошибка перевода документа в следующий статус");
 0199                _logger.LogError(e.Message, e);
 0200                throw;
 201            }
 225202        }
 203
 204        /// <summary>
 205        /// Выполнение подготовительных действий с документом перед обновлением статуса - проверка остатков, резервирова
 206        /// </summary>
 207        /// <param name="movement">Документ для обновления статуса</param>
 208        /// <param name="status">Следующий статус</param>
 209        /// <param name="comment">Комментарий</param>
 210        /// <param name="errorToNotification">Вывод ошибок в нотификации - по умолчанию true</param>
 211        /// <returns></returns>
 212        /// <exception cref="SvetaException"></exception>
 213        /// <exception cref="ArgumentException"></exception>
 214        /// <exception cref="Exception"></exception>
 215        private async Task BeforeUpdateStatus(Movement movement, MovementStatus status, string comment, bool errorToNoti
 216        {
 217
 218            if (comment != null)
 219                movement.Notes.Add(new MovementNote { Body = comment });
 220            string text = default;
 221            // Создание черновика отгрузки при переходе заявки в статус в Работе
 222            switch (status.Id)
 223            {
 224                case (long)MovementsStatus.InQueue:
 225                    {
 226                        try
 227                        {
 86228                            if (movement.PrepaimentSum != Math.Ceiling(movement.Items.Sum(i => i.Price * i.Quantity)))
 229                            {
 230                                movement.ReCalculate();
 231                            }
 232
 258233                            foreach (var good in await _goodService.GetGoods(movement.Items.Select(d => d.GoodId).ToList
 234                            {
 86235                                movement.Items.FirstOrDefault(d => d.GoodId == good.Id).Good = good;
 236                            }
 237
 86238                            var errors = movement.Items.Where(item => item.Good.Prices.Actual(movement.Sender.Id) == nul
 86239                                                                      || item.Good.Rests.All(d =>
 129240                                                                          !d.IsDeleted && d.DepartmentId == movement.Sen
 129241                                                                          d.Quantity == 0))
 0242                                                    .Select(d => $"Товар {d.Good.Name} не может быть заказан.").ToList()
 243                            errors.AddRange(movement.Items.Select(item =>
 86244                                    item.Good.DepartmentGoodSettings.FirstOrDefault(d =>
 0245                                        !d.IsDeleted && d.Department.Id == movement.Sender.Id) ?? new DepartmentGoodSett
 86246                                        { Good = item.Good, MinQuantity = 1, PickingQuantum = 1 })
 247                                .Where(setting =>
 129248                                    movement.Items.FirstOrDefault(item => item.Good.Id == setting.Good.Id).Quantity <
 86249                                    setting.MinQuantity)
 250                                .Select(error =>
 0251                                    $"Количество заказного товара {error.Good.Name} меньше минимальной партии {error.Min
 252                                .ToList());
 253                            errors.AddRange(movement.Items.Select(item =>
 86254                                    item.Good.DepartmentGoodSettings.FirstOrDefault(d =>
 0255                                        !d.IsDeleted && d.Department.Id == movement.Sender.Id) ?? new DepartmentGoodSett
 86256                                        { Good = item.Good, MinQuantity = 1, PickingQuantum = 1 })
 257                                .Where(setting =>
 129258                                    movement.Items.FirstOrDefault(item => item.Good.Id == setting.Good.Id).Quantity %
 86259                                    setting.PickingQuantum != 0)
 260                                .Select(error =>
 0261                                    $"Количество заказного товара {error.Good.Name} не кратно кванту поставки {error.Pic
 262                                .ToList());
 263                            movement.Receiver.Cluster = await _clusterService.GetCluster(movement.Receiver.ClusterId.Val
 264                            if (movement.Receiver.Cluster.MinOrderSum > movement.PrepaimentSum)
 265                                errors.Add($"Сумма заказа заявки меньше допустимой суммы ({movement.Receiver.Cluster.Min
 266                            if (movement.DeliveryType == null)
 267                            {
 268                                var deliveryTypeCount = movement.Receiver.Cluster.ClusterDeliveryTypes?.Count;
 269                                if (deliveryTypeCount == 0)
 270                                    throw new SvetaException($"У кластера склада не указаны типы доставки", (int)ErrorCo
 271                                else if (deliveryTypeCount == 1)
 272                                    movement.DeliveryType = await _dirService.GetDeliveryType(movement.Receiver.Cluster.
 273                                else
 274                                    throw new SvetaException($"Не выбран тип получения", (int)ErrorCode.Warning);
 275                            }
 276                            if (errors.Count > 0)
 277                            {
 278                                if (errors.Count < 3 || !errorToNotification)
 279                                {
 280                                    var inererror = "Заявка не отправлена. ";
 281                                    inererror += string.Join(" , ", errors);
 282                                    await _notificationWorker.CreateNotification(movement, "Ошибка отправки заявки", ine
 283                                    throw new SvetaException(inererror, (int)ErrorCode.Warning);
 284                                }
 285                                string error = $"При отправке заявки {MovementHelpers.CreateLink(movement, _commonSettin
 286                                error += string.Join(" , ", errors);
 287                                error += " Отредактируйте документ и попробуйте снова";
 288                                await _notificationWorker.CreateNotification(movement, "Ошибка отправки заявки", error);
 289                                throw new SvetaException("Заявка не отправлена. Дополнительная информация в оповещениях"
 290                            }
 291                            movement.MinOrderSum = movement.Receiver.Cluster.MinOrderSum;
 292                        }
 293                        catch (Exception e)
 294                        {
 295                            _logger.LogError($"Ошибка перевода заявки в статус в очереди на обработку {movement.Id}");
 296                            _logger.LogError(e.Message + "\n" + e.StackTrace);
 297                            throw;
 298                        }
 299
 300                        var isFirstOrder = !(await _movementService.GetFilterMovementsWithoutPagination(customerId: move
 301                        var balance = await _walletPaymentService.GetWalletFreeBalance(0,0) ?? new WalletBalanceFreeResp
 302                        if (isFirstOrder) //если первый заказ, отправляем смс/email
 303                        {
 304                            await _smsWorker.SendSmsByPattern("021", movement.Customer.PhoneNumber, movement.Customer.Ex
 305                                    {
 306                                        { "X", movement.DocumentNumber },
 307                                        { "LINK", _commonSettings.BaseFrontUrl + "/user/balance"}
 308                                    });
 309                            await _emailWorker.SendEmailByPattern(1226, "Заказ от клиента", movement.Customer.Email, mov
 310                                    {
 311                                        { "ordernum", movement.DocumentNumber },
 312                                        { "accounturl", CreateLink("ЗАПОКУПКИ.РФ", _commonSettings.BaseFrontUrl + "/user
 313                                    });
 314                        }
 315
 316                        if (movement.PrepaimentSum > balance.BalanceFree)//если недостаточно средств, отправляем смс/ema
 317                        {
 318                            var movementUser = (await _userService.GetContragentOwner(movement.Customer.Id))?.FirstName;
 319                            await _smsWorker.SendSmsByPattern("022", movement.Customer.PhoneNumber, movement.Customer.Ex
 320                                    {
 321                                        { "FIO", movementUser },
 322                                        { "X", movement.DocumentNumber },
 323                                        { "LINK",_commonSettings.BaseFrontUrl + "/user/balance"}
 324                                    });
 325                            await _emailWorker.SendEmailByPattern(1239, "Заказ от клиента", movement.Customer.Email, mov
 326                                    {
 327                                        { "name", movementUser },
 328                                        { "ordernum", movement.DocumentNumber },
 329                                        { "accounturl", CreateLink("счёт ЗАПОКУПКИ.РФ", _commonSettings.BaseFrontUrl + "
 330                                    });
 331                        }
 332                        break;
 333                    }
 334                case (long)MovementsStatus.InProgress:
 335                    {
 336                        try
 337                        {
 338                            await createShipmentSlim.WaitAsync();
 339                            // возможен откат из статуса обработано, проверим это
 340                            if (movement.ChildrenId.HasValue)
 341                            {
 342                                var history =
 343                                    await _movementStatusJournalService.GetMovementStatusJournal(movement.Id);
 5344                                if (history.Select(d => d.StatusCurrent.Id).Contains((long) MovementsStatus.Finished))
 345                                {
 346                                    //отгрузка уже была создана, можно обновлять статус без доп действий
 347                                    break;
 348                                }
 349                            }
 350                            // Создаем отгрузку
 351                            Movement shipment = null;
 234352                            foreach (var good in await _goodService.GetGoods(movement.Items.Select(d => d.GoodId).ToList
 353                            {
 78354                                movement.Items.FirstOrDefault(d => d.GoodId == good.Id).Good = good;
 355                            }
 356                            shipment = await _movementWorker.CreateShipmentDraft(movement).ConfigureAwait(true)
 357                                       ?? throw new ArgumentException($"Для заявки {movement.Id} не создалась отгрузка")
 358                            try
 359                            {
 360                                await _notificationWorker.CreateNotification(shipment, "Создан новый документ",
 361                                    $"Создан новый документ {MovementHelpers.CreateLink(shipment, _commonSettings)}");
 362                            }
 363                            catch (Exception e)
 364                            {
 365                                _logger.LogError(
 366                                    $"Ошибка создания уведомления о создании черновика отгрузки по заявке {movement.Id}"
 367                                _logger.LogError(e.Message + "\n" + e.StackTrace);
 368                            }
 369
 370                            if (shipment != null)
 371                            {
 372                                var type = await _movementTypeStatusService.GetMovementType((long)MovementKind.Shipment)
 373                                var existsShipments = await _movementService.GetMovementsByParent(movement.Id, type, 0);
 374                                if (existsShipments.Count > 1)
 375                                {
 0376                                    var existShip = existsShipments.OrderBy(d => d.CreationDateTime)
 0377                                        .FirstOrDefault(d => d.Id != shipment.Id);
 378                                    if (existShip != null)
 379                                    {
 380                                        if (movement.Children == null)
 381                                            movement.Children = existShip;
 382                                        //await _movementService.DeleteMovement(shipment);
 0383                                        foreach (var child in existsShipments.Where(d => d.Id != existShip.Id))
 384                                        {
 385                                            await _movementService.DeleteMovement(child);
 386                                        }
 387
 388                                        text = $" На основании заявки создана отгрузка №{existShip.DocumentNumber}";
 389                                    }
 390                                }
 391                                else
 392                                {
 393                                    movement.Children = shipment;
 394                                    text = $" На основании заявки создана отгрузка №{shipment.DocumentNumber}";
 395                                }
 396                            }
 397                            else
 398                            {
 399                                throw new ArgumentException(
 400                                    $"Для заявки {movement.DocumentNumber} не была создана отгрузка ");
 401                            }
 402                        }
 403                        catch (Exception e)
 404                        {
 405                            _logger.LogError(e.Message + "\n" + e.StackTrace);
 406                            throw;
 407                        }
 408                        finally
 409                        {
 410                            createShipmentSlim.Release();
 411                        }
 412
 413                        break;
 414                    }
 415                case (long)MovementsStatus.Reject:
 416                    {
 417                        try
 418                        {
 419                            _authUserService.SwitchToServiceUser();
 420                            await _movementWorker.RemoveChildrenDraft(movement.Id, MovementKind.Shipment, MovementsStatu
 421                            _authUserService.RevertUser();
 422                        }
 423                        catch (Exception e)
 424                        {
 425                            _logger.LogError($"Ошибка удаления черновика отгрузки при отказе от заявки {movement.Id} {e.
 426                            _logger.LogError(e.Message + "\n" + e.StackTrace);
 427                            throw;
 428                        }
 429
 430                        break;
 431                    }
 432                case (long) MovementsStatus.ShipmentDraft:
 433                {
 434                    var parent = await _movementService.GetLightMovement(movement.ParentId.Value);
 435                    if (parent != null && parent.MovementStatus.Id != (long) MovementsStatus.InProgress)
 436                    {
 437                        string commentLocal = $"Автоматический перевод заявки в статус \"В обработке\" из статуса " +
 438                                              $"\"{parent.MovementStatus.Name}\" после перевода отгрузки {movement.Docum
 439                                              $"\"Черновик\" {DateTime.UtcNow.ToMoscowTime().ToString("yyyy-MM-dd")} в "
 440                                              $"{DateTime.UtcNow.ToMoscowTime().ToString("HH:mm:ss", CultureInfo.Invaria
 441                        await SetNextStatus(movement.Parent.Id,
 442                            MovementStatusKeys.revertFinished, commentLocal);
 443                    }
 444                    break;
 445                }
 446                case (long)MovementsStatus.PaymentAwaiting:
 447                    {
 198448                        foreach (var good in await _goodService.GetGoods(movement.Items.Select(d => d.GoodId).ToList()))
 449                        {
 66450                            movement.Items.FirstOrDefault(d => d.GoodId == good.Id).Good = good;
 451                        }
 452                        List<string> errors = null;
 453                        try
 454                        {
 66455                            errors = movement.Items.Where(item => item.Good.Prices.Actual(movement.Sender.Id) == null)
 0456                            .Select(d => $"Не найдена актуальная цена для товара {d.Good.Name}").Union(
 66457                                movement.Items.Where(item => item.Good.Rests.Any(d =>
 99458                                        !d.IsDeleted && d.DepartmentId == movement.Sender.Id && d.Quantity < item.Quanti
 0459                                    .Select(d => $"Недостаточно товара {d.Good.Name}, заказано {d.Quantity},  на складе 
 460
 461                        }
 462                        catch (Exception e)
 463                        {
 464                            _logger.LogError($"Ошибка сбора данных по отгрузке {movement.Id} перед отправкой на оплату {
 465                            _logger.LogError(e.Message + " \n " + e.StackTrace);
 466                            throw;
 467                        }
 468                        if (errors != null && errors.Count > 0)
 469                        {
 470                            string error =
 471                                $"Отгрузка {MovementHelpers.CreateLink(movement, _commonSettings)} не может быть отправл
 472                            error += string.Join(" , ", errors);
 473                            if (errorToNotification)
 474                            {
 475                                await _notificationWorker.CreateNotification(movement, "Ошибка отправки заявки на оплату
 476                                throw new SvetaException("Заявка не отправлена. Дополнительная информация в оповещениях"
 477                                    (int)ErrorCode.Warning);
 478                            }
 479                            else
 480                            {
 481                                throw new ArgumentException(error);
 482                            }
 483                        }
 484                        try
 485                        {
 486                            if (movement.ParentId == null)
 487                                throw new ArgumentException("Не указан идентификатор родителя");
 488                            var parent = await _movementService.GetLightMovement(movement.ParentId.Value);
 489                            if (parent != null &&
 490                                parent.MovementStatus?.Id == (long)MovementsStatus.InProgress)
 491                            {
 492                                string commentLocal = $"Автоматический перевод заявки в статус \"Обработано\" из статуса
 493                                                      $"\"{parent.MovementStatus.Name}\" после перевода отгрузки {moveme
 494                                                      $"\"Ожидания оплаты\" {DateTime.UtcNow.ToMoscowTime().ToString("yy
 495                                                      $"{DateTime.UtcNow.ToMoscowTime().ToString("HH:mm:ss", CultureInfo
 496
 497                                _authUserService.SwitchToServiceUser();
 498                                await SetNextStatus(movement.Parent.Id,
 499                                    MovementStatusKeys.finished, commentLocal);
 500                                _authUserService.RevertUser();
 501                            }
 502                        }
 503                        catch (Exception e)
 504                        {
 505                            _logger.LogError($"Ошибка перевода родительской заявки в следущий статус перед отправкой на 
 506                            _logger.LogError(e.Message + " \n " + e.StackTrace);
 507                            throw;
 508                        }
 509
 510                        //отправляем смс, что ожидаем оплаты
 511                        var movementUser = (await _userService.GetContragentOwner(movement.Customer.Id))?.FirstName;
 512                        await _smsWorker.SendSmsByPattern("060", movement.Customer.PhoneNumber, movement.Customer.Extern
 513                                    {
 514                                        { "FIO", movementUser  },
 515                                        { "X", movement.DocumentNumber },
 516                                        { "Y", movement.PrepaimentSum.ToString("F2") },
 517                                        { "LINK", _commonSettings.BaseFrontUrl + "/shipments/" + movement.Id }
 518                                    });
 519                        await _emailWorker.SendEmailByPattern(1241, "Заявка-Обработана", movement.Customer.Email, moveme
 520                                    {
 521                                        { "name", movementUser },
 522                                        { "ordernum", movement.DocumentNumber },
 523                                        { "summ", movement.PrepaimentSum.ToString("F2") },
 524                                        { "accounturl", CreateLink("счета", _commonSettings.BaseFrontUrl + "/shipments/"
 525                                    });
 526                        break;
 527                    }
 528                case (long)MovementsStatus.Picking:
 529                    {
 530                        try
 531                        {
 180532                            foreach (var good in await _goodService.GetGoods(movement.Items.Select(d => d.GoodId).ToList
 533                            {
 60534                                movement.Items.FirstOrDefault(d => d.GoodId == good.Id).Good = good;
 535                            }
 536                            await createRestSlim.WaitAsync();
 537                            var holds = await _restHoldService.GetRestHolds(movement.Id);
 538                            var history =
 539                                await _movementStatusJournalService.GetMovementStatusJournal(movement.Id);
 540                            // Возможен откат из статуса Готово к выдаче тогда остатки уже будут зарезервированы, состав
 541                            // резервируем только в том случае если этого не было
 34542                            if (holds == null || holds.Count == 0 || !history.Select(d => d.StatusCurrent.Id).Contains((
 543                            {
 58544                                var goods = movement.Items.Select(d => d.Good.Id).ToList();
 545                                var rests = await _restService.GetRests(movement.Sender.Id, goods);
 546                                if (rests != null && rests.Count > 0)
 547                                {
 548                                    var restHold = new List<RestHold>();
 549                                    rests.ForEach(rest =>
 58550                                    {
 87551                                        var quantity = movement.Items.FirstOrDefault(d => d.Good.Id == rest.GoodId)?.Qua
 58552                                                       0;
 58553                                        rest.Quantity -= quantity;
 58554                                        restHold.Add(new RestHold
 58555                                        {
 58556                                            Movement = movement,
 58557                                            Good = rest.Good,
 58558                                            Quantity = quantity
 58559                                        });
 58560                                    });
 561                                    await _restService.UpdateRests(rests);
 562                                    await _restHoldService.CreateHold(restHold);
 563                                }
 564                            }
 565                        }
 566                        catch (Exception e)
 567                        {
 568                            _logger.LogError($"Ошибка изменения остатков после оплаты заявки {movement.Id} {e.StackTrace
 569                            _logger.LogError(e.Message + " \n " + e.StackTrace);
 570                        }
 571                        finally
 572                        {
 573                            createRestSlim.Release();
 574                        }
 575                        //отправляем смс, что заказ собирается
 576                        var movementUser = (await _userService.GetContragentOwner(movement.Customer.Id))?.FirstName;
 577                        var balance = await _walletPaymentService.GetWalletFreeBalance(movement.Customer.Id, 0) ?? new W
 578                        await _smsWorker.SendSmsByPattern("080", movement.Customer.PhoneNumber, movement.Customer.Extern
 579                                    {
 580                                        { "FIO", movementUser },
 581                                        { "X", movement.DocumentNumber },
 582                                        { "Y", balance.BalanceFree.ToString("F2") }
 583                                    });
 584                        await _emailWorker.SendEmailByPattern(1243, "Отгрузка-Сборка", movement.Customer.Email, movement
 585                                    {
 586                                        { "name", movementUser },
 587                                        { "ordernum", movement.DocumentNumber },
 588                                    });
 589                        break;
 590                    }
 591                case (long)MovementsStatus.ReadyForShipment:
 592                    {
 593                        try
 594                        {
 595                            await releaseRestSlim.WaitAsync();
 596                            var transaction = await _walletPaymentService.GetTransaction(movement.Id,
 597                                TransactionStatusKind.Holded);
 598                            if (transaction != null && transaction.Sum != movement.PrepaimentSum)
 599                            {
 600                                try
 601                                {
 602                                    await _walletPaymentService.ChangeDealTransaction(movement.Id);
 603                                }
 604                                catch (Exception e)
 605                                {
 606                                    _logger.LogError($"Ошибка изменения суммы транзакции документа {movement.Id} {e.Stac
 607                                    _logger.LogError(e.Message + " \n " + e.StackTrace);
 608                                    throw;
 609                                }
 610                                try
 611                                {
 612                                    var holds = await _restHoldService.GetRestHolds(movement.Id);
 613                                    if (holds != null && holds.Count > 0)
 614                                    {
 615                                        var changedHolds = new List<RestHold>();
 616                                        var createdHolds = new List<RestHold>();
 617                                        var changedRest = new List<Rest>();
 0618                                        var goods = movement.Items.Select(d => d.GoodId).Union(holds.Select(d => d.GoodI
 619                                            .ToList();
 620                                        var rests = await _restService.GetRests(movement.Sender.Id, goods,
 621                                            withDeleted: true);
 622                                        goods.ForEach(good =>
 0623                                        {
 0624                                            var hold = holds.FirstOrDefault(d => d.GoodId == good);
 625                                            // сперва проверим что есть неудаленный остаток
 0626                                            var rest = rests.FirstOrDefault(d => !d.IsDeleted && d.GoodId == good) ??
 0627                                                           rests.FirstOrDefault(d => d.GoodId == good);
 0628                                            var item = movement.Items.FirstOrDefault(d => d.GoodId == good);
 0629                                            if (hold == null && item != null)
 0630                                            {
 0631                                                hold = new RestHold
 0632                                                {
 0633                                                    Movement = movement,
 0634                                                    GoodId = item.GoodId,
 0635                                                    Quantity = item.Quantity
 0636                                                };
 0637                                                createdHolds.Add(hold);
 0638                                                rest.Quantity -= item.Quantity;
 0639                                                rest.IsDeleted = false;
 0640                                                createdHolds.Add(hold);
 0641                                                changedRest.Add(rest);
 0642                                                return;
 643                                            }
 644
 0645                                            if (hold != null && item == null)
 0646                                            {
 0647                                                hold.IsDeleted = true;
 0648                                                rest.Quantity += hold.Quantity;
 0649                                                rest.IsDeleted = false;
 0650                                                changedRest.Add(rest);
 0651                                                changedHolds.Add(hold);
 0652                                                return;
 653                                            }
 654
 0655                                            if (item.Quantity != hold.Quantity)
 0656                                            {
 0657                                                rest.Quantity -= item.Quantity - hold.Quantity;
 0658                                                rest.IsDeleted = false;
 0659                                                hold.Quantity = item.Quantity;
 0660                                                changedRest.Add(rest);
 0661                                                changedHolds.Add(hold);
 0662                                            }
 0663                                        });
 664                                    }
 665                                }
 666                                catch (Exception e)
 667                                {
 668                                    _logger.LogError($"Ошибка изменения резерва товаров в документе {movement.Id} {e.Sta
 669                                    _logger.LogError(e.Message + " \n " + e.StackTrace);
 670                                    throw;
 671                                }
 672                            }
 673
 674                        }
 675                        catch (Exception e)
 676                        {
 677                            _logger.LogError(e.Message + " \n " + e.StackTrace);
 678                            throw;
 679                        }
 680                        finally
 681                        {
 682                            releaseRestSlim.Release();
 683                        }
 684                        //отправляем смс, что готово к выдаче, если выбран самовывоз
 685                        var movementUser = (await _userService.GetContragentOwner(movement.Customer.Id))?.FirstName;
 686                        var warehouseAddress = (await _addressService.GetActualAddressDepartment(movement.Sender.Id))?.F
 687                        var schedule = await _scheduleService.GetNextWorkDay(movement.Sender.Id) ?? new WorkSchedule();
 688                        if (movement.DeliveryType.Id == (long)DeliveryTypeKind.Pickup)
 689                        {
 0690                            foreach (var good in await _goodService.GetGoods(movement.Items.Select(d => d.GoodId).ToList
 691                            {
 0692                                movement.Items.FirstOrDefault(d => d.GoodId == good.Id).Good = good;
 693                            }
 694                            await _smsWorker.SendSmsByPattern("090", movement.Customer.PhoneNumber, movement.Customer.Ex
 695                                    {
 696                                        { "FIO", movementUser },
 697                                        { "X", movement.DocumentNumber },
 698                                        { "PERIOD", $"{schedule.BeginTime.Date.ToShortDateString()} с {schedule.BeginTim
 699                                        { "ADDRESS", warehouseAddress}
 700                                    });
 701                            await _emailWorker.SendEmailByPattern(1244, "Отгрузка-Готово к выдаче", movement.Customer.Em
 702                                    {
 703                                        { "name", movementUser },
 704                                        { "ordernum", movement.DocumentNumber },
 705                                        { "adress_warehouse", warehouseAddress},
 706                                        { "day", schedule.BeginTime.Date.ToShortDateString()},
 707                                        { "time_start", schedule.BeginTime.ToMoscowTime().ToShortTimeString() },
 708                                        { "time_end", schedule.EndTime.ToMoscowTime().ToShortTimeString() + " по МСК" },
 709                                        { "comentariy", CreateLink("здесь", _commonSettings.BaseFrontUrl + "/shipments/"
 710                                    });
 711                        }
 712                        break;
 713                    }
 714                case (long)MovementsStatus.SupplierReject:
 715                    {
 716                        try
 717                        {
 718                            await supplierRejectSlim.WaitAsync();
 719                            var transactionsList =
 720                                await _movementStatusJournalService.GetMovementStatusJournal(movement.Id);
 721                            if (transactionsList.All(d =>
 28722                                d.StatusCurrent.Id != (long)MovementsStatus.Picking))
 723                            {
 724                                try
 725                                {
 726                                    // Отгрузка не дошла до стадии сборки - значит не было оплаты
 727                                    var parent = await _movementService.GetLightMovement(movement.ParentId.Value);
 728                                    if (parent != null && parent.MovementStatus.Id != (long) MovementsStatus.Reject)
 729                                    {
 730                                        _authUserService.SwitchToServiceUser();
 731                                        await SetNextStatus(movement.Parent.Id, MovementStatusKeys.autoReject,
 732                                            $"Автоматический перевод в статус \"Отказ\" из статуса \"{movement.Parent.Mo
 733                                            $"{DateTime.UtcNow.ToMoscowTime().ToString("yyyy-MM-dd")} в " +
 734                                            $"{DateTime.UtcNow.ToMoscowTime().ToString("HH:mm:ss", CultureInfo.Invariant
 735                                            $"так как поставщик отказал в отгрузке с комментарием - \"{comment}\"");
 736                                        _authUserService.RevertUser();
 737                                    }
 738                                }
 739                                catch (Exception e)
 740                                {
 741                                    _logger.LogError(
 742                                        $"Ошибка перевода родительской заявки в отказ при отказе от отгрузки {movement.I
 743                                    _logger.LogError(e.Message + " \n" + e.StackTrace);
 744                                    throw;
 745                                }
 746
 747                                try
 748                                {
 749                                    //Поставщик сразу отказал, отправляем смс
 750                                    var movementUser = (await _userService.GetContragentOwner(movement.Customer.Id))?.Fi
 751                                    await _smsWorker.SendSmsByPattern("030", movement.Customer.PhoneNumber, movement.Cus
 752                                    {
 753                                        { "FIO", movementUser  },
 754                                        { "X", movement.Parent.DocumentNumber },
 755                                        { "REASON", $"\"{comment}\""}
 756                                    });
 757                                    await _emailWorker.SendEmailByPattern(1240, "Заявка-Отказ", movement.Customer.Email,
 758                                    {
 759                                        { "name", movementUser },
 760                                        { "ordernum", movement.Parent.DocumentNumber },
 761                                        { "feed_back_from_distrib", comment },
 762                                        { "web_catalog", CreateLink("заказ", _commonSettings.BaseFrontUrl + "/showcase/c
 763                                    });
 764                                }
 765                                catch (Exception e)
 766                                {
 767                                    throw;
 768                                }
 769                                // выходим
 770                                return;
 771                            }
 772
 773                            if (await _walletPaymentService.CheckNeedFallbackMoney(movement.Id))
 774                            {
 775                                await _walletPaymentService.CancelTransaction(movement.Id);
 776                                var transaction =
 777                                    await _walletPaymentService.GetTransaction(movement.Id,
 778                                        TransactionStatusKind.Canceled);
 779                                if (transaction == null)
 780                                    throw new Exception($"После отмены оплаты не найдена транзакция в статусе Отменено")
 781                            }
 782
 783                            await ReturnRestWarehouse(movement);
 784                        }
 785                        catch (Exception e)
 786                        {
 787                            _logger.LogError($"Ошибка перевода заявки в отказ поставщиком {movement.Id} {e.StackTrace}")
 788                            throw;
 789                        }
 790                        finally
 791                        {
 792                            supplierRejectSlim.Release();
 793                        }
 794                        break;
 795                    }
 796                case (long)MovementsStatus.ShipmentReject:
 797                case (long)MovementsStatus.ClaimAccepted:
 798                    {
 799                        try
 800                        {
 801                            await claimAcceptedSlim.WaitAsync();
 802                            try
 803                            {
 804                                if (await _walletPaymentService.CheckNeedFallbackMoney(movement.Id))
 805                                {
 806                                    await _walletPaymentService.CancelTransaction(movement.Id);
 807                                    var transaction =
 808                                        await _walletPaymentService.GetTransaction(movement.Id,
 809                                            TransactionStatusKind.Canceled);
 810                                    if (transaction == null)
 811                                        throw new Exception(
 812                                            $"После отмены оплаты не найдена транзакция в статусе Отменено");
 813                                }
 814                            }
 815                            catch (Exception e)
 816                            {
 817                                _logger.LogError($"Ошибка возврата денежных средств покупателю в документе {movement.Id}
 818                                _logger.LogError(e.Message + " \n " + e.StackTrace);
 819                                throw;
 820                            }
 821
 822                            await ReturnRestWarehouse(movement);
 823                        }
 824                        catch (Exception e)
 825                        {
 826                            _logger.LogError(e.Message + " \n " + e.StackTrace);
 827                            throw;
 828                        }
 829                        finally
 830                        {
 831                            claimAcceptedSlim.Release();
 832                        }
 833
 834                        break;
 835                    }
 836                case (long)MovementsStatus.Received:
 837                case (long)MovementsStatus.ClaimDeclined:
 838                    {
 839                        try
 840                        {
 841                            await claimDeclinedSlim.WaitAsync();
 842                            try
 843                            {
 844                                if (await _walletPaymentService.CheckNeedFallbackMoney(movement.Id))
 845                                {
 846                                    await _walletPaymentService.ConfirmTransaction(movement.Id);
 847
 848                                    var transaction =
 849                                        await _walletPaymentService.GetTransaction(movement.Id,
 850                                            TransactionStatusKind.Confirmed);
 851                                    if (transaction == null)
 852                                        throw new Exception($"После оплаты не найдена транзакция в статусе Подтерждено")
 853                                }
 854                            }
 855                            catch (Exception e)
 856                            {
 857                                _logger.LogError($"Ошибка отправки денежных средств продавцу в документе {movement.Id} {
 858                                _logger.LogError(e.Message + " \n " + e.StackTrace);
 859                                throw;
 860                            }
 861
 862                            // Удаляем резерв, он уже не нужен
 863                            try
 864                            {
 865                                var restHolds = await _restHoldService.GetRestHolds(movement.Id);
 866                                if (restHolds != null && restHolds.Count > 0)
 867                                {
 868                                    await _restHoldService.DeleteHolds(restHolds);
 869                                }
 870                            }
 871                            catch (Exception ex)
 872                            {
 873                                _logger.LogError($"Ошибка удаления резерева в документе {movement.Id} {ex.StackTrace}");
 874                                _logger.LogError(ex.Message + " \n " + ex.StackTrace);
 875                                throw;
 876                            }
 877                        }
 878                        catch (Exception e)
 879                        {
 880                            _logger.LogError(e.Message + " \n " + e.StackTrace);
 881                            throw;
 882                        }
 883                        finally
 884                        {
 885                            claimDeclinedSlim.Release(1);
 886                        }
 887
 888                        //Отправляем смс о принятии
 889                        var movementUser = (await _userService.GetContragentOwner(movement.Customer.Id))?.FirstName;
 890                        if (status.Id == (long)MovementsStatus.Received)
 891                        {
 6892                            foreach (var good in await _goodService.GetGoods(movement.Items.Select(d => d.GoodId).ToList
 893                            {
 2894                                movement.Items.FirstOrDefault(d => d.GoodId == good.Id).Good = good;
 895                            }
 896                            await _smsWorker.SendSmsByPattern("100", movement.Customer.PhoneNumber, movement.Customer.Ex
 897                                    {
 898                                        { "FIO", movementUser },
 899                                        { "LINK", _commonSettings.BaseFrontUrl + "/shipments/" + movement.Id }
 900                                    });
 901                            await _emailWorker.SendEmailByPattern(1248, "Отгрузка-Принято", movement.Customer.Email, mov
 902                                    {
 903                                        { "name", movementUser },
 904                                        { "comentariy", CreateLink("здесь", _commonSettings.BaseFrontUrl + "/shipments/"
 905                                    });
 906                        }
 907
 908                            break;
 909                    }
 910                case (long)MovementsStatus.CustomerReject:
 911                {
 912                    try
 913                    {
 914                        await customerRejectdSlim.WaitAsync();
 915                        var transactionsList =
 916                            await _movementStatusJournalService.GetMovementStatusJournal(movement.Id);
 917                        if (transactionsList.Any(d =>
 35918                            d.StatusCurrent.Id == (long) MovementsStatus.Shipped))
 919                        {
 920                            movement.Notes.Add(new MovementNote
 921                            {
 922                                Body = $"Автоматический перевод в статус \"Разбор претензии\" из " +
 923                                       $"статуса \"{movement.MovementStatus.Name}\" после отказа от отгрузки" +
 924                                       $"{DateTime.UtcNow.ToMoscowTime().ToString("yyyy-MM-dd")} в " +
 925                                       $"{DateTime.UtcNow.ToMoscowTime().ToString("HH:mm:ss", CultureInfo.InvariantCultu
 926                            });
 927                            status = await GetNextStatus((long) MovementsStatus.CustomerReject,
 928                                MovementStatusKeys.claimProcess, movement.Sender.Id);
 929                        }
 930                        else
 931                        {
 932                            try
 933                            {
 934                                if (await _walletPaymentService.CheckNeedFallbackMoney(movement.Id))
 935                                {
 936                                    await _walletPaymentService.CancelTransaction(movement.Id);
 937                                    var transaction =
 938                                        await _walletPaymentService.GetTransaction(movement.Id,
 939                                            TransactionStatusKind.Canceled);
 940                                    if (transaction == null)
 941                                        throw new Exception(
 942                                            $"После отмены оплаты не найдена транзакция в статусе Отменено");
 943                                }
 944                            }
 945                            catch (Exception e)
 946                            {
 947                                _logger.LogError($"Ошибка возврата денежных средств покупателю в документе {movement.Id}
 948                                _logger.LogError(e.Message + " \n " + e.StackTrace);
 949                                throw;
 950                            }
 951
 952                            await ReturnRestWarehouse(movement);
 953                        }
 954                    }
 955                    catch (Exception e)
 956                    {
 957                        _logger.LogError(
 958                            $"Ошибка перевода отгрузки в следующий статус после отказа от отгрузки {e.StackTrace}");
 959                        _logger.LogError(e.Message + " \n " + e.StackTrace);
 960                        throw;
 961                    }
 962                    finally
 963                    {
 964                        customerRejectdSlim.Release();
 965                    }
 966                    break;
 967                }
 968                case (long) MovementsStatus.Transit:
 969                {
 970                    var cluster = await _clusterService.GetCluster(movement.Receiver.ClusterId.Value)
 971                                  ?? throw new SvetaException($"Кластер {movement.Receiver.ClusterId.Value.ToString()} н
 6972                    if (!cluster.ClusterDeliveryTypes.Select(d => d.DeliveryTypeId)
 973                        .Contains((long) DeliveryTypeKind.Delivery))
 974                    {
 975                        throw new SvetaException($"Невозможно перевести в статус 'В пути'. ТПЗ 'Доставка' " +
 976                                                 $"отсутствует в кластере '{movement.Receiver.Cluster.Name}'.", (int)Err
 977                    }
 978
 979                    if (movement.DeliveryType.Id != (long) DeliveryTypeKind.Delivery)
 980                    {
 981                        throw new SvetaException($"Невозможно перевести в статус 'В пути'. В документе выбран другой спо
 982                    }
 983                    break;
 984                }
 985            };
 986            await UpdateStatusMovement(movement, null, status, text).ConfigureAwait(false);
 987        }
 988
 989        /// <summary>
 990        /// Возврат остатков для склада
 991        /// </summary>
 992        /// <param name="movement">Документ</param>
 993        /// <returns></returns>
 994        private async Task ReturnRestWarehouse(Movement movement)
 995        {
 996            try
 997            {
 998                var restHolds = await _restHoldService.GetRestHolds(movement.Id);
 999                if (restHolds != null && restHolds.Count > 0)
 1000                {
 201001                    var goods = movement.Items.Select(d => d.GoodId).ToList();
 1002                    var rests = await _restService.GetRests(movement.Sender.Id, goods, withDeleted: true);
 1003                    if (rests != null && rests.Count > 0)
 1004                        foreach (var rest in rests)
 1005                        {
 201006                            var quantity = restHolds.FirstOrDefault(d => d.GoodId == rest.GoodId)?.Quantity ?? 0;
 1007                            rest.Quantity += quantity;
 1008                            rest.IsDeleted = false;
 1009                        }
 1010
 1011                    await _restService.UpdateRests(rests);
 1012                    await _restHoldService.DeleteHolds(restHolds);
 1013                }
 1014            }
 1015            catch (Exception ex)
 1016            {
 1017                _logger.LogError($"Ошибка возврата резерева в документе {movement.Id}");
 1018                _logger.LogError(ex.Message);
 1019                throw;
 1020            }
 1021        }
 1022
 1023        /// <summary>
 1024        /// Метод обновления статуса документа
 1025        /// </summary>
 1026        /// <param name="movement">Обновляемый документ</param>
 1027        /// <param name="status">Идентификатор статуса - либо этот параметр, либо объект</param>
 1028        /// <param name="movementStatus">объект статуса - либо этот параметр, либо идентификатор</param>
 1029        /// <returns></returns>
 1030        public async Task UpdateStatusMovement(Movement movement, MovementsStatus? status = null,
 1031            MovementStatus movementStatus = null, string text = default)
 2461032        {
 2461033            if (status == null && movementStatus == null)
 01034                throw new ArgumentNullException($"{nameof(status)} и {nameof(movementStatus)} не могут быть равны null о
 2461035            MovementStatus mStatus = movementStatus ?? await _movementTypeStatusService.GetMovementStatus((long)status);
 1036
 2461037            movement.MovementStatus = mStatus;
 1038
 2461039            await _movementService.UpdateMovement(movement);
 2461040            await _movementStatusJournalService.Create(new MovementStatusJournal
 2461041            {
 2461042                Movement = movement,
 2461043                StatusCurrent = mStatus
 2461044            });
 2461045            var moscowTime = DateTime.UtcNow.ToMoscowTime();
 2461046            string body = $"Документ {MovementHelpers.CreateLink(movement, _commonSettings)} переведен в статус \"{movem
 2461047                          $" {moscowTime.ToString("dd-MM-yyyy")} " +
 2461048                          $"в {moscowTime.ToString("HH:mm:ss", CultureInfo.InvariantCulture)} по московскому времени.";
 2461049            if (text != default)
 391050                body = $"{body} {text}";
 2461051            await _notificationWorker.CreateNotification(movement, $"Обновление статуса документа ({movement.MovementSta
 2461052                body);
 2461053        }
 1054
 1221055        private string CreateLink(string linkName, string hyperLink) => $"<a class='notification-link' href='{hyperLink}
 1056    }
 1057
 1058    /// <summary>
 1059    /// Список ключей для перехода статусов
 1060    /// </summary>
 1061    public static class MovementStatusKeys
 1062    {
 1063        public static string supplierRejectShipment = "SupplierReject";
 1064        public static string rejectShipment = "CustomerReject";
 1065        public static string correctionShipment = "Correction";
 1066        public static string claimDeclineShipment = "ClaimDecline";
 1067        public static string claimAcceptShipment = "ClaimAccept";
 1068        public static string autoReject = "Auto";
 1069        public static string paymentReserve = "PaymentReserve";
 1070        public static string payment = "Payment";
 1071        public static string ship = "Ship";
 1072        public static string readyToShip = "ReadyToShip";
 1073        public static string accept = "Accept";
 1074        public static string confirm = "Confirm";
 1075        public static string closed = "Closed";
 1076        public static string claimProcess = "ClaimProcess";
 1077        public static string finished = "Finished";
 1078        public static string rejectOrder = "RejectOrder";
 1079        public static string send = "Send";
 1080        public static string deliver = "Away";
 1081        public static string rejectPayment = "RejectPayment";
 1082        public static string revertPayment = "RevertPayment";
 1083        public static string revertReadyToShip = "RevertReadyToShip";
 1084        public static string revertDelivery = "RevertDelivery";
 1085        public static string revertShip = "RevertShip";
 1086        public static string revertClaim = "RevertClaim";
 1087        public static string revertFinished = "RevertFinished";
 1088    }
 1089}