< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_Supplier_Index
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Supplier/Index.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 20
Coverable lines: 20
Total lines: 144
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 26
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ExecuteAsync()0%702260%
<ExecuteAsync()0%2040%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Supplier/Index.cshtml

#LineLine coverage
 1@using OzonOrdersWeb.Areas.Studio2.ViewModels.Grid
 2@using OzonOrdersWeb.WebServices.Directories
 3@model List<SupplierGridRow>
 4@{
 05    ViewData["Title"] = $"Поставщики ({Model.Count})";
 06    Layout = "~/Views/Shared/_Layout.cshtml";
 07    var jsonOptions = new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web);
 8}
 9
 010@await Html.PartialAsync("_BsxGridShell")
 011<script type="application/json" id="spApiSuppliersData">@Html.Raw(System.Text.Json.JsonSerializer.Serialize((List<ApiSup
 12<link rel="stylesheet" href="~/css/Studio2/Supplier/supplier-index.css" asp-append-version="true"/>
 13
 14<div class="container-fluid py-3 sp-page bsx-surface">
 15    <div class="app-section-hero app-section-hero--import d-flex flex-wrap justify-content-between align-items-start gap
 16        <div>
 17            <h1><i class="fas fa-truck-loading me-2 text-primary"></i>Поставщики</h1>
 18            <div class="subtitle">Справочник поставщиков, коэффициенты цен и связь с контрагентами 1С</div>
 19            <div class="subtitle-help mt-1">
 20                Поставщик привязывается к заказу и влияет на расчёт закупки (коэффициент и валюта), себестоимости
 21                (доставка за кг × вес товара), сроков поставки и остатков (CSV URL). ИНН нужен для проводок в 1С —
 22                в колонке «1С» показано, найден ли контрагент в базе.
 23            </div>
 24            <div class="app-section-hero__quick-links">
 25                <a asp-area="Studio2" asp-controller="OzonClients" asp-action="Index"><i class="fas fa-store"></i>Клиент
 26                <a asp-area="Studio2" asp-controller="Warehouse" asp-action="Index"><i class="fas fa-warehouse"></i>Скла
 27                <a asp-area="Studio2" asp-controller="Manufacturers" asp-action="Index"><i class="fas fa-tag"></i>Произв
 28                <a asp-area="Studio2" asp-controller="ApiSupplierRequestAudit" asp-action="Index"><i class="fas fa-clipb
 29                <a asp-area="Studio2" asp-controller="FidSupplierLink" asp-action="Index"><i class="fas fa-link"></i>Свя
 30            </div>
 31        </div>
 32        <div class="d-flex flex-wrap gap-2 align-items-center">
 33            <div class="bsx-dir-stats">
 034                <span class="bsx-dir-stat">Всего <b>@Model.Count</b></span>
 035                <span class="bsx-dir-stat">С API <b>@Model.Count(r => r.ApiProviders.Count > 0)</b></span>
 036                @if (Model.Any(r => r.Counterparty.State == "missing"))
 37                {
 038                    <span class="bsx-dir-stat bsx-dir-stat--warn" title="ИНН задан, но контрагент в 1С не найден">Нет в 
 39                }
 40            </div>
 41            <a asp-action="Create" class="btn btn-primary btn-sm"><i class="fas fa-plus me-1"></i>Создать</a>
 42        </div>
 43    </div>
 44
 45    <div class="sp-api-suppliers mb-3">
 046        @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("apiSup", "Поставщики по API", "fa-p
 047            "Прямые интеграции через API. Статус проверяется при открытии страницы; двойной клик — консоль поставщика.")
 48        <a asp-area="Studio2" asp-controller="FidSupplierLink" asp-action="Index" class="sp-api-fid-link"><i class="fas 
 49    </div>
 50
 051    @if (TempData["ErrorMessage"] is string errorMessage)
 52    {
 53        <div class="alert alert-danger alert-dismissible fade show" role="alert">
 054            <i class="fas fa-exclamation-triangle me-2"></i>@errorMessage
 55            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 56        </div>
 57    }
 58
 059    @if (TempData["SuccessMessage"] is string successMessage)
 60    {
 61        <div class="alert alert-success alert-dismissible fade show" role="alert">
 062            <i class="fas fa-check-circle me-2"></i>@successMessage
 63            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 64        </div>
 65    }
 66
 067    @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("supplier", "Список поставщиков", "fa-li
 068        "Клик по ячейке — правка, «Сохранить» в строке сохраняет всю строку. Двойной клик по названию поставщика — полна
 69</div>
 70
 71@section Scripts {
 72<script src="~/js/Studio2/Supplier/index.js" asp-append-version="true"></script>
 73<script src="~/js/Studio2/Grid/bsx-directory.js" asp-append-version="true"></script>
 74<script>
 75    (function () {
 76        const D = window.BsxDirectory;
 77        const esc = BsxGrid.esc;
 078        const editUrl = id => '@Url.Action("Edit", "Supplier", new { area = "Studio2" })/' + id;
 79        const CURRENCIES = ['RUB', 'USD', 'EUR', 'BYN'];
 80        const CURRENCY_LABELS = { NON: '—' };
 81        const money = p => p.value == null || p.value === '' ? '—' : Number(p.value).toLocaleString('ru-RU', { minimumFr
 82        const text = p => p.value || '—';
 83        const edit = (def) => Object.assign({ editable: true }, def);
 84        // Десятичные поля биндятся в культуре сервера — отправляем с её разделителем.
 085        const decimalSeparator = '@System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator';
 86        const dec = v => v == null || v === '' ? '' : String(v).replace('.', decimalSeparator);
 87
 88        function logos(list) {
 89            if (!list || !list.length) return '';
 90            return '<span class="bsx-logo-row">' + list.map(x =>
 91                `<span class="bsx-logo" title="Подключён API-поставщик: ${esc(x.name)}">${x.logoUrl ? `<img src="${esc(x
 92        }
 93
 94        D.grid({
 95            key: 'dirSupplier',
 96            prefix: 'supplier',
 097            rows: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model, jsonOptions)),
 98            rowUrl: r => editUrl(r.id),
 99            onSave: r => D.submit(editUrl(r.id), {
 100                Id: r.id, Name: r.name, CostFactor: dec(r.costFactor), CurrencyCode: r.currencyCode,
 101                WeightFactor: dec(r.weightFactor), WeightFactorCurrencyCode: r.weightFactorCurrencyCode,
 102                CsvUrl: r.csvUrl ?? '', Site: r.site ?? '', AdditionalTerm: r.additionalTerm ?? '',
 103                ExpectedDeliveryHours: r.expectedDeliveryHours ?? '', INNCode: r.innCode ?? '',
 104                IsVatApplicable: r.isVatApplicable, IsReSeller: r.isReSeller
 105            }),
 106            columnDefs: [
 107                edit({ field: 'name', headerName: 'Поставщик', pinned: 'left', width: 260, sort: 'asc',
 108                  cellRenderer: p => `<span class="text-truncate">${esc(p.value || '—')}</span>${logos(p.data.apiProvide
 109                D.setCol({ colId: 'api', headerName: 'API', headerTooltip: 'Подключён ли API-поставщик', width: 100, hid
 110                  valueGetter: p => p.data.apiProviders.length > 0, filterParams: { labels: { 'true': 'Есть', 'false': '
 111                  cellRenderer: p => p.value ? D.tone('API', 'accent', 'fa-plug') : '' }),
 112                D.numCol(edit({ field: 'costFactor', headerName: 'Коэф.', headerTooltip: 'Коэффициент на закупку', width
 113                  cellEditor: 'agNumberCellEditor', valueFormatter: money })),
 114                D.setCol(edit({ field: 'currencyCode', headerName: 'Валюта', width: 105, filterParams: { labels: CURRENC
 115                  cellEditor: 'agSelectCellEditor', cellEditorParams: { values: CURRENCIES }, valueFormatter: p => CURRE
 116                D.numCol(edit({ field: 'weightFactor', headerName: '₽/кг', headerTooltip: 'Стоимость доставки за кг — в 
 117                  cellEditor: 'agNumberCellEditor', valueFormatter: money })),
 118                D.setCol(edit({ field: 'weightFactorCurrencyCode', headerName: 'Вал. кг', width: 105, filterParams: { la
 119                  cellEditor: 'agSelectCellEditor', cellEditorParams: { values: CURRENCIES }, valueFormatter: p => CURRE
 120                edit({ field: 'csvUrl', headerName: 'CSV', headerTooltip: 'CSV URL остатков', width: 200, valueFormatter
 121                edit({ field: 'site', headerName: 'Сайт', width: 170, valueFormatter: text, tooltipField: 'site' }),
 122                D.numCol(edit({ field: 'additionalTerm', headerName: 'Срок', headerTooltip: 'Дополнительный срок поставк
 123                  cellEditor: 'agNumberCellEditor', valueFormatter: text })),
 124                D.numCol(edit({ field: 'expectedDeliveryHours', headerName: 'SLA, ч', headerTooltip: 'Норматив прихода д
 125                  cellEditor: 'agNumberCellEditor', cellEditorParams: { min: 1 }, valueFormatter: text })),
 126                edit({ field: 'innCode', headerName: 'ИНН', headerTooltip: 'Контрагент 1С', width: 140, valueFormatter: 
 127                D.boolCol(edit({ field: 'isVatApplicable', headerName: 'НДС', headerTooltip: 'Облагается НДС', width: 95
 128                D.boolCol(edit({ field: 'isReSeller', headerName: 'Реал.', headerTooltip: 'Реализатор', width: 95, cellE
 129                D.setCol({ colId: 'counterparty', headerName: '1С', headerTooltip: 'Контрагент 1С по ИНН', flex: 1, minW
 130                  valueGetter: p => p.data.counterparty.state === 'ok' ? p.data.counterparty.name : p.data.counterparty.
 131                  filterParams: { labels: { '': '(ИНН не задан)' } },
 132                  tooltipValueGetter: p => p.data.counterparty.fullName || '',
 133                  cellRenderer: p => {
 134                      const c = p.data.counterparty;
 135                      if (c.state === 'ok') return `<span class="bsx-tone-text-accent text-truncate"><i class="fas fa-ch
 136                      if (c.state === 'missing') return `<span class="bsx-tone-text-danger" title="ИНН ${esc(p.data.innC
 137                      return '<span class="bsx-muted">—</span>';
 138                  } }),
 139                D.actionsCol(r => [{ icon: 'fa-pencil-alt', title: 'Изменить', href: editUrl(r.id) }], { width: 160 })
 140            ]
 141        });
 142    })();
 143</script>
 144}

Methods/Properties

ExecuteAsync()
<ExecuteAsync()