< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_Partials_Orders__IndexTableBody
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Partials/Orders/_IndexTableBody.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 476
Coverable lines: 476
Total lines: 1285
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 618
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%2565425060%
L()100%210%
upd()0%2040%
<ExecuteAsync()100%210%
GetPriceTextColorClass(...)0%210140%
GetClientTooltip(...)0%210140%
GetIndexColor(...)0%2040%
GetColorIndexHex(...)0%210140%
GetColorIndexDescription(...)0%210140%
GetPriceTooltip(...)0%600240%
Money()100%210%
Row()100%210%
Value()100%210%
Text()0%620%
Date()0%2040%
GetPromotionTooltip(...)0%7280%
GetPurchasePriceTooltip(...)0%110100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Partials/Orders/_IndexTableBody.cshtml

#LineLine coverage
 1@using OzonDomains
 2@using OzonDomains.Models
 3@using OzonOrdersWeb.ViewModels.OrderViewModels
 4@using OzonOrdersWeb.Areas.Studio2.ViewHelpers
 5
 6@model OrderPageViewModel<Order, OServices.FiltersServices.FilterModels.OrderFilterModel>
 7
 8@{
 09    Func<string?, string> truncateStatus = text =>
 010    {
 011        if (string.IsNullOrEmpty(text)) return text ?? "";
 012        return text.Length > 20 ? text.Substring(0, 20) + "..." : text;
 013    };
 14
 015    var readMap = Model.OrderListFieldReadAccess;
 016    var u = Model.User;
 017    bool L(string field, string leg) => OrderListColumnGate.CanShowOrderColumn(u, readMap, field, leg);
 018    var indexAction = ViewData["IndexAction"] as string ?? "Index";
 019    var isReturnsPage = string.Equals(indexAction, "Returns", StringComparison.OrdinalIgnoreCase);
 020    var labelExistsMap = ViewData["LabelExistsByShipmentNumber"] as Dictionary<string, bool>
 021                         ?? new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
 22}
 23<tbody id="shipmentTableBody">
 24        @{
 25            // Переменные для группировки по отправлениям
 026            long? previousShipmentId = null;
 027            bool groupByShipment = ViewBag.GroupByShipment ?? Model.FilterModel.GroupByShipment;
 028            var shipmentGroups = new Dictionary<long, int>(); // ShipmentId -> количество заказов в группе
 29
 30            // Подсчитываем количество заказов в каждой группе
 031            if (groupByShipment)
 32            {
 033                foreach (var order in Model.Items.Where(o => o.ShipmentId.HasValue))
 34                {
 035                    var sid = order.ShipmentId!.Value;
 036                    if (!shipmentGroups.ContainsKey(sid))
 037                        shipmentGroups[sid] = 0;
 038                    shipmentGroups[sid]++;
 39                }
 40            }
 41        }
 042        @foreach (var item in Model.Items)
 43        {
 44            // Подсветка изменённых столбцов: возвращает " bg-warning" (для конкатенации в class=)
 45            // или "" — вместо разворачивания тернарника в разметке каждой ячейки.
 46            string upd(params string[] cols)
 047                => item.UpdatedColumns != null && cols.Any(item.UpdatedColumns.Contains) ? " bg-warning" : "";
 48
 49            // Определяем позицию заказа в группе отправления
 050            bool isFirstInGroup = false;
 051            bool isInGroup = false;
 052            int groupSize = 0;
 53
 054            if (groupByShipment && item.ShipmentId.HasValue)
 55            {
 056                groupSize = shipmentGroups.GetValueOrDefault(item.ShipmentId.Value, 1);
 057                isInGroup = groupSize > 1;
 058                isFirstInGroup = isInGroup && item.ShipmentId != previousShipmentId;
 059                previousShipmentId = item.ShipmentId;
 60            }
 61            else
 62            {
 063                previousShipmentId = null;
 64            }
 65
 066            string rowClass = "custom-white";
 067            if ((item.AppStatus?.Name == "Отменен" && (item.ClientStatus?.Name == "Отменён" || item.ClientStatus == null
 068                item.ClientStatus?.Name is "Отменен" or "Отменён")
 69            {
 070                rowClass = "table-danger";
 71            }
 072            else if (item.IsReturnable == true)
 73            {
 074                rowClass = "custom-red";
 75            }
 76            else
 77            {
 078                rowClass = item.AppStatus?.Name switch
 079                {
 080                    "Заказан поставщику" => "table-success",
 081                    "Отгружен поставщиком" => "table-info",
 082                    "Отгружен реализатору" => "table-warning",
 083                    "Заказан реализатору" => "table-secondary",
 084                    _ => rowClass
 085                };
 86            }
 87
 088            @if (item.OrderArchive != null)
 89            {
 090                rowClass = rowClass + " table-archived";
 91            }
 92
 93            // Добавляем классы группировки
 094            if (isFirstInGroup)
 95            {
 096                rowClass = rowClass + " shipment-group-first";
 97            }
 098            else if (isInGroup && !isFirstInGroup)
 99            {
 0100                rowClass = rowClass + " shipment-group-continuation";
 101            }
 102
 103            <tr class="@rowClass order-row"
 0104                data-order-id="@item.Id"
 0105                data-shipment-id="@item.ShipmentId"
 0106                data-is-archived="@(item.OrderArchive != null ? "true" : "false")"
 0107                data-cart-item-id="@item.CartItemId"
 0108                data-app-status-name="@item.AppStatus?.Name"
 0109                data-market-status="@item.Status"
 110                oncontextmenu="showInfoAtCursor('@item.Id', '@item.CartItemId', @(item.OrderArchive != null ? "true" : "
 111                style="cursor: pointer;">
 0112                @if ((L("ShipmentNumber", "Номер заказа") || L("Delivery", "Номер заказа") || L("DeliveryType", "Номер з
 113                {
 114                    <td data-column="shipmentNumber" class="@(item.UpdatedColumns?.Contains("ShipmentNumber") == true ? 
 115                        <div class="orders-shipment-content d-flex flex-column" style="min-width: 0;">
 0116                                @if (L("ShipmentNumber", "Номер заказа"))
 117                                {
 118                                    <!-- Номер заказа -->
 119                                    <div class="ml-2" onclick="copyArticle(this)" role="button">
 0120                                        @if (isInGroup && !isFirstInGroup)
 121                                        {
 122                                            <span class="shipment-group-indicator"
 123                                                  title="Часть отправления @item.ShipmentId">
 124                                                <i class="fas fa-level-up-alt fa-rotate-90 fa-lg"></i>
 125                                            </span>
 126                                        }
 127                                        else
 128                                        {
 0129                                            @Html.DisplayFor(modelItem => item.ShipmentNumber)
 130                                        }
 0131                                        @if (item.ShipmentNumber != null && item.ShipmentNumber != "" && !(isInGroup && 
 132                                        {
 0133                                            var shipmentNumberParts = item.ShipmentNumber.Split('-');
 0134                                            if (shipmentNumberParts.Length > 0)
 135                                            {
 0136                                                var key = shipmentNumberParts[0];
 0137                                                @if (!isFirstInGroup || groupSize <= 1)
 138                                                {
 0139                                                    if (@Model.UniqueNumbers.TryGetValue(key, out int count) && count !=
 140                                                    {
 141                                                        <a href="javascript:void(0);"
 0142                                                           onclick="setShipmentNumberFilter('@key')">@count</a>
 143                                                    }
 144                                                }
 145                                            }
 146                                        }
 147                                    </div>
 148                                }
 0149                                @if (L("Delivery", "Номер заказа"))
 150                                {
 151                                    <div class="d-flex" style="gap: 2px;">
 0152                                        @if (item.ExcelFileData != null && item.OzonClient != null)
 153                                        {
 154                                            <a href="@Url.Action("ViewFile", new { filePath = item.OzonClient.Name, file
 155                                               target="_blank" class="orders-row-ic-xlsx"
 156                                               onclick="return validateLink('@item.ExcelFileData.FolderName', '@item.Exc
 157                                                <i class="bi bi-filetype-xlsx" aria-hidden="true"></i>
 158                                            </a>
 159                                        }
 160                                        <div
 161                                            class="text-muted d-flex@(upd("Delivery"))"
 162                                            style="white-space: nowrap;" title="@item.DeliveryCity">
 0163                                            @if (!(isInGroup && !isFirstInGroup))
 164                                            {
 0165                                                string? delivery = item.Delivery?.Provider?.Name;
 166
 0167                                                if (!string.IsNullOrEmpty(delivery) && delivery.Length > 20)
 168                                                {
 0169                                                    delivery = delivery.Substring(0, 20) + "...";
 170                                                }
 171
 0172                                                <span title="@item.Delivery?.Name">@delivery</span>
 173                                            }
 174                                            @{
 0175                                                var transactionDocument = item.OrderExtensions?
 0176                                                    .FirstOrDefault(e => e.Key == "Документ транзакции")?.Value;
 177                                            }
 0178                                            @if (!string.IsNullOrEmpty(transactionDocument))
 179                                            {
 180                                                <div class="d-flex flex-column align-items-start ms-2">
 181                                                    <a href="@Url.Action("DownloadFile", "TransactionFiles", new { area 
 182                                                       class="transaction-document-link"
 0183                                                       data-order-id="@item.Id"
 184                                                       data-tooltip="Скачать документ транзакции"
 185                                                       tooltip=""
 186                                                       data-placement="top"
 187                                                       title="Скачать документ транзакции"
 188                                                       style="color: #0d6efd; text-decoration: none; margin-bottom: 4px;
 189                                                        <i class="bi bi-file-earmark-arrow-down" aria-hidden="true"></i>
 190                                                    </a>
 191                                                </div>
 192                                            }
 193                                        </div>
 194                                    </div>
 195                                }
 0196                            @if (L("DeliveryType", "Номер заказа") && !(isInGroup && !isFirstInGroup) && !string.IsNullO
 197                            {
 198                                <div class="orders-shipment-delivery-detail text-muted" style="white-space: nowrap; font
 199                                    @{
 0200                                        var deliveryFullName = item.Delivery.Name;
 0201                                        if (deliveryFullName.Length > 30)
 0202                                            deliveryFullName = deliveryFullName.Substring(0, 30) + "...";
 203                                    }
 0204                                    @deliveryFullName
 205                                </div>
 206                            }
 207                        </div>
 208                    </td>
 0209                    @if (L("ShipmentNumber", "Номер заказа"))
 210                    {
 211                    <td data-column="shipmentActions" class="orders-shipment-actions-col">
 212                            <div class="d-flex flex-column align-items-center orders-shipment-actions">
 0213                                @if (!(isInGroup && !isFirstInGroup))
 214                                {
 0215                                    @if (item.OzonClient?.ClientType == OzonDomains.ClientType.OZON)
 216                                    {
 0217                                        var labelExists = false;
 0218                                        var labelKnown = !string.IsNullOrEmpty(item.ShipmentNumber)
 0219                                                         && labelExistsMap.TryGetValue(item.ShipmentNumber!, out labelEx
 0220                                        var hasLabel = labelKnown && labelExists;
 221                                        <a href="javascript:void(0);"
 222                                           id="labelIcon_@item.Id"
 0223                                           data-shipment-number="@item.ShipmentNumber"
 0224                                           data-label-exists="@(hasLabel ? "true" : "false")"
 0225                                           data-label-checked="@(labelKnown ? "true" : "false")"
 226                                           onclick="printSingleOrderLabel(@item.Id)"
 227                                           title="@(hasLabel ? "Этикетка уже есть в системе. Нажмите для открытия" : "Пе
 228                                           style="@(hasLabel ? "color: #28a745; text-decoration: none; position: relativ
 229                                           class="label-icon-link">
 230                                            <i class="bi bi-printer label-icon" aria-hidden="true"
 231                                               style="@(hasLabel ? "color:#28a745;" : null)"></i>
 232                                            <i class="bi bi-check-circle-fill label-exists-indicator" aria-hidden="true"
 233                                               style="display:@(hasLabel ? "block" : "none");"></i>
 234                                        </a>
 235                                    }
 0236                                    else if (item.OzonClient?.ClientType == OzonDomains.ClientType.YANDEX)
 237                                    {
 0238                                        var yandexLabelExists = false;
 0239                                        var yandexLabelKnown = !string.IsNullOrEmpty(item.ShipmentNumber)
 0240                                                               && labelExistsMap.TryGetValue(item.ShipmentNumber!, out y
 0241                                        var yandexHasLabel = yandexLabelKnown && yandexLabelExists;
 242                                        <a href="javascript:void(0);"
 243                                           id="labelIconYandex_@item.Id"
 0244                                           data-shipment-number="@item.ShipmentNumber"
 245                                           data-order-source="yandex"
 0246                                           data-label-exists="@(yandexHasLabel ? "true" : "false")"
 0247                                           data-label-checked="@(yandexLabelKnown ? "true" : "false")"
 248                                           onclick="printSingleOrderLabelYandex(@item.Id)"
 249                                           title="@(yandexHasLabel ? "Этикетка уже есть в системе. Нажмите для открытия"
 250                                           style="@(yandexHasLabel ? "color: #28a745; text-decoration: none; position: r
 251                                           class="label-icon-link">
 252                                            <i class="bi bi-printer label-icon" aria-hidden="true"
 253                                               style="@(yandexHasLabel ? "color:#28a745;" : null)"></i>
 254                                            <i class="bi bi-check-circle-fill label-exists-indicator" aria-hidden="true"
 255                                               style="display:@(yandexHasLabel ? "block" : "none");"></i>
 256                                        </a>
 257                                    }
 258
 259                                    <a href="javascript:void(0);"
 260                                       id="articleLabelIcon_@item.Id"
 0261                                       data-order-id="@item.Id"
 262                                       onclick="generateArticleLabel(@item.Id, '@(item.EtProducer?.Name ?? "")', '@(item
 263                                       title="Этикетка по артикулу"
 264                                       style="color: #6c757d; text-decoration: none;"
 265                                       class="article-label-icon-link">
 266                                        <i class="bi bi-upc-scan" aria-hidden="true"></i>
 267                                    </a>
 268                                }
 269                                else
 270                                {
 271                                    <!-- Для продолжения группы показываем badge с количеством заказов -->
 0272                                    @if (groupSize > 1)
 273                                    {
 274                                        <span class="badge bg-light text-dark ms-2"
 275                                              title="Заказов в отправлении: @groupSize">
 0276                                            <i class="fas fa-boxes me-1"></i>@groupSize
 277                                        </span>
 278                                    }
 279                                }
 280                            </div>
 281                    </td>
 282                    }
 283                }
 284                <td data-column="client">
 285                    <div class="two-row-cell">
 0286                        @if (L("OzonClient", "Клиент"))
 287                        {
 288                            <div
 289                                class="cell-row orders-client-main-row@(upd("OzonClient"))"
 0290                                data-app-tooltip-html="@GetClientTooltip(item.OzonClient)"
 291                                data-app-tooltip-variant="client"
 292                                style="cursor: help;">
 0293                                @if (item.OzonClient != null)
 294                                {
 0295                                    string ozonClient = item.OzonClient.Name;
 0296                                    if (ozonClient.Length > 18)
 297                                    {
 0298                                        ozonClient = ozonClient.Substring(0, 18) + "...";
 299                                    }
 300
 0301                                    <span class="orders-client-name" title="@item.OzonClient.Name">@ozonClient</span>
 302                                }
 303                            </div>
 304                        }
 0305                        @if (L("DeliveryCity", "Город доставки"))
 306                        {
 0307                            @if (item.DeliveryCity != null && item.DeliveryCity != "")
 308                            {
 309                                <div
 310                                    class="cell-row@(upd("DeliveryCity")) text-muted d-flex align-items-center"
 311                                    style="white-space: nowrap;" title="@item.DeliveryCity">
 312                                    @{
 0313                                        string deliveryCity = item.DeliveryCity;
 0314                                        if (deliveryCity.Length > 11)
 315                                        {
 0316                                            deliveryCity = deliveryCity.Substring(0, 11) + "...";
 317                                        }
 318
 0319                                        @deliveryCity
 320                                    }
 0321                                    @if (Model.UniqueDeliveryCitys.ContainsKey(item.DeliveryCity) && Model.UniqueDeliver
 322                                    {
 323                                        <a href="javascript:void(0);"
 324                                           onclick="setDeliveryCityFilter('@item.DeliveryCity')"
 0325                                           style="margin-left: 2px;">(@Model.UniqueDeliveryCitys[item.DeliveryCity])</a>
 326                                    }
 327                                </div>
 328                            }
 329                        }
 0330                        @if (isReturnsPage && !string.IsNullOrEmpty(item.ReturnLogisticBarcode))
 331                        {
 332                            <div class="cell-row text-muted" style="white-space: nowrap;" title="@item.ReturnLogisticBar
 333                                <small style="cursor: pointer;" title="Нажмите, чтобы скопировать" onclick="copyArticle(
 0334                                    @item.ReturnLogisticBarcode
 335                                    <i class="bi bi-upc text-secondary barcode-icon-link" aria-hidden="true"
 0336                                       data-barcode="@item.ReturnLogisticBarcode"></i>
 337                                </small>
 338                            </div>
 339                        }
 0340                        else if (L("TrackingNumber", "Номер заказа") && !string.IsNullOrEmpty(item.TrackingNumber))
 341                        {
 342                            <div class="cell-row text-muted" style="white-space: nowrap; cursor: pointer;" title="@item.
 0343                                <small>@item.TrackingNumber</small>
 344                            </div>
 345                        }
 346                    </div>
 347                </td>
 348
 349                <td data-column="dates">
 350                    <div class="two-row-cell">
 0351                        @if (L("ProcessingDate", "Принят в обработку"))
 352                        {
 353                            <div
 354                                class="cell-row@(upd("ProcessingDate"))"
 355                                id="td2ProcessingDate"
 356                                title="@Html.DisplayFor(modelItem => item.FullFormattedProcessingDate)">
 0357                                @OrderDateDisplay.FormatTableDateFull(item.ProcessingDate)
 358                            </div>
 359                        }
 360
 0361                        @if (L("ShippingDate", "Дата отгрузки"))
 362                        {
 363                            <div
 364                                class="cell-row@(upd("ShippingDate")) text-muted"
 365                                title="@Html.DisplayFor(modelItem => item.FullFormattedShippingDate)">
 0366                                @OrderDateDisplay.FormatTableDateFull(item.ShippingDate)
 367                            </div>
 368                        }
 0369                        @if (isReturnsPage)
 370                        {
 371                            <div class="cell-row text-muted"
 372                                 title="@item.FullFormattedReturnStatusChangeDate">
 0373                                @item.FormattedReturnStatusChangeDate
 374                            </div>
 375                        }
 376                        @* Прогнозная дата доставки от поставщика — всегда серая (это оценка, не факт).
 377                           Гейт тот же, что у плашки статуса поставщика. *@
 0378                        @if (L("AppStatus", "Статус") && item.SupplierExpectedDeliveryDate.HasValue)
 379                        {
 380                            <div class="cell-row text-muted"
 381                                 title="@item.FullFormattedSupplierExpectedDeliveryDate">
 0382                                @item.FormattedSupplierExpectedDeliveryDate
 383                            </div>
 384                        }
 385                    </div>
 386                </td>
 387
 388                <td data-column="period">
 389                    <div class="two-row-cell">
 0390                        @if (L("DeliveryPeriod", "Срок доставки"))
 391                        {
 392                            <div class="cell-row orders-period-row">
 393                                <div
 0394                                    title="@Html.DisplayFor(modelItem => item.FormattedDeliveryDay) @Html.DisplayFor(mod
 395                            </div>
 396                        }
 0397                        @if (L("DeliveryPeriod", "Срок доставки"))
 398                        {
 399                            <div class="cell-row text-muted orders-period-row">
 0400                                <div>@Html.DisplayFor(modelItem => item.FormattedTimeLeftDay)</div>
 401                            </div>
 402                        }
 0403                        @if (L("AppStatus", "Статус") && item.SupplierExpectedDeliveryDate.HasValue)
 404                        {
 405                            <div class="cell-row text-muted orders-period-row">
 0406                                <div title="@item.FullFormattedSupplierExpectedDeliveryDate">@item.FormattedSupplierExpe
 407                            </div>
 408                        }
 409                    </div>
 410                </td>
 411
 412                <td class="orders-product-col" data-column="productInfo">
 413                    <div class="d-flex orders-product-cell">
 0414                        @if (!string.IsNullOrEmpty(item.ProductInfo?.ImageUrl))
 415                        {
 416                            <img src="@item.ProductInfo.ImageUrl"
 417                                 alt="@(item.ProductName ?? item.Article)"
 418                                 class="product-thumb flex-shrink-0"
 419                                 onerror="this.style.display='none'"
 420                                 loading="lazy" />
 421                        }
 422                        <div class="orders-product-text-grid" style="min-width: 0; flex: 1; overflow: hidden; display: g
 423                            <div class="orders-product-top-row">
 0424                                @if (L("ProductKey", "Key"))
 425                                {
 426                                    <div id="thArticle" title="@item.ProductKey"
 427                                         class="product-key-cell d-flex align-items-center min-w-0@(upd("ProductKey"))">
 428                                        @{
 0429                                            string? productKey = item.ProductKey;
 0430                                            string? displayText = productKey;
 431
 0432                                            if (!string.IsNullOrEmpty(productKey))
 433                                            {
 0434                                                var match = System.Text.RegularExpressions.Regex.Match(productKey, @"\[(
 435
 0436                                                if (match.Success)
 437                                                {
 0438                                                    string mainPart = productKey.Substring(0, match.Index);
 0439                                                    string numberPart = match.Value;
 440
 0441                                                    if (mainPart.Length > 17)
 442                                                    {
 0443                                                        mainPart = mainPart.Substring(0, 17) + "...";
 444                                                    }
 445
 0446                                                    displayText = $"{mainPart}<span class='text-danger fw-bold product-k
 447                                                }
 448                                                else
 449                                                {
 0450                                                    if (productKey.Length > 17)
 451                                                    {
 0452                                                        displayText = productKey.Substring(0, 17) + "...";
 453                                                    }
 454                                                }
 455                                            }
 456                                        }
 457
 458                                        <span class="custom-product-link">
 0459                                            @Html.Raw(displayText)
 460                                        </span>
 461
 0462                                        @if (!string.IsNullOrEmpty(item.ProductInfo?.Barcode))
 463                                        {
 464                                            <i class="bi bi-upc text-secondary barcode-icon-link" aria-hidden="true"
 0465                                               data-barcode="@item.ProductInfo.Barcode"></i>
 466                                        }
 467
 0468                                        @if (item.ProductKey != null && item.ProductKey != "" && Model.UniqueArticles.Co
 469                                        {
 470                                            <a href="javascript:void(0);"
 471                                               onclick="setArticleFilter('@System.Net.WebUtility.HtmlEncode(item.Product
 472                                               style="margin-left:5px;flex-shrink:0;font-size:0.75rem;"
 473                                               title="Фильтровать по этому артикулу">
 0474                                                (@Model.UniqueArticles[item.ProductKey])
 475                                            </a>
 476                                        }
 477                                    </div>
 478                                }
 0479                                @if (L("Manufacturer", "Производитель"))
 480                                {
 481                                    <div id="thManufacturer" class="product-manufacturer-cell d-flex flex-column nowrap 
 0482                                        @if (item.EtProducer != null && item.EtProducer.Name == null)
 483                                        {
 0484                                            <form asp-area="Studio2" asp-action="@indexAction" asp-controller="Producers
 485                                                <input type="hidden" name="MarketPrefix" value="@item.EtProducer.MarketP
 486                                                <button type="submit" class="btn btn-link">Указать</button>
 487                                            </form>
 488                                        }
 489                                        else
 490                                        {
 0491                                            @(item.EtProducer?.Name ?? "")
 492                                        }
 493                                    </div>
 494                                }
 0495                                @if (L("Article", "Артикул"))
 496                                {
 497                                    <div id="thArticle2"
 498                                         class="d-flex flex-column nowrap text-end align-items-end article-and-replaceme
 499                                        <div class="d-flex align-items-center" style="gap: 2px;">
 500                                            @{
 0501                                                var replacementArticle = item.ProductInfo?.ReplacementProduct?.Article;
 0502                                                var hasReplacement = !string.IsNullOrEmpty(replacementArticle);
 503                                            }
 0504                                            @if (item.ProductInfoId.HasValue)
 505                                            {
 506                                                <span class="replacement-link-icon @(hasReplacement ? "active" : "")"
 0507                                                      data-product-info-id="@item.ProductInfoId"
 0508                                                      data-original-article="@item.Article"
 0509                                                      @(hasReplacement ? Html.Raw("data-replacement-article=\"" + System
 510                                                      style="font-size: 0.875rem; line-height: 1; min-width: 16px; curso
 511                                                      title="@(hasReplacement ? "Связано с: " + replacementArticle + ". 
 512                                                    <i class="bi bi-link-45deg" aria-hidden="true"></i>
 513                                                </span>
 514                                            }
 515                                            <div role="button"
 516                                                 title="Нажмите, чтобы скопировать"
 517                                                 onclick="copyArticle(this)">
 0518                                                @if (item.Article != null)
 519                                                {
 0520                                                    @Html.DisplayFor(modelItem => item.Article)
 521                                                }
 522                                            </div>
 523                                        </div>
 524                                        <div class="replacement-article-display"
 525                                             style="display: @(hasReplacement ? "block" : "none");"
 526                                             aria-hidden="@(hasReplacement ? "false" : "true")">
 527                                            <span class="replacement-arrow">↳</span>
 0528                                            <span class="replacement-article-text">@(replacementArticle ?? "")</span>
 529                                        </div>
 530                                    </div>
 531                                }
 532                            </div>
 533                            <script>
 534                                document.addEventListener('DOMContentLoaded', function () {
 535                                    var articleFilter = document.getElementById('articleFilter');
 536                                    var articleFilter2 = document.getElementById('articleFilter2');
 537
 538                                    if (articleFilter && articleFilter2) {
 539                                        articleFilter.addEventListener('input', function () {
 540                                            if (articleFilter2.value !== articleFilter.value) articleFilter2.value = art
 541                                        });
 542                                        articleFilter.addEventListener('change', function () {
 543                                            if (articleFilter2.value !== articleFilter.value) articleFilter2.value = art
 544                                        });
 545                                        articleFilter2.addEventListener('input', function () {
 546                                            if (articleFilter.value !== articleFilter2.value) {
 547                                                articleFilter.value = articleFilter2.value;
 548                                                articleFilter.dispatchEvent(new Event('input', {bubbles: true}));
 549                                            }
 550                                        });
 551                                        articleFilter2.addEventListener('change', function () {
 552                                            if (articleFilter.value !== articleFilter2.value) {
 553                                                articleFilter.value = articleFilter2.value;
 554                                                articleFilter.dispatchEvent(new Event('change', {bubbles: true}));
 555                                            }
 556                                        });
 557                                    }
 558                                });
 559                            </script>
 560
 0561                            @if (L("ProductName", "Наименование товара"))
 562                            {
 0563                                var productName = item.ProductName;
 0564                                var truncatedName = string.IsNullOrEmpty(productName)
 0565                                    ? productName
 0566                                    : (productName.Length > 110 ? productName.Substring(0, 110) + "..." : productName);
 567
 568                                <div id="thProductName" class="text-muted" title="@productName" style="overflow: hidden;
 569                                    <a href="http://studio-web-client.interparts.ru/info?producerId=@item.EtProducer?.Id
 570                                       target="_blank"
 571                                       class="custom-product-link">
 0572                                        @truncatedName
 573                                    </a>
 574                                </div>
 575                            }
 576                        </div>
 577                    </div>
 578                </td>
 579
 580                <td class="checkbox-column-cell" data-column="checkboxActions">
 581                    <div class="form-check">
 0582                        <input type="checkbox" class="form-check-input record-checkbox" data-id="@item.Id">
 583                    </div>
 584                </td>
 585
 586                <td class="status-column-cell" data-column="status">
 0587                    @if (L("ClientStatus", "Статус клиента"))
 588                    {
 589                        <div
 590                            class="nowrap"
 591                            title="@(item.ClientStatus?.Name ?? "")">
 0592                            @if (item.ClientStatus?.Name != null)
 593                            {
 0594                                string status = truncateStatus(item.ClientStatus.Name);
 595
 0596                                var bgClientStatusColor = item.ClientStatus?.StatusColor?.ColorCode ?? "transparent";
 0597                                var textColor = StatusColorContrast.GetTextColor(bgClientStatusColor);
 598
 599                                <div class="status-color-badge orders-chip"
 600                                     style="--status-badge-bg:@bgClientStatusColor; --status-badge-fg:@textColor; backgr
 0601                                    @status
 602                                </div>
 0603                                var cancellation = item.LatestOzonPostingCancellation;
 0604                                var isCancelledClientStatus = item.ClientStatus?.Name is "Отменен" or "Отменён" or "Отме
 0605                                var hasCancellationInfo = cancellation != null &&
 0606                                    (!string.IsNullOrWhiteSpace(cancellation.ReasonTitle) || !string.IsNullOrWhiteSpace(
 0607                                var cancellationTooltip = hasCancellationInfo
 0608                                    ? $"Причина отмены: {cancellation!.ReasonTitle ?? cancellation.ReasonMessage}" +
 0609                                      (cancellation.ReasonId.HasValue ? $" (ID: {cancellation.ReasonId})" : string.Empty
 0610                                      (!string.IsNullOrWhiteSpace(cancellation.ReasonTypeId) ? $"; инициатор: {cancellat
 0611                                      (!string.IsNullOrWhiteSpace(cancellation.ReasonMessage) && cancellation.ReasonMess
 0612                                    : string.Empty;
 0613                                @if (isCancelledClientStatus && hasCancellationInfo)
 614                                {
 0615                                    <span class="ozon-cancellation-info" title="@cancellationTooltip" data-app-tooltip="
 616                                        <i class="fas fa-circle-info" aria-hidden="true"></i>
 617                                    </span>
 618                                }
 619                            }
 620                            else
 621                            {
 622                                <div></div>
 623                            }
 624                            <span class="shipment-totals-badge"
 0625                                  data-order-id="@item.Id"
 626                                  style="font-size: 13px; margin-left: 4px; display: inline-block; cursor: pointer; flex
 627                                  onclick="showOrderShipmentTotals(@item.Id);"
 628                                  title="Открыть итоги по отправлению"></span>
 629                        </div>
 630                    }
 631
 0632                    @if (L("AppStatus", "Статус"))
 633                    {
 634                        <div
 635                            class="text-muted nowrap app-status-line"
 636                            title="@item.AppStatus?.Name">
 637                            @{
 0638                                string appStatus = truncateStatus(item.AppStatus?.Name);
 639
 0640                                var bgAppStatusColor = item.AppStatus?.StatusColor?.ColorCode ?? "transparent";
 0641                                var textColor = StatusColorContrast.GetTextColor(bgAppStatusColor);
 642                            }
 643
 644                            <a asp-area="Studio2"
 645                               asp-controller="OrderStatusFlow"
 646                               asp-action="OrderStatus"
 0647                               asp-route-id="@item.Id"
 648                               target="_blank"
 649                               style="text-decoration: none; display: inline-block;">
 650                                <div class="nowrap status-color-badge orders-chip orders-chip--action"
 651                                     style="--status-badge-bg:@bgAppStatusColor; --status-badge-fg:@textColor; backgroun
 0652                                    @appStatus
 653                                </div>
 654                            </a>
 655                        </div>
 656                    }
 657
 658                    @* Третья строка ячейки — состояние заказа у API-поставщика (OrderSupplierStates).
 659                       Показывается под системным статусом: в корзине, заказан, затем живой статус от API.
 660                       Доступ гейтится тем же правом, что и системный статус. *@
 0661                    @if (L("AppStatus", "Статус") && !string.IsNullOrWhiteSpace(item.SupplierStatusName))
 662                    {
 0663                        var bgSupplierColor = item.SupplierStatusColor ?? "transparent";
 0664                        var supplierTextColor = StatusColorContrast.GetTextColor(bgSupplierColor);
 0665                        var supplierName = item.SupplierStateSupplierName ?? item.SupplierStateProviderKey;
 0666                        var supplierTooltip = $"{supplierName}: {item.SupplierStatusName}";
 0667                        if (!string.IsNullOrWhiteSpace(item.SupplierStateExternalOrderNumber))
 668                        {
 0669                            supplierTooltip += $" · заказ {item.SupplierStateExternalOrderNumber}";
 670                        }
 0671                        var supplierCartUrl = $"/SupplierCart?provider={Uri.EscapeDataString((item.SupplierStateProvider
 672
 673                        <div class="text-muted nowrap supplier-status-line" title="@supplierTooltip">
 674                            <a href="@supplierCartUrl"
 675                               target="_blank"
 676                               style="text-decoration: none; display: inline-block;">
 677                                <div class="nowrap status-color-badge supplier-status-badge orders-chip orders-chip--act
 678                                     style="--status-badge-bg:@bgSupplierColor; --status-badge-fg:@supplierTextColor; ba
 0679                                    @truncateStatus(item.SupplierStatusName)
 680                                </div>
 681                            </a>
 682                        </div>
 683                    }
 684
 685                    @{
 0686                        var returnStatusText = item.ReturnStatusDisplayName;
 0687                        if (string.IsNullOrWhiteSpace(returnStatusText))
 688                        {
 0689                            returnStatusText = item.ReturnStatusSysName;
 690                        }
 0691                        returnStatusText = truncateStatus(returnStatusText);
 0692                        var showReturnStatusRow = item.IsReturned
 0693                            || !string.IsNullOrWhiteSpace(returnStatusText)
 0694                            || !string.IsNullOrWhiteSpace(item.ReturnWarehouseName);
 695                    }
 0696                    @if (showReturnStatusRow)
 697                    {
 698                        <div class="text-muted nowrap"
 699                             title="@(string.IsNullOrWhiteSpace(item.ReturnStatusDisplayName) ? item.ReturnStatusSysName
 700                            <a href="@Url.Action("Index", "OrderReturns", new { area = "Studio2", orderId = item.Id })"
 701                               target="_blank"
 702                               title="Возврат — открыть детали"
 703                               class="orders-chip orders-chip--action orders-chip--return">Возв.</a>
 0704                            @if (!string.IsNullOrWhiteSpace(returnStatusText))
 705                            {
 0706                                <span class="orders-chip orders-chip--neutral">@returnStatusText</span>
 707                            }
 708                        </div>
 709                    }
 0710                    @if (item.CartItemStatus != null)
 711                    {
 0712                        var cartStatus = truncateStatus(item.CartItemStatus.Name);
 713
 0714                        var bgColor = item.CartItemStatusColor?.ColorCode ?? "transparent";
 0715                        var cartTextColor = StatusColorContrast.GetTextColor(bgColor);
 716
 717                        <a href="@Url.Action("CartStatuses", "OrderCartItemStatus", new { area = "Studio2", highlightId 
 718                           class="text-muted nowrap custom-product-link status-color-badge orders-chip orders-chip--acti
 719                           style="--status-badge-bg:@bgColor; --status-badge-fg:@cartTextColor; background-color:@bgColo
 720                           title="@item.CartItemStatus.Name">
 0721                            @cartStatus
 722                        </a>
 723                    }
 724                </td>
 725
 726                <td>
 0727                    @if (L("ShipmentWarehouse", "Склад отгрузки"))
 728                    {
 0729                        var zasyl = item.OrderExtensions?
 0730                            .FirstOrDefault(e => e.Key == "Засыл")?.Value;
 731                        <div
 732                            class="@(item.UpdatedColumns?.Contains("ShipmentWarehouseId") == true ? "bg-warning" : null)
 0733                            @(item.ShipmentWarehouse?.Name ?? "")
 0734                            @if (!string.IsNullOrEmpty(zasyl))
 735                            {
 0736                                <span class="text-muted"> [@zasyl]</span>
 737                            }
 738                        </div>
 739                    }
 740
 0741                    @if (L("ShipmentWarehouse", "Склад отгрузки"))
 742                    {
 743                        <div
 744                            class="text-muted"
 745                            title="@item.FullFormattedLastStatusChangeDate">
 0746                            @Html.DisplayFor(modelItem => item.FormattedLastStatusChangeDate)
 747                        </div>
 748                    }
 0749                    @if (!string.IsNullOrWhiteSpace(item.ReturnWarehouseName))
 750                    {
 751                        <div class="text-muted nowrap" title="@item.ReturnWarehouseName">
 0752                            @item.ReturnWarehouseName
 753                        </div>
 754                    }
 755                </td>
 756                <td data-column="supplier">
 757                    <div class="two-row-cell">
 0758                        @if (L("Supplier", "Поставщик"))
 759                        {
 760                            <div class="cell-row@(upd("SupplierId"))">
 0761                                @(item.Supplier?.Name ?? "")
 762                            </div>
 763                        }
 764
 0765                        @if (!string.IsNullOrWhiteSpace(item.SupplierStateDirection))
 766                        {
 767                            <div class="cell-row text-muted" style="padding-left: 10px;" title="@($"Направление поставщи
 0768                                <span aria-hidden="true">&#8627;</span> @item.SupplierStateDirection
 769                            </div>
 770                        }
 771
 0772                        @if (L("SupplierOrderNumber", "Номер заказа поставщику"))
 773                        {
 774                            <div class="cell-row text-muted supplier-status-provider-row@(upd("OrderNumberToSupplier"))"
 775                                <span class="supplier-order-number-text"
 776                                      title="@item.OrderNumberToSupplier"
 0777                                      data-placement="top">@Html.DisplayFor(modelItem => item.OrderNumberToSupplier)</sp
 778                            </div>
 779                        }
 780                    </div>
 781                </td>
 782
 783                <td>
 784                    <div class="two-row-cell">
 0785                        @if (L("Category", "Категория"))
 786                        {
 787                            <div class="cell-row@(upd("ProductInfoId"))">
 0788                                @if (!string.IsNullOrEmpty(item.ProductInfo?.CommercialCategory))
 789                                {
 0790                                    string сommercialCategory = item.ProductInfo.CommercialCategory;
 0791                                    if (сommercialCategory.Length > 10)
 792                                    {
 0793                                        сommercialCategory = сommercialCategory.Substring(0, 10) + "...";
 794                                    }
 795
 0796                                    @сommercialCategory
 797                                }
 798
 799                            </div>
 800                        }
 801
 0802                        @if (L("VolumeWeight", "Объемный вес"))
 803                        {
 804                            <div class="cell-row justify-content-end text-muted@(upd("ProductInfoId"))">
 0805                                @if (item.ProductInfo != null)
 806                                {
 0807                                    @Html.DisplayFor(modelItem => item.ProductInfo.VolumetricWeight)
 0808                                    @if (item.ProductInfo.Weight != 0)
 809                                    {
 810                                        <div style="white-space: nowrap; margin: 0; font-size: 10px; text-align: right">
 0811                                            @Html.DisplayFor(modelItem => item.ProductInfo.Weight)
 812                                        </div>
 813                                    }
 814                                }
 815                            </div>
 816                        }
 817                    </div>
 818                </td>
 819
 820                <td data-column="sitePrice">
 821                    <div class="two-row-cell">
 0822                        @if (L("SitePrice", "Цена сайта"))
 823                        {
 824                            <div
 825                                class="cell-row justify-content-end@(upd("ProductInfoId"))">
 0826                                @if (item.ProductInfo != null)
 827                                {
 0828                                    @OrderCellFormat.Money(item.ProductInfo.CurrentPriceWithDiscount)
 829                                }
 830                            </div>
 831                        }
 832
 0833                        @if (L("Quantity", "Количество"))
 834                        {
 835                            <div
 836                                class="cell-row justify-content-end text-muted quantity-cell"
 837                                id="quantity_@item.Id">
 838                                <span class="@(item.Quantity > 1 ? "qty-value--multi" : "")">
 0839                                    @Html.DisplayFor(modelItem => item.Quantity)
 840                                </span>
 841                            </div>
 842                        }
 843                    </div>
 844                </td>
 845
 846                <td data-column="price">
 847                    <div class="two-row-cell">
 0848                        @if (L("Price", "Цена"))
 849                        {
 850                            <div class="cell-row justify-content-end">
 0851                                @if (item.OzonPromotionSnapshot?.HasAnyPromotion == true)
 852                                {
 853                                    <span class="ozon-promotion-icon"
 0854                                          data-app-tooltip-html="@GetPromotionTooltip(item.OzonPromotionSnapshot)"
 855                                          data-app-tooltip-variant="promotion"
 856                                          aria-label="Товар участвует в акции Ozon">
 857                                        <i class="fas fa-tags" aria-hidden="true"></i>
 858                                    </span>
 859                                }
 860                                <span class="@GetPriceTextColorClass(item.OrderPrice?.PriceIndexes?.ColorIndex)"
 0861                                      data-app-tooltip-html="@GetPriceTooltip(item)"
 862                                      data-app-tooltip-variant="price">
 0863                                    @OrderCellFormat.Money(item.Price)
 864                                </span>
 865                            </div>
 866                        }
 867
 0868                        @if (L("ShipmentTotal", "Сумма отправления"))
 869                        {
 870                            <div
 871                                class="cell-row justify-content-end@(upd("ShipmentAmount"))">
 0872                                @OrderCellFormat.Money(item.ShipmentAmount)
 873                            </div>
 874                        }
 875                    </div>
 876                </td>
 877
 878                <td data-column="purchasePrice">
 879                    <div class="two-row-cell">
 0880                        @if (L("PurchasePrice", "Цена закупки"))
 881                        {
 882                            <div
 883                                class="cell-row align-items-сenter justify-content-end@(upd("PurchasePrice"))"
 884                                style="white-space: nowrap; width: 100%"
 0885                                data-app-tooltip-html="@GetPurchasePriceTooltip(item)"
 886                                data-app-tooltip-variant="purchase-price"
 887                            >
 888                                <p class="align-self-end m-0">
 0889                                    @OrderCellFormat.Money(item.PurchasePrice)
 890                                </p>
 891                            </div>
 892                        }
 893
 0894                        @if (L("Cost", "Себестоимость"))
 895                        {
 0896                            var hasExtraExpenses = item.ExtraExpenses != null && item.ExtraExpenses.Any(e => e.ExpenseTy
 0897                            var extraExpensesTotal = hasExtraExpenses ? (item.ExtraExpenses ?? Enumerable.Empty<OrderExt
 0898                            var validExtraExpenses = hasExtraExpenses ? (item.ExtraExpenses ?? Enumerable.Empty<OrderExt
 899                            <div
 900                                class="cell-row justify-content-end align-items-start@(upd("CostPrice"))"
 901                                style="position: relative;">
 902                                <span style="position: relative; display: inline-block;"
 903                                      class="cost-price-with-expenses">
 0904                                    @OrderCellFormat.Money(item.CostPrice)
 0905                                    @if (hasExtraExpenses)
 906                                    {
 907                                        <span class="extra-expenses-indicator"
 908                                              style="position: absolute; top: -2px; right: -8px; color: #dc3545; font-si
 909                                              data-app-tooltip-variant="expenses"
 0910                                              data-app-tooltip-html="<div style='text-align: left; padding: 4px;'><b>Доп
 911                                            ▼
 912                                        </span>
 913                                    }
 914                                </span>
 915                            </div>
 916                        }
 917                    </div>
 918                </td>
 0919                @if (L("OzonCommission", "Комиссия ОЗОН"))
 920                {
 921                    <td data-column="commission">
 922                        <div
 923                            class="two-row-cell@(upd("MinOzonCommission", "MaxOzonCommission"))">
 924                            <div class="cell-row">
 0925                                @OrderCellFormat.Money(item.MinOzonCommission)
 926                            </div>
 927                            <div class="cell-row">
 0928                                @OrderCellFormat.Money(item.MaxOzonCommission)
 929                            </div>
 930                        </div>
 931                    </td>
 932                }
 933
 0934                @if (L("Profit", "Прибыль"))
 935                {
 936                    <td data-column="profit">
 937                        <div
 938                            class="two-row-cell@(upd("MinProfit", "MaxProfit"))">
 939                            <div class="cell-row">
 0940                                <span class="@OrderCellFormat.SignClass(item.MaxProfit)">@OrderCellFormat.Money(item.Max
 941                            </div>
 942                            <div class="cell-row">
 0943                                <span class="@OrderCellFormat.SignClass(item.MinProfit)">@OrderCellFormat.Money(item.Min
 944                            </div>
 945                        </div>
 946                        @* 13,5% от суммы отправления — только при наведении, как доставка в номере заказа *@
 0947                        @if (item.TargetShipmentProfit135.HasValue)
 948                        {
 0949                            var targetText = OrderCellFormat.Money(item.TargetShipmentProfit135);
 0950                            var targetSlashPrefix = item.MaxProfit.HasValue && item.MaxProfit.Value != 0 ? "/ " : "";
 951                            <div class="orders-profit-target-detail text-muted"
 952                                 title="Целевая прибыль 13,5% от суммы отправления"
 953                                 style="white-space: nowrap; font-size: 0.75em;">
 0954                                @targetSlashPrefix@targetText
 955                            </div>
 956                        }
 957                    </td>
 958                }
 959
 0960                @if (L("MarkupPercent", "Наценка %"))
 961                {
 962                    <td data-column="markup">
 963                        <div
 964                            class="two-row-cell@(upd("MinDiscount"))">
 965                            <div class="cell-row">
 0966                                <span class="@OrderCellFormat.SignClass(item.MaxDiscount)">@OrderCellFormat.Money(item.M
 0967                                @if (item.MaxDiscount.HasValue)
 968                                {
 969                                    <span class="orders-unit">%</span>
 970                                }
 971                            </div>
 972                            <div class="cell-row">
 0973                                <span class="@OrderCellFormat.SignClass(item.MinDiscount)">@OrderCellFormat.Money(item.M
 0974                                @if (item.MinDiscount.HasValue)
 975                                {
 976                                    <span class="orders-unit">%</span>
 977                                }
 978                            </div>
 979                        </div>
 980                    </td>
 981                }
 982            </tr>
 0983        }
 0984        @if (!Model.Items.Any())
 985        {
 986            <tr class="orders-empty-row">
 987                <td colspan="100">
 988                    <div class="orders-empty-state d-flex flex-column align-items-center justify-content-center py-5">
 989                        <svg width="184" height="152" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg"
 990                             class="mb-3">
 991                            <g fill="none" fill-rule="evenodd">
 992                                <g transform="translate(24 31.67)">
 993                                    <ellipse fill-opacity=".8" fill="#F5F5F7" cx="67.797" cy="106.89" rx="67.797"
 994                                             ry="12.668"/>
 995                                    <path
 996                                        d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.
 997                                        fill="#AEB8C2"/>
 998                                    <path
 999                                        d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.5
 1000                                        fill="url(#linearGradient-1)" transform="translate(13.56)"/>
 1001                                    <path
 1002                                        d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 
 1003                                        fill="#F5F5F7"/>
 1004                                    <path
 1005                                        d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V1
 1006                                        fill="#DCE0E6"/>
 1007                                </g>
 1008                                <path
 1009                                    d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.
 1010                                    fill="#DCE0E6"/>
 1011                                <g transform="translate(149.65 15.383)" fill="#FFF">
 1012                                    <ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"/>
 1013                                    <path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"/>
 1014                                </g>
 1015                            </g>
 1016                        </svg>
 1017                        <p class="text-muted fs-5 mb-1">Ничего не найдено</p>
 1018                        <p class="text-muted small">Попробуйте изменить параметры фильтрации</p>
 1019                    </div>
 1020                </td>
 1021            </tr>
 1022        }
 1023        </tbody>
 1024
 1025@functions {
 1026
 1027    public string GetPriceTextColorClass(string? colorIndex)
 1028    {
 01029        return colorIndex?.ToUpper() switch
 01030        {
 01031            "SUPER" => "text-success fw-bold",
 01032            "GREEN" => "text-success",
 01033            "YELLOW" => "text-warning",
 01034            "RED" => "text-danger",
 01035            "INDEX" => "text-secondary",
 01036            "UNSPECIFIED" => "text-muted",
 01037            _ => ""
 01038        };
 1039    }
 1040
 1041    public string GetClientTooltip(OzonClient? client)
 1042    {
 01043        if (client == null)
 01044            return "Информация о клиенте отсутствует";
 1045
 01046        var tooltipLines = new List<string>();
 1047
 1048        // Тип клиента
 01049        string clientTypeText = client.ClientType switch
 01050        {
 01051            ClientType.OZON => "Ozon",
 01052            ClientType.YANDEX => "Yandex",
 01053            _ => "Другое"
 01054        };
 1055
 01056        string clientTypeColor = client.ClientType switch
 01057        {
 01058            ClientType.OZON => "#007bff",
 01059            ClientType.YANDEX => "#dc3545",
 01060            _ => "#6c757d"
 01061        };
 1062
 01063        tooltipLines.Add($"<div style='font-weight: bold; margin-bottom: 8px;'>Тип клиента: <span style='color: {clientT
 1064
 1065        // ИНН
 01066        if (!string.IsNullOrEmpty(client.INNCode))
 1067        {
 01068            tooltipLines.Add($"<div style='margin: 4px 0;'><b>ИНН:</b> {System.Net.WebUtility.HtmlEncode(client.INNCode)
 1069        }
 1070        else
 1071        {
 01072            tooltipLines.Add($"<div style='margin: 4px 0; color: #6c757d;'><b>ИНН:</b> не указан</div>");
 1073        }
 1074
 1075        // Склад
 01076        if (!string.IsNullOrEmpty(client.WarehouseName))
 1077        {
 01078            tooltipLines.Add($"<div style='margin: 4px 0;'><b>Склад:</b> {System.Net.WebUtility.HtmlEncode(client.Wareho
 1079        }
 1080        else
 1081        {
 01082            tooltipLines.Add($"<div style='margin: 4px 0; color: #6c757d;'><b>Склад:</b> не указан</div>");
 1083        }
 1084
 1085        // Валюта
 01086        tooltipLines.Add($"<div style='margin: 4px 0;'><b>Валюта:</b> {client.CurrencyCode}</div>");
 1087
 1088        // Обертка для светлого фона и ширины
 01089        return $@"
 01090                <div style='
 01091
 01092                    background: white;
 01093                    color: #333;
 01094                    padding: 12px;
 01095                    border-radius: 6px;
 01096                    border: 1px solid #ddd;
 01097                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
 01098                    min-width: 200px;
 01099                    font-family: Arial, sans-serif;
 01100                    font-size: 13px;
 01101                    line-height: 1.4;
 01102                '>
 01103                    {string.Join("", tooltipLines)}
 01104                </div>";
 1105    }
 1106
 1107
 1108    // Метод для получения цвета индекса на основе значения
 1109    public string GetIndexColor(decimal indexValue)
 1110    {
 01111        return indexValue switch
 01112        {
 01113            > 1.2m => "#28a745", // Зеленый - выгодно
 01114            > 0.8m => "#ffc107", // Желтый - умеренно
 01115            _ => "#dc3545" // Красный - невыгодно
 01116        };
 1117    }
 1118
 1119    // Метод для получения HEX цвета индекса
 1120    public string GetColorIndexHex(string? colorIndex)
 1121    {
 01122        return colorIndex?.ToUpper() switch
 01123        {
 01124            "SUPER" => "#198754", // Темно-зеленый
 01125            "GREEN" => "#28a745", // Зеленый
 01126            "YELLOW" => "#ffc107", // Желтый
 01127            "RED" => "#dc3545", // Красный
 01128            "INDEX" => "#6c757d", // Серый
 01129            "UNSPECIFIED" => "#6c757d", // Серый
 01130            _ => "#6c757d" // Серый по умолчанию
 01131        };
 1132    }
 1133
 1134
 1135    public string GetColorIndexDescription(string? colorIndex)
 1136    {
 01137        return colorIndex?.ToUpper() switch
 01138        {
 01139            "SUPER" => "Супервыгодный",
 01140            "GREEN" => "Выгодный",
 01141            "YELLOW" => "Умеренный",
 01142            "RED" => "Невыгодный",
 01143            "INDEX" => "Отсутствует",
 01144            "UNSPECIFIED" => "Не определён",
 01145            _ => "Не определён"
 01146        };
 1147    }
 1148
 1149    public string GetPriceTooltip(Order item)
 1150    {
 01151        var price = item.OrderPrice;
 01152        if (price == null)
 01153            return "Информация о проценке Ozon отсутствует";
 1154
 01155        var snapshot = item.OzonPromotionSnapshot;
 01156        var indexes = price.PriceIndexes;
 01157        var currency = price.CurrencyCode;
 01158        var actionPrice = snapshot?.MarketingSellerPrice ?? price.MarketingSellerPrice;
 01159        var netPrice = snapshot?.NetPrice ?? price.NetPrice;
 01160        var retailPrice = snapshot?.RetailPrice ?? price.RetailPrice;
 01161        var vat = snapshot?.Vat ?? price.Vat;
 01162        var acquiring = snapshot?.Acquiring;
 01163        var indexColor = GetColorIndexHex(indexes?.ColorIndex);
 01164        var indexDescription = GetColorIndexDescription(indexes?.ColorIndex);
 1165
 01166        static string Money(decimal value, string currencyCode) => $"{value:N2} {System.Net.WebUtility.HtmlEncode(curren
 1167        static string Row(string title, string apiField, string value) =>
 01168            $"<div class='price-tooltip-row'><span title='{System.Net.WebUtility.HtmlEncode(apiField)}'>{System.Net.WebU
 1169
 01170        var priceRows = string.Join(string.Empty,
 01171        [
 01172            Row("Текущая цена", "price", Money(price.Price, currency)),
 01173            Row("Маркетинговая цена", "marketing_price", Money(price.MarketingPrice, currency)),
 01174            Row("Цена с акциями", "marketing_seller_price", Money(actionPrice, currency)),
 01175            Row("Минимальная цена", "min_price", Money(price.MinPrice, currency)),
 01176            Row("Старая цена", "old_price", Money(price.OldPrice, currency)),
 01177            Row("Розничная цена", "retail_price", Money(retailPrice, currency)),
 01178            Row("Чистая цена", "net_price", Money(netPrice, currency))
 01179        ]);
 1180
 01181        var indexRows = indexes == null
 01182            ? "<div class='price-tooltip-empty'>Индексы Ozon не получены.</div>"
 01183            : string.Join(string.Empty,
 01184            [
 01185                Row("Ozon", "ozon_index_data", $"{Money(indexes.OzonMinPrice, indexes.OzonMinPriceCurrency)} · <em>{inde
 01186                Row("Внешние конкуренты", "external_index_data", $"{Money(indexes.ExternalMinPrice, indexes.ExternalMinP
 01187                Row("Собственные МП", "self_marketplaces_index_data", $"{Money(indexes.SelfMarketplacesMinPrice, indexes
 01188            ]);
 1189
 01190        var serviceRows = string.Join(string.Empty,
 01191        [
 01192            Row("НДС", "vat", $"{vat:N2}%"),
 01193            Row("Автоакции", "auto_action_enabled", price.AutoActionEnabled ? "Включены" : "Выключены"),
 01194            Row("Автодобавление", "auto_add_to_ozon_actions_list_enabled", price.AutoAddToOzonActionsListEnabled ? "Вклю
 01195            acquiring.HasValue ? Row("Эквайринг", "acquiring", Money(acquiring.Value, currency)) : string.Empty
 01196        ]);
 1197
 01198        return $@"
 01199            <div class='price-tooltip-box'>
 01200                <div class='price-tooltip-title'><i class='fas fa-ruble-sign'></i> Проценка Ozon</div>
 01201                <div class='price-tooltip-index'>Ценовой индекс: <b style='color: {indexColor};'>{indexDescription}</b><
 01202                <div class='price-tooltip-section'>Цены из <code>/v5/product/info/prices</code></div>
 01203                <div class='price-tooltip-rows'>{priceRows}</div>
 01204                <div class='price-tooltip-section'>Индексы: минимальная цена · индекс</div>
 01205                <div class='price-tooltip-rows'>{indexRows}</div>
 01206                <div class='price-tooltip-section'>Параметры</div>
 01207                <div class='price-tooltip-rows'>{serviceRows}</div>
 01208            </div>";
 1209    }
 1210
 1211    public string GetPromotionTooltip(OrderOzonPromotionSnapshot snapshot)
 1212    {
 01213        static string Value(decimal value) => value.ToString("N2");
 01214        static string Text(string? value) => System.Net.WebUtility.HtmlEncode(value ?? "—");
 01215        static string Date(DateTime? value) => value?.ToLocalTime().ToString("dd.MM.yyyy HH:mm") ?? "—";
 1216
 01217        var conditionLines = new List<string>();
 01218        foreach (var condition in snapshot.Conditions)
 1219        {
 01220            var valueLine = condition.Value.HasValue
 01221                ? string.Format("<div>Значение условия: <b>{0}</b></div>", Value(condition.Value.Value))
 01222                : string.Empty;
 01223            var actionPriceLine = condition.ActionPrice.HasValue
 01224                ? string.Format("<div>Акционная цена: <b>{0} {1}</b></div>", Value(condition.ActionPrice.Value), Text(sn
 01225                : string.Empty;
 1226
 01227            conditionLines.Add(string.Format(
 01228                "<div class='promotion-tooltip-condition'><div class='promotion-tooltip-condition-title'>{0}</div><div c
 01229                Text(condition.Title),
 01230                Text(condition.SourceType),
 01231                Date(condition.DateFrom),
 01232                Date(condition.DateTo),
 01233                valueLine,
 01234                actionPriceLine));
 1235        }
 1236
 01237        var conditions = conditionLines.Count == 0
 01238            ? "<div class='promotion-tooltip-empty'>Ozon подтвердил участие товара в акции.</div>"
 01239            : string.Join(string.Empty, conditionLines);
 1240
 01241        return $@"
 01242            <div class='promotion-tooltip-box'>
 01243                <div class='promotion-tooltip-title'><i class='fas fa-tags'></i> Акции Ozon</div>
 01244                <div class='promotion-tooltip-prices'>
 01245                    <div><span>Цена</span><b>{Value(snapshot.Price)} {Text(snapshot.CurrencyCode)}</b></div>
 01246                    <div><span>Цена продавца</span><b>{Value(snapshot.MarketingSellerPrice)} {Text(snapshot.CurrencyCode
 01247                    <div><span>Мин. цена</span><b>{Value(snapshot.MinPrice)} {Text(snapshot.CurrencyCode)}</b></div>
 01248                    <div><span>Старая цена</span><b>{Value(snapshot.OldPrice)} {Text(snapshot.CurrencyCode)}</b></div>
 01249                </div>
 01250                <div class='promotion-tooltip-stats'>
 01251                    <span>Комиссия FBS: <b>{Value(snapshot.FbsCommissionPercent)}%</b></span>
 01252                    <span>Эквайринг: <b>{Value(snapshot.Acquiring)} {Text(snapshot.CurrencyCode)}</b></span>
 01253                    <span>Индекс Ozon: <b>{snapshot.OzonPriceIndex:N2}</b></span>
 01254                </div>
 01255                <div class='promotion-tooltip-conditions'>{conditions}</div>
 01256            </div>";
 1257    }
 1258
 1259    public string GetPurchasePriceTooltip(Order item)
 1260    {
 01261        if (item == null)
 01262            return "Цена закупки недоступна";
 1263
 01264        if (item.Supplier?.CurrencyCode == CurrencyCode.RUB)
 01265            return "Цена закупки в рублях";
 1266
 01267        var tooltipLines = new List<string>();
 01268        var supplierCurrency = item.Supplier?.CurrencyCode.ToString() ?? "RUB";
 01269        var originalPurchasePrice = item.OriginalPurchasePrice ?? 0m;
 1270
 01271        tooltipLines.Add($"<div style='font-weight: bold; margin-bottom: 8px;'>Цена закупки: </div>");
 1272
 01273        tooltipLines.Add($@"
 01274        <div style='margin-left: 12px;'>
 01275            <b>{originalPurchasePrice:N2} {supplierCurrency}</b>
 01276        </div>
 01277    ");
 1278
 01279        return $@"
 01280        <div  class='tooltip-box'>
 01281            {string.Join("", tooltipLines)}
 01282        </div>";
 1283    }
 1284
 1285}