< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Controllers.OrderControlController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/OrderControlController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 162
Coverable lines: 162
Total lines: 262
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 146
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Index()0%506220%
HeaderTasks()100%210%
Claim()100%210%
Close()0%620%
RemoveFromQueue()0%620%
Rules()0%156120%
RoleUsers()100%210%
Scheduler()0%2040%
SchedulerStatus()0%156120%
Scheduler()0%620%
RunNow()100%210%
EditRule()0%7280%
EditRule()0%7280%
CurrentUserId()0%620%
IsAdmin()0%620%
TaskStatusName(...)0%4260%
TaskStatusClass(...)0%2040%
.cctor()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/OrderControlController.cs

#LineLine coverage
 1using System.Security.Claims;
 2using Microsoft.AspNetCore.Authorization;
 3using Microsoft.AspNetCore.Mvc;
 4using Microsoft.EntityFrameworkCore;
 5using OzonDomains.Models;
 6using OzonDomains.Models.Authorization.Enums;
 7using OzonOrdersWeb.Authorization;
 8using OzonRepositories.Context;
 9using OzonRepositories.Context.Identity;
 10using Microsoft.AspNetCore.Identity;
 11using Hangfire;
 12using Services.BackgroundJobsServices;
 13using Services.OrderControlServices;
 14using Services.OrderDailySummaryServices;
 15
 16namespace OzonOrdersWeb.Areas.Studio2.Controllers;
 17
 18[Area("Studio2")]
 19[Authorize(Roles = "User,Admin,SuperAdmin")]
 20public sealed class OrderControlController : Controller
 21{
 22    private readonly IOrderControlService _service;
 23    private readonly ApplicationDbContext _db;
 24    private readonly UserManager<CustomIdentityUser> _users;
 25    private readonly IRecurringJobManager _recurringJobManager;
 26    private readonly IBackgroundJobClient _backgroundJobs;
 27    private readonly IOrderDailySummaryService _dailySummaryService;
 28
 029    public OrderControlController(IOrderControlService service, ApplicationDbContext db, UserManager<CustomIdentityUser>
 30    {
 031        _service = service;
 032        _db = db;
 033        _users = users;
 034        _recurringJobManager = recurringJobManager;
 035        _backgroundJobs = backgroundJobs;
 036        _dailySummaryService = dailySummaryService;
 037    }
 38
 39    [PageAuthorize("Studio2.OrderControl.Index")]
 40    public async Task<IActionResult> Index(int page = 1, bool missingStatus = false, bool onlyOverdue = false, Cancellat
 41    {
 042        var tasks = await _service.GetAvailableTasksAsync(CurrentUserId(), IsAdmin(), cancellationToken);
 043        if (onlyOverdue)
 044            tasks = tasks.Where(x => x.Status == OrderControlTaskStatus.Overdue).ToList();
 45        const int pageSize = 25;
 046        var total = tasks.Count;
 047        var totalPages = Math.Max(1, (int)Math.Ceiling(total / (double)pageSize));
 048        page = Math.Clamp(page, 1, totalPages);
 049        ViewBag.PageNumber = page;
 050        ViewBag.TotalPages = totalPages;
 051        ViewBag.TotalItems = total;
 052        ViewBag.OnlyOverdue = onlyOverdue;
 053        ViewBag.IsAdmin = IsAdmin();
 054        ViewBag.ShowMissingStatus = missingStatus && IsAdmin();
 055        if (missingStatus && IsAdmin())
 056            ViewBag.MissingSystemStatusOrders = await _dailySummaryService.GetMissingSystemStatusOrdersAsync(cancellatio
 057        return View(tasks.Skip((page - 1) * pageSize).Take(pageSize).ToList());
 058    }
 59
 60    [HttpGet]
 61    [PageAuthorize("Studio2.OrderControl.Index")]
 62    public async Task<IActionResult> HeaderTasks(int skip = 0, int take = 5, CancellationToken cancellationToken = defau
 63    {
 064        var tasks = await _service.GetAvailableTasksAsync(CurrentUserId(), IsAdmin(), cancellationToken);
 065        var total = await _service.GetAvailableTaskCountAsync(CurrentUserId(), IsAdmin(), cancellationToken);
 066        skip = Math.Max(0, skip);
 067        take = Math.Clamp(take, 1, 10);
 068        return Json(new
 069        {
 070            total,
 071            hasMore = skip + take < total,
 072            items = tasks.Skip(skip).Take(take).Select(x => new
 073            {
 074                x.Id,
 075                shipmentNumber = x.Order?.ShipmentNumber ?? x.OrderId.ToString(),
 076                ruleName = x.Rule?.Name ?? "Контроль заказа",
 077                status = TaskStatusName(x.Status),
 078                statusClass = TaskStatusClass(x.Status),
 079                dueAtUtc = x.DueAtUtc,
 080                isOverdue = x.Status == OrderControlTaskStatus.Overdue,
 081                processingDate = x.Order?.ProcessingDate,
 082                article = x.Order?.Article,
 083                productName = x.Order?.ProductName,
 084                producer = x.Order?.EtProducer?.Name ?? x.Order?.Manufacturer?.Name,
 085                supplier = x.Order?.Supplier?.Name,
 086                ozonClient = x.Order?.OzonClient?.Name,
 087                imageUrl = x.Order?.ProductInfo?.ImageUrl,
 088                clientStatus = x.Order?.ClientStatus?.Name ?? "Ozon: не указан",
 089                clientStatusColor = x.Order?.ClientStatus?.StatusColor?.ColorCode,
 090                appStatus = x.Order?.AppStatus?.Name ?? "Не указан",
 091                appStatusColor = x.Order?.AppStatus?.StatusColor?.ColorCode
 092            })
 093        });
 094    }
 95
 96    [HttpPost, ValidateAntiForgeryToken]
 97    [PageAuthorize("Studio2.OrderControl.Claim", AccessType.Edit)]
 98    public async Task<IActionResult> Claim(long id, CancellationToken cancellationToken)
 99    {
 0100        await _service.ClaimAsync(id, CurrentUserId(), IsAdmin(), cancellationToken);
 0101        return RedirectToAction(nameof(Index));
 0102    }
 103
 104    [HttpPost, ValidateAntiForgeryToken]
 105    [PageAuthorize("Studio2.OrderControl.Close", AccessType.Edit)]
 106    public async Task<IActionResult> Close(long id, string reason, CancellationToken cancellationToken)
 107    {
 0108        if (!await _service.CloseAsync(id, CurrentUserId(), reason, IsAdmin(), cancellationToken))
 0109            TempData["ErrorMessage"] = "Укажите причину закрытия или убедитесь, что задача ещё активна.";
 0110        return RedirectToAction(nameof(Index));
 0111    }
 112
 113    [HttpPost, ValidateAntiForgeryToken]
 114    [PageAuthorize("Studio2.OrderControl.Close", AccessType.Edit)]
 115    public async Task<IActionResult> RemoveFromQueue(long id, CancellationToken cancellationToken)
 116    {
 0117        var removed = await _service.RemoveFromQueueAsync(id, CurrentUserId(), IsAdmin(), cancellationToken);
 0118        var total = removed
 0119            ? await _service.GetAvailableTaskCountAsync(CurrentUserId(), IsAdmin(), cancellationToken)
 0120            : 0;
 0121        return Json(new { success = removed, total });
 0122    }
 123
 124    [Authorize(Roles = "Admin")]
 125    [PageAuthorize("Studio2.OrderControl.Rules")]
 126    public async Task<IActionResult> Rules(string? type, bool? active, CancellationToken cancellationToken)
 127    {
 0128        var query = _db.OrderControlRules.Include(x => x.TargetAppRole).AsNoTracking();
 0129        if (!string.IsNullOrWhiteSpace(type)) query = query.Where(x => x.RuleType == type);
 0130        if (active.HasValue) query = query.Where(x => x.IsActive == active.Value);
 0131        ViewBag.SelectedType = type;
 0132        ViewBag.SelectedActive = active;
 0133        ViewBag.RuleTypes = RuleTypes;
 0134        var rules = await query.OrderBy(x => x.RuleType).ThenBy(x => x.Name).ToListAsync(cancellationToken);
 0135        var roleLinks = await _db.UserAppRoles.Where(x => rules.Select(r => r.TargetAppRoleId).Contains(x.AppRoleId)).To
 0136        var ids = roleLinks.Select(x => x.UserId).Distinct().ToList();
 0137        var names = await _users.Users.Where(x => ids.Contains(x.Id)).Select(x => new { x.Id, Name = x.GoogleDisplayName
 0138        ViewBag.RoleUsers = roleLinks.GroupBy(x => x.AppRoleId).ToDictionary(x => x.Key, x => string.Join(", ", x.Select
 0139        return View(rules);
 0140    }
 141
 142    [Authorize(Roles = "Admin")]
 143    public async Task<IActionResult> RoleUsers(int roleId, CancellationToken cancellationToken)
 144    {
 0145        var ids = await _db.UserAppRoles.Where(x => x.AppRoleId == roleId).Select(x => x.UserId).ToListAsync(cancellatio
 0146        var users = await _users.Users.Where(x => ids.Contains(x.Id)).OrderBy(x => x.UserName).Select(x => x.GoogleDispl
 0147        return Json(users);
 0148    }
 149
 150    [Authorize(Roles = "Admin")]
 151    [PageAuthorize("Studio2.OrderControl.Rules")]
 152    public async Task<IActionResult> Scheduler(CancellationToken cancellationToken)
 153    {
 0154        ViewBag.Executions = await _db.OrderControlExecutions.AsNoTracking().OrderByDescending(x => x.StartedAtUtc).Take
 0155        return View(await _db.OrderControlSettings.FindAsync([1], cancellationToken) ?? new OrderControlSettings { Id = 
 0156    }
 157
 158    [Authorize(Roles = "Admin")]
 159    public async Task<IActionResult> SchedulerStatus(CancellationToken cancellationToken)
 160    {
 0161        var last = await _db.OrderControlExecutions.AsNoTracking().OrderByDescending(x => x.StartedAtUtc).FirstOrDefault
 0162        var today = DateTime.UtcNow.Date;
 0163        return Json(new
 0164        {
 0165            isRunning = last?.Status == "Running",
 0166            lastStatus = last?.Status ?? "Не запускалась",
 0167            lastStartedAtUtc = last?.StartedAtUtc,
 0168            lastCompletedAtUtc = last?.CompletedAtUtc,
 0169            lastCreatedTasks = last?.CreatedTasksCount ?? 0,
 0170            activeTasks = await _db.OrderControlTasks.CountAsync(x => OrderControlTaskStatus.Active.Contains(x.Status), 
 0171            newToday = await _db.OrderControlTasks.CountAsync(x => x.CreatedAtUtc >= today, cancellationToken)
 0172        });
 0173    }
 174
 175    [HttpPost, ValidateAntiForgeryToken, Authorize(Roles = "Admin")]
 176    [PageAuthorize("Studio2.OrderControl.Rules", AccessType.Edit)]
 177    public async Task<IActionResult> Scheduler(int intervalMinutes, CancellationToken cancellationToken)
 178    {
 0179        var settings = await _db.OrderControlSettings.FindAsync([1], cancellationToken) ?? new OrderControlSettings { Id
 0180        settings.IntervalMinutes = Math.Clamp(intervalMinutes, 5, 59); settings.UpdatedAtUtc = DateTime.UtcNow;
 0181        _db.Update(settings); await _db.SaveChangesAsync(cancellationToken);
 0182        _recurringJobManager.RemoveIfExists(OrderControlEvaluationJob.JobId);
 0183        _recurringJobManager.AddOrUpdate<OrderControlEvaluationJob>(OrderControlEvaluationJob.JobId,
 0184            x => x.ExecuteAsync(), OrderControlEvaluationJob.GetCron(settings.IntervalMinutes),
 0185            new RecurringJobOptions { TimeZone = RussianTimeZoneHelper.Zone });
 0186        TempData["SuccessMessage"] = "Периодичность сохранена, задача Hangfire перерегистрирована.";
 0187        return RedirectToAction(nameof(Scheduler));
 0188    }
 189
 190    [HttpPost, ValidateAntiForgeryToken, Authorize(Roles = "Admin")]
 191    [PageAuthorize("Studio2.OrderControl.Rules", AccessType.Edit)]
 192    public IActionResult RunNow()
 193    {
 0194        _backgroundJobs.Enqueue<OrderControlEvaluationJob>(x => x.ExecuteAsync());
 0195        TempData["SuccessMessage"] = "Проверка поставлена в очередь Hangfire. Статус обновится автоматически.";
 0196        return RedirectToAction(nameof(Scheduler));
 197    }
 198
 199    [Authorize(Roles = "Admin")]
 200    [PageAuthorize("Studio2.OrderControl.Rules", AccessType.Edit)]
 201    public async Task<IActionResult> EditRule(int? id, CancellationToken cancellationToken)
 202    {
 0203        ViewBag.Roles = await _db.AppRoles.Where(x => x.IsActive).OrderBy(x => x.DisplayName).ToListAsync(cancellationTo
 0204        ViewBag.RuleTypes = RuleTypes;
 0205        if (id is null)
 0206            return View(new OrderControlRule { IsActive = true, Priority = 2 });
 0207        var rule = await _db.OrderControlRules.FindAsync([id.Value], cancellationToken);
 0208        return rule is null ? NotFound() : View(rule);
 0209    }
 210
 211    [HttpPost, ValidateAntiForgeryToken, Authorize(Roles = "Admin")]
 212    [PageAuthorize("Studio2.OrderControl.Rules", AccessType.Edit)]
 213    public async Task<IActionResult> EditRule(OrderControlRule model, CancellationToken cancellationToken)
 214    {
 0215        if (!ModelState.IsValid) return await EditRule(model.Id == 0 ? null : model.Id, cancellationToken);
 0216        var now = DateTime.UtcNow;
 0217        if (model.Id == 0)
 218        {
 0219            model.Code = $"{model.RuleType.ToLowerInvariant()}-{Guid.NewGuid():N}";
 0220            model.CreatedAtUtc = now;
 0221            model.UpdatedAtUtc = now;
 0222            _db.OrderControlRules.Add(model);
 223        }
 224        else
 225        {
 0226            var existing = await _db.OrderControlRules.FindAsync([model.Id], cancellationToken);
 0227            if (existing is null) return NotFound();
 0228            existing.Name = model.Name;
 0229            existing.IsActive = model.IsActive;
 0230            existing.Priority = model.Priority;
 0231            existing.TargetAppRoleId = model.TargetAppRoleId;
 0232            existing.ParametersJson = model.ParametersJson;
 0233            existing.UpdatedAtUtc = now;
 234        }
 0235        await _db.SaveChangesAsync(cancellationToken);
 0236        return RedirectToAction(nameof(Rules));
 0237    }
 238
 0239    private string CurrentUserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty;
 0240    private bool IsAdmin() => User.IsInRole("Admin") || User.IsInRole("SuperAdmin");
 0241    private static string TaskStatusName(string status) => status switch
 0242    {
 0243        OrderControlTaskStatus.Open => "Новая",
 0244        OrderControlTaskStatus.InProgress => "В работе",
 0245        OrderControlTaskStatus.Overdue => "Просрочена",
 0246        _ => status
 0247    };
 0248    private static string TaskStatusClass(string status) => status switch
 0249    {
 0250        OrderControlTaskStatus.Overdue => "overdue",
 0251        OrderControlTaskStatus.InProgress => "progress",
 0252        _ => "open"
 0253    };
 0254    public static readonly IReadOnlyDictionary<string, (string Name, string Description, string BadgeClass, string Examp
 0255        new Dictionary<string, (string, string, string, string)>
 0256        {
 0257            ["NewUnprocessedToday"] = ("Новый заказ без обработки", "Контролирует заказы, поступившие сегодня без статус
 0258            ["UnprocessedOver12Hours"] = ("Без обработки более 12 часов", "Контролирует заказы без системного статуса бо
 0259            ["SupplierArrivalOverdue"] = ("Просрочен приход от поставщика", "Контролирует заказы со статусом «Заказан по
 0260            ["NoStatusMovement"] = ("Нет движения по статусу", "Контролирует заказы без изменения статуса заданное число
 0261        };
 262}