< Summary

Information
Class: OzonOrdersWeb.Areas.PartnerOffice.Controllers.SaveRulesBody
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/PartnerOffice/Controllers/PartnersController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 221
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Rules()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/PartnerOffice/Controllers/PartnersController.cs

#LineLine coverage
 1using System.Security.Claims;
 2using Microsoft.AspNetCore.Authorization;
 3using Microsoft.AspNetCore.Mvc;
 4using Microsoft.EntityFrameworkCore;
 5using OzonDomains;
 6using OzonDomains.Models.Authorization.Enums;
 7using OzonDomains.Models.Partners;
 8using OzonOrdersWeb.Areas.PartnerOffice.Models;
 9using OzonOrdersWeb.Authorization;
 10using OzonOrdersWeb.WebServices.Grid;
 11using OzonOrdersWeb.WebServices.Journals;
 12using OzonOrdersWeb.WebServices.Partners;
 13using OzonRepositories.Context;
 14using Services.Authorization;
 15using Services.PartnerServices;
 16
 17namespace OzonOrdersWeb.Areas.PartnerOffice.Controllers;
 18
 19/// <summary>
 20/// Кабинет владельца: создание и настройка партнёров. Всё управление живёт в Studio2,
 21/// сервис PartnerOffice нужен только самим партнёрам.
 22/// </summary>
 23[Area("PartnerOffice")]
 24[Authorize(Roles = AuthorizationConstants.SuperAdminRoleName + ",Admin")]
 25[Route("PartnerOffice/[controller]/[action]/{id?}")]
 26public sealed class PartnersController(
 27    IPartnerManagementService management,
 28    IPartnerAuthService auth,
 29    IPartnerOrdersService orders,
 30    ApplicationDbContext db) : Controller
 31{
 32    private string OwnerId => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "owner";
 33
 34    [HttpGet]
 35    [HttpGet("~/PartnerOffice/Partners")]
 36    public async Task<IActionResult> Index(CancellationToken ct) =>
 37        View(PartnerGridRows.Build(await management.ListAsync(ct)));
 38
 39    [HttpGet]
 40    public IActionResult Create() => View(new PartnerCreateForm());
 41
 42    [HttpPost, ValidateAntiForgeryToken]
 43    public async Task<IActionResult> Create(PartnerCreateForm form, CancellationToken ct)
 44    {
 45        var result = await management.CreateAsync(
 46            new CreatePartnerRequest(form.Name, form.Email, form.GoogleEmail), OwnerId, ct);
 47        if (!result.Ok)
 48        {
 49            ModelState.AddModelError(string.Empty, result.Error!);
 50            return View(form);
 51        }
 52
 53        var invite = await auth.SendInviteAsync(result.Value!.Id, OwnerId, ct);
 54        if (invite.Ok)
 55            TempData["Success"] = $"Партнёр создан, приглашение отправлено на {result.Value.Email}. Теперь задайте прави
 56        else
 57            TempData["Error"] = $"Партнёр создан, но приглашение не ушло: {invite.Error}";
 58        return RedirectToAction(nameof(Edit), new { id = result.Value.Id });
 59    }
 60
 61    [HttpGet]
 62    public async Task<IActionResult> Edit(long id, CancellationToken ct)
 63    {
 64        var partner = await management.GetAsync(id, ct);
 65        if (partner is null) return NotFound();
 66        return View(new PartnerEditViewModel(partner, await LoadLookupsAsync(ct)));
 67    }
 68
 69    [HttpPost, ValidateAntiForgeryToken]
 70    public async Task<IActionResult> UpdateMain(long id, PartnerMainForm form, CancellationToken ct)
 71    {
 72        var channels = PartnerNotificationChannels.None;
 73        if (form.NotifyEmail) channels |= PartnerNotificationChannels.Email;
 74        if (form.NotifyTelegram) channels |= PartnerNotificationChannels.Telegram;
 75
 76        var result = await management.UpdateAsync(id,
 77            new UpdatePartnerRequest(form.Name, form.Email, form.GoogleEmail, form.IsActive, channels, form.Notification
 78            OwnerId, ct);
 79        return Back(id, result.Ok ? "Сохранено." : null, result.Error);
 80    }
 81
 82    /// <summary>Повторно отправляет приглашение со ссылкой и новым кодом. Прежнее приглашение перестаёт действовать.</s
 83    [HttpPost, ValidateAntiForgeryToken]
 84    public async Task<IActionResult> SendInvite(long id, CancellationToken ct)
 85    {
 86        var result = await auth.SendInviteAsync(id, OwnerId, ct);
 87        return Back(id, result.Ok ? "Приглашение отправлено." : null, result.Error);
 88    }
 89
 90    [HttpPost, ValidateAntiForgeryToken]
 91    public async Task<IActionResult> SetPermissions(long id, PartnerPermissionsForm form, CancellationToken ct)
 92    {
 93        var codes = new List<PartnerPermissionCode>();
 94        if (form.LabelPrint) codes.Add(PartnerPermissionCode.LabelPrint);
 95        if (form.Assemble) codes.Add(PartnerPermissionCode.Assemble);
 96        if (form.TrackNumber) codes.Add(PartnerPermissionCode.TrackNumber);
 97
 98        var result = await management.SetPermissionsAsync(id, codes, OwnerId, ct);
 99        return Back(id, result.Ok ? "Возможности сохранены." : null, result.Error);
 100    }
 101
 102    /// <summary>Сохраняет правила целиком. Принимает JSON из редактора правил.</summary>
 103    [HttpPost, ValidateAntiForgeryToken]
 104    public async Task<IActionResult> SaveRules(long id, [FromBody] SaveRulesBody body, CancellationToken ct)
 105    {
 106        var result = await management.SaveRulesAsync(id, body.Rules ?? [], OwnerId, ct);
 107        return result.Ok
 108            ? Json(new { ok = true, rules = result.Value!.Rules })
 109            : BadRequest(new { ok = false, error = result.Error });
 110    }
 111
 112    /// <summary>Сколько заказов подойдёт под правила прямо сейчас и id первых для ссылки на таблицу заказов. Ничего не 
 113    [HttpPost, ValidateAntiForgeryToken]
 114    public async Task<IActionResult> PreviewRules([FromBody] SaveRulesBody body, CancellationToken ct)
 115    {
 116        var rules = body.Rules ?? [];
 117        var error = PartnerManagementService.ValidateRules(rules);
 118        if (error is not null) return BadRequest(new { ok = false, error });
 119        var preview = await orders.PreviewAsync(rules, ct: ct);
 120        return Json(new { ok = true, count = preview.Count, ids = preview.Ids, truncated = preview.Truncated, limit = pr
 121    }
 122
 123    [HttpPost, ValidateAntiForgeryToken]
 124    public async Task<IActionResult> Delete(long id, CancellationToken ct)
 125    {
 126        var result = await management.DeleteAsync(id, OwnerId, ct);
 127        if (!result.Ok)
 128        {
 129            TempData["Error"] = result.Error;
 130            return RedirectToAction(nameof(Index));
 131        }
 132
 133        TempData["Success"] = "Партнёр удалён.";
 134        return RedirectToAction(nameof(Index));
 135    }
 136
 137    private const string AuditPageCode = "PartnerOffice.Partners.Audit";
 138
 139    /// <summary>Каркас страницы журнала: строки грид подгружает блоками через <see cref="AuditGrid"/>.</summary>
 140    [HttpGet]
 141    public async Task<IActionResult> Audit(long? partnerId, CancellationToken ct)
 142    {
 143        var partners = await management.ListAsync(ct);
 144        ViewBag.PartnerId = partnerId;
 145        ViewBag.PartnerName = partnerId is { } id ? partners.FirstOrDefault(p => p.Id == id)?.Name : null;
 146        ViewBag.TotalCount = await db.PartnerAuditLogs.CountAsync(ct);
 147        return View();
 148    }
 149
 150    [HttpPost("~/PartnerOffice/Partners/Audit/grid")]
 151    [ValidateAntiForgeryToken]
 152    [PageAuthorize(AuditPageCode, AccessType.View)]
 153    public async Task<IActionResult> AuditGrid([FromBody] ServerGridRequest? request, CancellationToken ct)
 154    {
 155        var error = ServerGridQuery.Validate(request);
 156        if (error != null) return BadRequest(new { success = false, error });
 157
 158        var def = JournalGridDefinitions.PartnerAudit;
 159        var filtered = ServerGridQuery.ApplyFilters(AuditSource(request!), def, request!);
 160        var total = await filtered.CountAsync(ct);
 161        var start = Math.Max(0, request!.StartRow);
 162        var take = Math.Clamp(request.EndRow - start, 1, ServerGridQuery.MaxBlockSize);
 163        var page = start >= total
 164            ? []
 165            : await ServerGridQuery.ApplySort(filtered, def, request.SortModel).Skip(start).Take(take).ToListAsync(ct);
 166
 167        var ids = page.Where(r => r.PartnerId != null).Select(r => r.PartnerId!.Value).Distinct().ToList();
 168        var names = await db.Partners.AsNoTracking().Where(p => ids.Contains(p.Id)).ToDictionaryAsync(p => p.Id, p => p.
 169        var rows = PartnerGridRows.BuildAudit(page, names);
 170        var lastRow = start + rows.Count >= total ? total : -1;
 171        return Ok(new { success = true, rows, totalCount = total, lastRow });
 172    }
 173
 174    [HttpPost("~/PartnerOffice/Partners/Audit/grid/facets")]
 175    [ValidateAntiForgeryToken]
 176    [PageAuthorize(AuditPageCode, AccessType.View)]
 177    public async Task<IActionResult> AuditGridFacets([FromBody] ServerGridRequest? request, CancellationToken ct)
 178    {
 179        if (request is null || string.IsNullOrWhiteSpace(request.ColId)) return BadRequest(new { success = false, error 
 180        request.StartRow = 0;
 181        request.EndRow = 1;
 182        var error = ServerGridQuery.Validate(request);
 183        if (error != null) return BadRequest(new { success = false, error });
 184        var values = await ServerGridQuery.FacetsAsync(AuditSource(request), JournalGridDefinitions.PartnerAudit, reques
 185        return Ok(new { success = true, values });
 186    }
 187
 188    /// <summary>Журнал одного партнёра, если страница открыта с ?partnerId=.</summary>
 189    private IQueryable<PartnerAuditLog> AuditSource(ServerGridRequest request)
 190    {
 191        IQueryable<PartnerAuditLog> source = db.PartnerAuditLogs.AsNoTracking();
 192        if (request.Params.TryGetValue("partnerId", out var raw) && long.TryParse(raw, out var partnerId))
 193            source = source.Where(x => x.PartnerId == partnerId);
 194        return source;
 195    }
 196
 197    private IActionResult Back(long id, string? success, string? error)
 198    {
 199        if (success is not null) TempData["Success"] = success;
 200        if (error is not null) TempData["Error"] = error;
 201        return RedirectToAction(nameof(Edit), new { id });
 202    }
 203
 204    private async Task<RuleLookups> LoadLookupsAsync(CancellationToken ct)
 205    {
 206        var statuses = await db.AppStatuses.AsNoTracking().OrderBy(x => x.Name).ToListAsync(ct);
 207        var warehouses = await db.Warehouses.AsNoTracking().OrderBy(x => x.Name).ToListAsync(ct);
 208        var clients = await db.OzonClients.AsNoTracking().OrderBy(x => x.Name).ToListAsync(ct);
 209
 210        return new RuleLookups(
 211            statuses.Select(x => new RuleLookupOption(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToList(),
 212            warehouses.Select(x => new RuleLookupOption(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToList(),
 213            clients.Select(x => new RuleLookupOption(x.Id.ToString(), x.Name)).ToList(),
 214            [new RuleLookupOption(nameof(ClientType.OZON), "Ozon"), new RuleLookupOption(nameof(ClientType.YANDEX), "Янд
 215    }
 216}
 217
 218public sealed class SaveRulesBody
 219{
 0220    public List<PartnerRuleDto>? Rules { get; set; }
 221}

Methods/Properties

get_Rules()