| | | 1 | | @using OzonDomains |
| | | 2 | | @using OzonDomains.Models |
| | | 3 | | @using Services.FiltersServices.SortModels |
| | | 4 | | @using OzonOrdersWeb.ViewModels.OrderViewModels |
| | | 5 | | @using OzonOrdersWeb.Areas.Studio2.ViewHelpers |
| | | 6 | | @using Microsoft.AspNetCore.Html |
| | | 7 | | @using System.Net |
| | | 8 | | |
| | | 9 | | @model OrderPageViewModel<Order, OServices.FiltersServices.FilterModels.OrderFilterModel> |
| | | 10 | | |
| | | 11 | | <script src="~/js/copyArticle.js"></script> |
| | | 12 | | <script src="~/js/Studio2/Orders/generateLabel.js"></script> |
| | | 13 | | |
| | | 14 | | @{ |
| | | 15 | | // Текущий sortOrder для определения активной колонки |
| | 0 | 16 | | var currentSort = ViewBag.CurrentSortOrder as OrderSortState? ?? OrderSortState.StandardState; |
| | | 17 | | |
| | | 18 | | // Хелпер для получения текста с условной стрелкой. Возвращает IHtmlContent — Razor рендерит |
| | | 19 | | // его как есть (без повторного экранирования), поэтому подсветку активного столбца сортировки |
| | | 20 | | // можно включить прямо здесь одним местом, без правки ~30 мест вызова: оборачиваем текст+стрелку |
| | | 21 | | // в <span class="sort-col-active">, а CSS :has() уже подсвечивает содержащий th/cell-row/pair — |
| | | 22 | | // источник истины — серверный currentSort, а не клиентский клик+кука (те были рассинхронизированы, |
| | | 23 | | // т.к. большинство th/wrapper вообще без id). |
| | 0 | 24 | | Func<string, OrderSortState, OrderSortState, IHtmlContent> getSortText = (text, ascState, descState) => |
| | 0 | 25 | | { |
| | 0 | 26 | | var encoded = WebUtility.HtmlEncode(text); |
| | 0 | 27 | | if (currentSort == ascState) |
| | 0 | 28 | | return new HtmlString($"<span class=\"sort-col-active\">{encoded}<span class=\"sort-col-arrow\">↑</span></sp |
| | 0 | 29 | | if (currentSort == descState) |
| | 0 | 30 | | return new HtmlString($"<span class=\"sort-col-active\">{encoded}<span class=\"sort-col-arrow\">↓</span></sp |
| | 0 | 31 | | return new HtmlString(encoded); |
| | 0 | 32 | | }; |
| | | 33 | | |
| | 0 | 34 | | Func<string?, string> truncateStatus = text => |
| | 0 | 35 | | { |
| | 0 | 36 | | if (string.IsNullOrEmpty(text)) return text ?? ""; |
| | 0 | 37 | | return text.Length > 20 ? text.Substring(0, 20) + "..." : text; |
| | 0 | 38 | | }; |
| | | 39 | | |
| | 0 | 40 | | var readMap = Model.OrderListFieldReadAccess; |
| | 0 | 41 | | var u = Model.User; |
| | 0 | 42 | | bool L(string field, string leg) => OrderListColumnGate.CanShowOrderColumn(u, readMap, field, leg); |
| | 0 | 43 | | var indexAction = ViewData["IndexAction"] as string ?? "Index"; |
| | 0 | 44 | | var isReturnsPage = string.Equals(indexAction, "Returns", StringComparison.OrdinalIgnoreCase); |
| | 0 | 45 | | var returnVisualStatusOptions = ViewBag.ReturnVisualStatusOptions as Dictionary<string, string> ?? new Dictionary<st |
| | 0 | 46 | | var clientStatusColors = ViewBag.ClientStatusColors as Dictionary<string, string> ?? new Dictionary<string, string>( |
| | 0 | 47 | | var labelExistsMap = ViewData["LabelExistsByShipmentNumber"] as Dictionary<string, bool> |
| | 0 | 48 | | ?? new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); |
| | | 49 | | } |
| | | 50 | | |
| | | 51 | | <div id="filterEnterHint" class="filter-enter-hint" role="status" hidden> |
| | | 52 | | <i class="fas fa-keyboard" aria-hidden="true"></i> |
| | | 53 | | <span>Нажмите Enter для применения фильтра</span> |
| | | 54 | | <button type="button" class="filter-enter-hint-dismiss" aria-label="Закрыть">×</button> |
| | | 55 | | </div> |
| | | 56 | | |
| | | 57 | | <div class="table-container table-edge-flush"> |
| | | 58 | | <table class="table orders-index-table transaction-table@(Model.Items.Any() ? "" : " orders-index-table--empty")" id |
| | | 59 | | |
| | | 60 | | <!-- Каркас для колонок (будет перестроен JavaScript на основе видимых th) --> |
| | | 61 | | <colgroup id="tableColgroup"> |
| | | 62 | | </colgroup> |
| | | 63 | | |
| | 0 | 64 | | <form id="filterForm" asp-action="@indexAction" method="post" data-no-loading> |
| | 0 | 65 | | @Html.AntiForgeryToken() |
| | | 66 | | <!-- Скрытые поля для синхронизации состояния фильтров из навигации --> |
| | | 67 | | <input type="hidden" id="includeArchivedFilterHidden" name="IncludeArchived" |
| | | 68 | | value="@(Model.FilterModel.IncludeArchived ? "true" : "false")"> |
| | | 69 | | <!-- Скрытое поле для авто-синхронизации статусов клиента --> |
| | | 70 | | <input type="hidden" id="syncClientStatusesHidden" name="SyncClientStatusesOnLoad" |
| | | 71 | | value="@(Model.FilterModel.SyncClientStatusesOnLoad ? "true" : "false")"> |
| | | 72 | | <!-- Скрытое поле для сканера штрих-кодов --> |
| | | 73 | | <input type="text" id="barcode" name="barcode" |
| | | 74 | | style="position: fixed; left: -9999px; top: 0; width: 1px; height: 1px; opacity: 0;" |
| | | 75 | | autocomplete="off" tabindex="-1"/> |
| | | 76 | | <!-- Скрытое поле для фильтрации по штрихкоду --> |
| | | 77 | | <input type="hidden" id="barcodeFilter" name="Barcode" value="@Model.FilterModel.Barcode"/> |
| | | 78 | | <input type="hidden" id="globalSearchFilterHidden" name="Search" value="@Model.FilterModel.Search"/> |
| | | 79 | | <!-- Скрытое поле для точного поиска по артикулу --> |
| | | 80 | | <input type="hidden" id="articleExactMatchFilter" name="ArticleExactMatch" |
| | | 81 | | value="@(Model.FilterModel.ArticleExactMatch ? "true" : "false")"/> |
| | | 82 | | <!-- Скрытое поле для группировки по ShipmentId --> |
| | | 83 | | <input type="hidden" id="groupByShipmentFilterHidden" name="GroupByShipment" |
| | | 84 | | value="@(Model.FilterModel.GroupByShipment ? "true" : "false")"/> |
| | | 85 | | <!-- Скрытое поле для фильтра "Только засыл" --> |
| | | 86 | | <input type="hidden" id="onlyWithZasylFilterHidden" name="OnlyWithZasyl" |
| | | 87 | | value="@(Model.FilterModel.OnlyWithZasyl ? "true" : "false")"/> |
| | | 88 | | <!-- Скрытое поле для фильтра "Только возвраты" --> |
| | | 89 | | <input type="hidden" id="onlyWithReturnsFilterHidden" name="OnlyWithReturns" |
| | | 90 | | value="@(Model.FilterModel.OnlyWithReturns ? "true" : "false")"/> |
| | | 91 | | <!-- Скрытое поле для фильтра по штрихкоду возврата --> |
| | | 92 | | <input type="hidden" id="returnLogisticBarcodeFilterHidden" |
| | | 93 | | value="@Model.FilterModel.ReturnLogisticBarcode"/> |
| | | 94 | | <!-- Скрытое поле для фильтра "Только с трек-номером" --> |
| | | 95 | | <input type="hidden" id="onlyWithTrackingNumberFilterHidden" name="OnlyWithTrackingNumber" |
| | | 96 | | value="@(Model.FilterModel.OnlyWithTrackingNumber ? "true" : "false")"/> |
| | | 97 | | <!-- Скрытые поля для фильтров, перенесённых в navbar dropdown --> |
| | | 98 | | <input type="hidden" id="deliveryNameFilterHidden" name="DeliveryName" |
| | | 99 | | value="@(Model.FilterModel.DeliveryName ?? "")"/> |
| | | 100 | | <input type="hidden" id="trackingNumberFilterHidden" name="TrackingNumber" |
| | | 101 | | value="@(Model.FilterModel.TrackingNumber ?? "")"/> |
| | | 102 | | <input type="hidden" id="supplierStatusIdFilterHidden" name="SupplierStatusId" |
| | | 103 | | value="@(Model.FilterModel.SupplierStatusId?.ToString() ?? "")"/> |
| | | 104 | | <input type="hidden" id="hasOzonPromotionFilterHidden" name="HasOzonPromotion" |
| | | 105 | | value="@(Model.FilterModel.HasOzonPromotion?.ToString().ToLowerInvariant() ?? "")"/> |
| | | 106 | | <thead> |
| | | 107 | | <tr> |
| | 0 | 108 | | @if ((L("ShipmentNumber", "Номер заказа") || L("Delivery", "Номер заказа") || L("DeliveryType", "Номер з |
| | | 109 | | { |
| | | 110 | | <th id="thShipmentNumber" data-column="shipmentNumber"> |
| | | 111 | | <div class="two-row-cell"> |
| | 0 | 112 | | @if (L("ShipmentNumber", "Номер заказа")) |
| | | 113 | | { |
| | | 114 | | <div id="thShipmentNumberSort" class="cell-row"> |
| | 0 | 115 | | <a class="text-dark" asp-action="@indexAction" |
| | 0 | 116 | | asp-route-sortOrder="@ViewBag.ShipmentNumberSort"> |
| | 0 | 117 | | <small>@getSortText("Номер заказа", OrderSortState.ShipmentNumberAsc, OrderSortS |
| | | 118 | | </a> |
| | | 119 | | <div class="shipment-drop-zone" title="Перетащите PDF/Excel с номерами отправлений"> |
| | | 120 | | <input asp-for="@Model.FilterModel.ShipmentNumber" id="shipmentNumberFilter" |
| | | 121 | | name="ShipmentNumber" class="form-control form-control-sm" |
| | | 122 | | autocomplete="off"/> |
| | | 123 | | <button type="button" id="shipmentAttachBtn" class="shipment-attach-btn" title=" |
| | | 124 | | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="current |
| | | 125 | | <path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7 |
| | | 126 | | </svg> |
| | | 127 | | </button> |
| | | 128 | | </div> |
| | | 129 | | </div> |
| | | 130 | | } |
| | 0 | 131 | | @if (L("Delivery", "Номер заказа")) |
| | | 132 | | { |
| | | 133 | | <div id="thDeliverySort" class="cell-row"> |
| | 0 | 134 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.Delive |
| | 0 | 135 | | <small>@getSortText("Доставка", OrderSortState.DeliveryAsc, OrderSortState.Deliv |
| | | 136 | | </a> |
| | | 137 | | <div class="position-relative w-100"> |
| | | 138 | | <input asp-for="@Model.FilterModel.Provider" |
| | | 139 | | id="deliveryFilter" |
| | | 140 | | name="Provider" |
| | | 141 | | class="form-control form-control-sm" |
| | | 142 | | autocomplete="off" readonly style="cursor:pointer"/> |
| | | 143 | | <span class="filter-chevron">▾</span> |
| | | 144 | | <ul id="deliveryDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 145 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overfl |
| | | 146 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a>< |
| | 0 | 147 | | @foreach (var wh in ViewBag.Providers) |
| | | 148 | | { |
| | | 149 | | <li> |
| | 0 | 150 | | <a href="#" data-value="@wh.Text" |
| | 0 | 151 | | style="font-weight: normal;"> @wh.Text</a></li> |
| | | 152 | | } |
| | | 153 | | </ul> |
| | | 154 | | </div> |
| | | 155 | | </div> |
| | | 156 | | } |
| | | 157 | | </div> |
| | | 158 | | </th> |
| | 0 | 159 | | @if (L("ShipmentNumber", "Номер заказа")) |
| | | 160 | | { |
| | | 161 | | <th data-column="shipmentActions" class="orders-shipment-actions-col" aria-label="Действия"></th |
| | | 162 | | } |
| | | 163 | | } |
| | | 164 | | <th data-column="client"> |
| | | 165 | | <div class="two-row-cell"> |
| | 0 | 166 | | @if (L("OzonClient", "Клиент")) |
| | | 167 | | { |
| | | 168 | | <div id="thOzonClient" class="cell-row"> |
| | 0 | 169 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.OzonClient" |
| | 0 | 170 | | <small>@getSortText("Клиент", OrderSortState.OzonClientAsc, OrderSortState.OzonClien |
| | | 171 | | </a> |
| | | 172 | | <div class="position-relative w-100"> |
| | | 173 | | <input asp-for="@Model.FilterModel.OzonClient" id="ozonClientFilterInput" |
| | | 174 | | name="OzonClient" |
| | | 175 | | class="form-control form-control-sm" autocomplete="off" readonly style="curso |
| | | 176 | | <span class="filter-chevron">▾</span> |
| | | 177 | | <ul id="ozonClientDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 178 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overflow-y |
| | | 179 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 180 | | @foreach (var client in ViewBag.Clients) |
| | | 181 | | { |
| | | 182 | | <li> |
| | 0 | 183 | | <a href="#" data-value="@client.Value" |
| | 0 | 184 | | style="font-weight: normal;"> @client.Text</a></li> |
| | | 185 | | } |
| | | 186 | | </ul> |
| | | 187 | | </div> |
| | | 188 | | </div> |
| | | 189 | | } |
| | 0 | 190 | | @if (L("DeliveryCity", "Город доставки")) |
| | | 191 | | { |
| | | 192 | | <div id="thDeliveryCity" class="cell-row"> |
| | 0 | 193 | | <a class="text-muted" asp-action="@indexAction" |
| | 0 | 194 | | asp-route-sortOrder="@ViewBag.DeliveryCitySort"> |
| | 0 | 195 | | <small>@getSortText("Город доставки", OrderSortState.DeliveryCityAsc, OrderSortState |
| | | 196 | | </a> |
| | | 197 | | <div class="position-relative w-100"> |
| | | 198 | | <input asp-for="@Model.FilterModel.DeliveryCity" id="deliveryCityFilter" |
| | | 199 | | name="DeliveryCity" |
| | | 200 | | class="form-control form-control-sm" autocomplete="off"/> |
| | | 201 | | <ul id="deliveryCityDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 202 | | style="display: none;"></ul> |
| | | 203 | | </div> |
| | | 204 | | </div> |
| | | 205 | | } |
| | 0 | 206 | | @if (isReturnsPage) |
| | | 207 | | { |
| | | 208 | | <div id="thReturnBarcodeFilter" class="cell-row"> |
| | | 209 | | <input asp-for="@Model.FilterModel.ReturnLogisticBarcode" id="returnLogisticBarcodeInlin |
| | | 210 | | name="ReturnLogisticBarcode" |
| | | 211 | | class="form-control form-control-sm w-100" autocomplete="off" |
| | | 212 | | placeholder="Штрихкод возврата"/> |
| | | 213 | | </div> |
| | | 214 | | } |
| | | 215 | | </div> |
| | | 216 | | </th> |
| | | 217 | | <th data-column="dates"> |
| | | 218 | | <div class="two-row-cell"> |
| | 0 | 219 | | @if (L("ProcessingDate", "Принят в обработку")) |
| | | 220 | | { |
| | | 221 | | <div id="thProcessingDate" class="cell-row"> |
| | 0 | 222 | | <a class="text-dark" asp-action="@indexAction" |
| | 0 | 223 | | asp-route-sortOrder="@ViewBag.ProcessingDateSort"> |
| | 0 | 224 | | <small>@getSortText("Принят", OrderSortState.ProcessingDateAsc, OrderSortState.Proce |
| | | 225 | | </a> |
| | | 226 | | <div class="orders-date-filter" data-orders-date-field="ProcessingDate"> |
| | | 227 | | <input id="processingDateFilter" name="ProcessingDate" form="filterForm" |
| | | 228 | | class="form-control form-control-sm" type="text" readonly data-orders-date-in |
| | | 229 | | value="@(Model.FilterModel.ProcessingDate?.ToString("yyyy-MM-dd") ?? "")" ari |
| | | 230 | | <button type="button" class="orders-date-calendar-toggle" aria-label="Выбрать дату п |
| | | 231 | | <div class="orders-date-calendar" hidden></div> |
| | | 232 | | </div> |
| | | 233 | | </div> |
| | | 234 | | } |
| | 0 | 235 | | @if (L("ShippingDate", "Дата отгрузки")) |
| | | 236 | | { |
| | | 237 | | <div id="thShippingDate" class="cell-row"> |
| | 0 | 238 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.ShippingDa |
| | 0 | 239 | | <small>@getSortText("Дата отгр.", OrderSortState.ShippingDateAsc, OrderSortState.Shi |
| | | 240 | | </a> |
| | | 241 | | <div class="orders-date-filter" data-orders-date-field="ShippingDate"> |
| | | 242 | | <input id="shippingDateFilter" name="ShippingDate" form="filterForm" |
| | | 243 | | class="form-control form-control-sm" type="text" readonly data-orders-date-in |
| | | 244 | | value="@(Model.FilterModel.ShippingDate?.ToString("yyyy-MM-dd") ?? "")" aria- |
| | | 245 | | <button type="button" class="orders-date-calendar-toggle" aria-label="Выбрать дату о |
| | | 246 | | <div class="orders-date-calendar" hidden></div> |
| | | 247 | | </div> |
| | | 248 | | </div> |
| | | 249 | | } |
| | 0 | 250 | | @if (isReturnsPage) |
| | | 251 | | { |
| | | 252 | | <div id="thShippingDate" class="cell-row"> |
| | | 253 | | <a class="text-muted"> |
| | | 254 | | <small>Дата изм.</small> |
| | | 255 | | </a> |
| | | 256 | | <input asp-for="@Model.FilterModel.ReturnStatusChangeDate" |
| | | 257 | | id="shippingDateFilter" |
| | | 258 | | name="ReturnStatusChangeDate" |
| | | 259 | | class="form-control form-control-sm" type="date"/> |
| | | 260 | | </div> |
| | | 261 | | } |
| | | 262 | | </div> |
| | | 263 | | </th> |
| | | 264 | | |
| | | 265 | | <th data-column="period"> |
| | | 266 | | <div class="two-row-cell"> |
| | 0 | 267 | | @if (L("DeliveryPeriod", "Срок доставки")) |
| | | 268 | | { |
| | | 269 | | <div id="thDeliveryPeriod" class="cell-row"> |
| | 0 | 270 | | <a class="text-dark" asp-action="@indexAction" |
| | 0 | 271 | | asp-route-sortOrder="@ViewBag.DeliveryPeriodSort" data-full-title="Срок доставки"> |
| | 0 | 272 | | <small>@getSortText("Срок.", OrderSortState.DeliveryPeriodAsc, OrderSortState.Delive |
| | | 273 | | </a> |
| | | 274 | | <input asp-for="@Model.FilterModel.DeliveryPeriod" name="DeliveryPeriod" |
| | | 275 | | id="deliveryPeriodInput" class="form-control form-control-sm" type="text" |
| | | 276 | | placeholder="1 д. 1 ч."/> |
| | | 277 | | <script> |
| | | 278 | | document.getElementById('deliveryPeriodInput').addEventListener('input', function (e |
| | | 279 | | let input = e.target.value; |
| | | 280 | | |
| | | 281 | | // Проверка и добавление "д." после первой цифры и пробела |
| | | 282 | | if (input.length > 0 && !input.includes('д.') && input.match(/^\d+ $/)) { |
| | | 283 | | e.target.value = input.trim() + ' д. '; |
| | | 284 | | } |
| | | 285 | | |
| | | 286 | | // Проверка и добавление "ч." после второй цифры и пробела |
| | | 287 | | const regexDay = /^\d+ д\. \d+ $/; |
| | | 288 | | if (regexDay.test(input) && !input.includes('ч.')) { |
| | | 289 | | e.target.value = input.trim() + ' ч.'; |
| | | 290 | | } |
| | | 291 | | |
| | | 292 | | // Валидация формата |
| | | 293 | | const pattern = /^\d+ д\. \d+ ч\.$/; |
| | | 294 | | if (!pattern.test(e.target.value)) { |
| | | 295 | | e.target.setCustomValidity('Неверный формат. Используйте формат: X д. Y ч.') |
| | | 296 | | } else { |
| | | 297 | | e.target.setCustomValidity(''); |
| | | 298 | | } |
| | | 299 | | }); |
| | | 300 | | </script> |
| | | 301 | | </div> |
| | | 302 | | } |
| | 0 | 303 | | @if (L("DeliveryPeriod", "Срок доставки")) |
| | | 304 | | { |
| | | 305 | | <div id="thDeliveryPeriod" class="cell-row"> |
| | 0 | 306 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.TimeLeftSo |
| | 0 | 307 | | <small>@getSortText("Остал.", OrderSortState.TimeLeftAsc, OrderSortState.TimeLeftDes |
| | | 308 | | </a> |
| | | 309 | | <input asp-for="@Model.FilterModel.TimeLeftDay" name="TimeLeftDay" id="timeLeftDayInput" |
| | | 310 | | class="form-control form-control-sm" type="number" placeholder="1 д."/> |
| | | 311 | | </div> |
| | | 312 | | } |
| | | 313 | | </div> |
| | | 314 | | </th> |
| | | 315 | | <th class="orders-product-col" data-column="productInfo"> |
| | | 316 | | <div class="two-row-cell w-100"> |
| | | 317 | | <div class="cell-row orders-product-head-row1"> |
| | | 318 | | <div id="thArticle" class="orders-product-pair"> |
| | 0 | 319 | | @if (L("ProductKey", "Key")) |
| | | 320 | | { |
| | 0 | 321 | | <a class="text-dark w-100" asp-action="@indexAction" |
| | 0 | 322 | | asp-route-sortOrder="@ViewBag.ArticleSort"> |
| | 0 | 323 | | <small>@getSortText("Key", OrderSortState.ArticleAsc, OrderSortState.ArticleDesc |
| | | 324 | | </a> |
| | | 325 | | <div class="position-relative w-100"> |
| | | 326 | | <input asp-for="@Model.FilterModel.Article" id="articleFilter" name="Article" |
| | | 327 | | class="form-control form-control-sm" autocomplete="off"/> |
| | | 328 | | <ul id="articleDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 329 | | style="display: none;"></ul> |
| | | 330 | | </div> |
| | | 331 | | } |
| | | 332 | | </div> |
| | | 333 | | <div id="thManufacturer" class="orders-product-pair orders-product-pair-center"> |
| | 0 | 334 | | @if (L("Manufacturer", "Производитель")) |
| | | 335 | | { |
| | 0 | 336 | | <a class="text-dark w-100" asp-action="@indexAction" |
| | 0 | 337 | | asp-route-sortOrder="@ViewBag.ManufacturerSort"> |
| | 0 | 338 | | <small>@getSortText("Производитель", OrderSortState.ManufacturerAsc, OrderSortSt |
| | | 339 | | </a> |
| | | 340 | | <div class="position-relative w-100"> |
| | | 341 | | <input asp-for="@Model.FilterModel.Manufacturer" id="manufacturerFilter" |
| | | 342 | | name="Manufacturer" class="form-control form-control-sm" |
| | | 343 | | autocomplete="off"/> |
| | | 344 | | <ul id="manufacturerDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 345 | | style="display: none;"></ul> |
| | | 346 | | </div> |
| | | 347 | | } |
| | | 348 | | </div> |
| | | 349 | | <div id="thArticle2" class="orders-product-pair orders-product-pair-end"> |
| | 0 | 350 | | @if (L("Article", "Артикул")) |
| | | 351 | | { |
| | 0 | 352 | | <a class="text-dark w-100" asp-action="@indexAction" |
| | 0 | 353 | | asp-route-sortOrder="@ViewBag.ArticleSort"> |
| | 0 | 354 | | <small>@getSortText("Код товара", OrderSortState.ArticleAsc, OrderSortState.Arti |
| | | 355 | | </a> |
| | | 356 | | <div class="position-relative w-100"> |
| | | 357 | | <input id="articleFilter2" |
| | | 358 | | name="Article2" |
| | | 359 | | type="text" |
| | | 360 | | value="@Model.FilterModel.Article" |
| | | 361 | | class="form-control form-control-sm" |
| | | 362 | | autocomplete="off" |
| | | 363 | | data-sync-target="articleFilter"/> |
| | | 364 | | <ul id="article2Dropdown" class="dropdown-menu filter-dropdown-list" |
| | | 365 | | style="display: none;"></ul> |
| | | 366 | | </div> |
| | | 367 | | } |
| | | 368 | | </div> |
| | | 369 | | </div> |
| | | 370 | | |
| | | 371 | | <div id="thProductName" class="cell-row"> |
| | 0 | 372 | | @if (L("ProductName", "Наименование товара")) |
| | | 373 | | { |
| | 0 | 374 | | <a class="text-muted w-100" asp-action="@indexAction" |
| | 0 | 375 | | asp-route-sortOrder="@ViewBag.ProductNameSort"> |
| | 0 | 376 | | <small>@getSortText("Наименование товара", OrderSortState.ProductNameAsc, OrderSortS |
| | | 377 | | </a> |
| | | 378 | | <div class="position-relative w-100"> |
| | | 379 | | <input asp-for="@Model.FilterModel.ProductName" id="productNameFilter" |
| | | 380 | | name="ProductName" class="form-control form-control-sm" |
| | | 381 | | autocomplete="off"/> |
| | | 382 | | <ul id="productNameDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 383 | | style="display: none;"></ul> |
| | | 384 | | </div> |
| | | 385 | | } |
| | | 386 | | </div> |
| | | 387 | | </div> |
| | | 388 | | </th> |
| | | 389 | | <th data-column="checkboxActions" class="checkbox-actions-head"> |
| | | 390 | | <div class="two-row-cell checkbox-column checkbox-actions-stack"> |
| | | 391 | | <div class="cell-row checkbox-actions-top"> |
| | | 392 | | <button type="button" class="btn btn-link hover-btn checkbox-actions-btn" |
| | | 393 | | onclick="clearForm()" title="Очистить все фильтры"> |
| | | 394 | | <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" |
| | | 395 | | class="bi bi-funnel" viewBox="0 0 16 16" aria-hidden="true"> |
| | | 396 | | <path |
| | | 397 | | d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13. |
| | | 398 | | </svg> |
| | | 399 | | <span class="visually-hidden">Очистить все фильтры</span> |
| | | 400 | | </button> |
| | | 401 | | </div> |
| | | 402 | | <div class="cell-row checkbox-actions-middle"> |
| | | 403 | | <div class="dropdown"> |
| | | 404 | | <button type="button" class="btn btn-link hover-btn checkbox-actions-btn" |
| | | 405 | | id="filterHistoryBtn" data-bs-toggle="dropdown" aria-expanded="false" |
| | | 406 | | title="История фильтров"> |
| | | 407 | | <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" |
| | | 408 | | class="bi bi-clock-history" viewBox="0 0 16 16" aria-hidden="true"> |
| | | 409 | | <path |
| | | 410 | | d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022l-.074.997zm2.004.45a7.003 |
| | | 411 | | <path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0z"/> |
| | | 412 | | <path |
| | | 413 | | d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 |
| | | 414 | | </svg> |
| | | 415 | | <span class="visually-hidden">История фильтров</span> |
| | | 416 | | </button> |
| | | 417 | | <ul class="dropdown-menu dropdown-menu-end" id="filterHistoryDropdown" |
| | | 418 | | style="min-width: 300px; max-height: 400px; overflow-y: auto;"> |
| | | 419 | | <li><h6 class="dropdown-header">История фильтров</h6></li> |
| | | 420 | | <li> |
| | | 421 | | <div id="filterHistoryList" class="px-2 py-2"> |
| | | 422 | | <small class="text-muted">История пуста</small> |
| | | 423 | | </div> |
| | | 424 | | </li> |
| | | 425 | | <li> |
| | | 426 | | <hr class="dropdown-divider"> |
| | | 427 | | </li> |
| | | 428 | | <li> |
| | | 429 | | <div class="px-2 py-1"> |
| | | 430 | | <small class="text-muted" id="activeFiltersList">Активных фильтров: 0</small |
| | | 431 | | </div> |
| | | 432 | | </li> |
| | | 433 | | </ul> |
| | | 434 | | </div> |
| | | 435 | | </div> |
| | | 436 | | <div class="cell-row form-check checkbox-actions-select"> |
| | | 437 | | <input class="form-check-input" type="checkbox" id="select-all" aria-label="Выбрать все"> |
| | | 438 | | </div> |
| | | 439 | | </div> |
| | | 440 | | </th> |
| | | 441 | | <th class="status-column-head" data-column="status"> |
| | | 442 | | <div class="two-row-cell"> |
| | 0 | 443 | | @if (L("ClientStatus", "Статус клиента")) |
| | | 444 | | { |
| | | 445 | | <div id="thStatusOzon" class="cell-row"> |
| | 0 | 446 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.StatusSort" |
| | 0 | 447 | | <small>@getSortText("Статус клиента", OrderSortState.StatusAsc, OrderSortState.Statu |
| | | 448 | | </a> |
| | | 449 | | <div class="position-relative w-100"> |
| | | 450 | | <input asp-for="@Model.FilterModel.Status" id="statusFilter" name="Status" |
| | | 451 | | class="form-control form-control-sm" autocomplete="off" readonly style="curso |
| | | 452 | | <span class="filter-chevron">▾</span> |
| | | 453 | | <ul id="statusDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 454 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overflow-y |
| | | 455 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 456 | | @foreach (var stat in ViewBag.Statuses) |
| | | 457 | | { |
| | 0 | 458 | | var isFinal = ViewBag.StatusesIsFinal != null && ViewBag.StatusesIsFinal[sta |
| | 0 | 459 | | string? clientStatusColor = null; |
| | 0 | 460 | | clientStatusColors.TryGetValue(stat.Value, out clientStatusColor); |
| | | 461 | | |
| | | 462 | | <li> |
| | | 463 | | <a href="#" |
| | 0 | 464 | | data-value="@stat.Value" |
| | 0 | 465 | | data-color="@clientStatusColor" |
| | | 466 | | class="@(isFinal ? "final-status-item" : "")" |
| | | 467 | | style="font-weight:@(isFinal ? "600" : "normal");"> |
| | 0 | 468 | | @if (isFinal) |
| | | 469 | | { |
| | | 470 | | <i class="fas fa-flag-checkered me-1 text-warning"></i> |
| | | 471 | | } |
| | 0 | 472 | | @stat.Text |
| | | 473 | | </a> |
| | | 474 | | </li> |
| | | 475 | | } |
| | | 476 | | </ul> |
| | | 477 | | </div> |
| | | 478 | | </div> |
| | | 479 | | } |
| | 0 | 480 | | @if (L("AppStatus", "Статус")) |
| | | 481 | | { |
| | | 482 | | <div id="thStatus" class="cell-row"> |
| | 0 | 483 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.AppStatusI |
| | 0 | 484 | | <small>@getSortText("Статус", OrderSortState.AppStatusIdAsc, OrderSortState.AppStatu |
| | | 485 | | </a> |
| | | 486 | | <div class="position-relative w-100"> |
| | | 487 | | <input asp-for="@Model.FilterModel.AppStatus" id="appStatusFilterInput" |
| | | 488 | | name="AppStatus" |
| | | 489 | | class="form-control form-control-sm" autocomplete="off" readonly style="curso |
| | | 490 | | <span class="filter-chevron">▾</span> |
| | | 491 | | <ul id="appStatusDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 492 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overflow-y |
| | | 493 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 494 | | @foreach (var status in ViewBag.AppStatuses) |
| | | 495 | | { |
| | 0 | 496 | | var color = ViewBag.StatusColors != null ? ViewBag.StatusColors[status.Value |
| | 0 | 497 | | var isFinal = ViewBag.AppStatusIsFinal != null && ViewBag.AppStatusIsFinal[s |
| | | 498 | | |
| | | 499 | | <li> |
| | | 500 | | <a href="#" |
| | 0 | 501 | | data-value="@status.Value" |
| | 0 | 502 | | data-color="@color" |
| | | 503 | | class="@(isFinal ? "final-status-item" : "")" |
| | | 504 | | style="font-weight:@(isFinal ? "600" : "normal");"> |
| | 0 | 505 | | @if (isFinal) |
| | | 506 | | { |
| | | 507 | | <i class="fas fa-flag-checkered me-1 text-warning"></i> |
| | | 508 | | } |
| | 0 | 509 | | @status.Text |
| | | 510 | | </a> |
| | | 511 | | </li> |
| | | 512 | | } |
| | | 513 | | |
| | | 514 | | </ul> |
| | | 515 | | </div> |
| | | 516 | | </div> |
| | | 517 | | } |
| | 0 | 518 | | @if (isReturnsPage) |
| | | 519 | | { |
| | | 520 | | <div id="thReturnStatus" class="cell-row"> |
| | | 521 | | <a class="text-muted"> |
| | | 522 | | <small>Статус возврата</small> |
| | | 523 | | </a> |
| | | 524 | | <div class="position-relative w-100"> |
| | | 525 | | @{ |
| | 0 | 526 | | var returnStatusDisplayValue = ""; |
| | 0 | 527 | | if (!string.IsNullOrWhiteSpace(Model.FilterModel.ReturnStatusSysName)) |
| | | 528 | | { |
| | 0 | 529 | | if (returnVisualStatusOptions.TryGetValue(Model.FilterModel.ReturnStatusSysN |
| | | 530 | | { |
| | 0 | 531 | | returnStatusDisplayValue = displayValue; |
| | | 532 | | } |
| | | 533 | | else |
| | | 534 | | { |
| | 0 | 535 | | returnStatusDisplayValue = Model.FilterModel.ReturnStatusSysName; |
| | | 536 | | } |
| | | 537 | | } |
| | | 538 | | } |
| | | 539 | | <input id="returnStatusFilterInput" |
| | | 540 | | class="form-control form-control-sm" autocomplete="off" |
| | | 541 | | value="@returnStatusDisplayValue"/> |
| | | 542 | | <input type="hidden" id="returnStatusSysNameHidden" name="ReturnStatusSysName" |
| | | 543 | | value="@Model.FilterModel.ReturnStatusSysName"/> |
| | | 544 | | <ul id="returnStatusDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 545 | | style="display: none; max-height: 200px; overflow-y: auto; position: absolute; z |
| | | 546 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 547 | | @foreach (var kv in returnVisualStatusOptions.OrderBy(k => k.Value)) |
| | | 548 | | { |
| | | 549 | | <li> |
| | 0 | 550 | | <a href="#" data-value="@kv.Key" style="font-weight: normal; white-space |
| | | 551 | | </li> |
| | | 552 | | } |
| | | 553 | | </ul> |
| | | 554 | | </div> |
| | | 555 | | <script> |
| | | 556 | | $(document).ready(function () { |
| | | 557 | | $('#returnStatusFilterInput').focus(function () { |
| | | 558 | | $('#returnStatusDropdown').show(); |
| | | 559 | | }); |
| | | 560 | | |
| | | 561 | | $('#returnStatusDropdown').on('click', 'a', function (e) { |
| | | 562 | | e.preventDefault(); |
| | | 563 | | var selectedValue = $(this).data('value'); |
| | | 564 | | var selectedText = $(this).text().trim(); |
| | | 565 | | if (selectedValue === 'Все') { |
| | | 566 | | $('#returnStatusFilterInput').val(''); |
| | | 567 | | $('#returnStatusSysNameHidden').val(''); |
| | | 568 | | } else { |
| | | 569 | | $('#returnStatusFilterInput').val(selectedText); |
| | | 570 | | $('#returnStatusSysNameHidden').val(selectedValue); |
| | | 571 | | } |
| | | 572 | | $('#returnStatusFilterInput').focus(); |
| | | 573 | | $('#returnStatusDropdown').hide(); |
| | | 574 | | updateFilterHighlighting(); |
| | | 575 | | }); |
| | | 576 | | |
| | | 577 | | $(document).on('click', function (e) { |
| | | 578 | | if (!$(e.target).closest('#returnStatusFilterInput').length && !$(e.target). |
| | | 579 | | $('#returnStatusDropdown').hide(); |
| | | 580 | | } |
| | | 581 | | }); |
| | | 582 | | |
| | | 583 | | // Если пользователь печатает вручную, пытаемся преобразовать display_name -> sy |
| | | 584 | | $('#returnStatusFilterInput').on('input', function () { |
| | | 585 | | var currentText = ($(this).val() || '').trim(); |
| | | 586 | | if (!currentText) { |
| | | 587 | | $('#returnStatusSysNameHidden').val(''); |
| | | 588 | | return; |
| | | 589 | | } |
| | | 590 | | var matched = false; |
| | | 591 | | $('#returnStatusDropdown a[data-value]').each(function () { |
| | | 592 | | var optionText = ($(this).text() || '').trim(); |
| | | 593 | | var optionValue = ($(this).data('value') || '').toString().trim(); |
| | | 594 | | if (currentText === optionText) { |
| | | 595 | | $('#returnStatusSysNameHidden').val(optionValue); |
| | | 596 | | matched = true; |
| | | 597 | | return false; |
| | | 598 | | } |
| | | 599 | | }); |
| | | 600 | | if (!matched) { |
| | | 601 | | // Разрешаем и прямой ввод sys_name. |
| | | 602 | | $('#returnStatusSysNameHidden').val(currentText); |
| | | 603 | | } |
| | | 604 | | }); |
| | | 605 | | }); |
| | | 606 | | </script> |
| | | 607 | | </div> |
| | | 608 | | } |
| | | 609 | | </div> |
| | | 610 | | </th> |
| | | 611 | | <th data-column="warehouse"> |
| | | 612 | | <div class="two-row-cell"> |
| | 0 | 613 | | @if (L("ShipmentWarehouse", "Склад отгрузки")) |
| | | 614 | | { |
| | | 615 | | <div id="thShipmentWarehouse" class="cell-row"> |
| | 0 | 616 | | <a class="text-dark" asp-action="@indexAction" |
| | 0 | 617 | | asp-route-sortOrder="@ViewBag.ShipmentWarehouseSort"> |
| | 0 | 618 | | <small>@getSortText("Склад отгрузки", OrderSortState.ShipmentWarehouseIdAsc, OrderSo |
| | | 619 | | </a> |
| | | 620 | | <div class="position-relative w-100"> |
| | | 621 | | <input asp-for="@Model.FilterModel.ShipmentWarehouse" id="shipmentWarehouseFilter" |
| | | 622 | | name="ShipmentWarehouse" class="form-control form-control-sm" |
| | | 623 | | autocomplete="off" readonly style="cursor:pointer"/> |
| | | 624 | | <span class="filter-chevron">▾</span> |
| | | 625 | | <ul id="shipmentWarehouseDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 626 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overflow-y |
| | | 627 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 628 | | @foreach (var warehous in ViewBag.Warehouses) |
| | | 629 | | { |
| | | 630 | | <li> |
| | 0 | 631 | | <a href="#" data-value="@warehous.Value" |
| | 0 | 632 | | style="font-weight: normal;"> @warehous.Text</a></li> |
| | | 633 | | } |
| | | 634 | | </ul> |
| | | 635 | | </div> |
| | | 636 | | </div> |
| | | 637 | | } |
| | 0 | 638 | | @if (L("LastStatusChangeDate", "Срок доставки")) |
| | | 639 | | { |
| | | 640 | | <div id="thDeliveryPeriod" class="cell-row"> |
| | 0 | 641 | | <a class="text-muted" asp-action="@indexAction" |
| | 0 | 642 | | asp-route-sortOrder="@ViewBag.LastStatusChangeDateSort" data-full-title="Дата изменен |
| | 0 | 643 | | <small>@getSortText("Дата изм. статуса", OrderSortState.LastStatusChangeDateAsc, Ord |
| | | 644 | | </a> |
| | | 645 | | <div class="orders-date-filter" data-orders-date-field="LastStatusChangeDate"> |
| | | 646 | | <input id="lastStatusChangeDateFilter" name="LastStatusChangeDate" form="filterForm" |
| | | 647 | | class="form-control form-control-sm" type="text" readonly data-orders-date-in |
| | | 648 | | value="@(Model.FilterModel.LastStatusChangeDate?.ToString("yyyy-MM-dd") ?? "" |
| | | 649 | | <button type="button" class="orders-date-calendar-toggle" aria-label="Выбрать дату и |
| | | 650 | | <div class="orders-date-calendar" hidden></div> |
| | | 651 | | </div> |
| | | 652 | | </div> |
| | | 653 | | } |
| | 0 | 654 | | @if (isReturnsPage) |
| | | 655 | | { |
| | | 656 | | <div id="thReturnWarehouseFilter" class="cell-row"> |
| | | 657 | | <a class="text-muted"> |
| | | 658 | | <small>Склад возврата</small> |
| | | 659 | | </a> |
| | | 660 | | <div class="position-relative w-100"> |
| | | 661 | | <input asp-for="@Model.FilterModel.ReturnWarehouseName" id="returnWarehouseFilter" |
| | | 662 | | name="ReturnWarehouseName" |
| | | 663 | | class="form-control form-control-sm" autocomplete="off" readonly style="curso |
| | | 664 | | <span class="filter-chevron">▾</span> |
| | | 665 | | <ul id="returnWarehouseDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 666 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overflow-y |
| | | 667 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 668 | | @foreach (var warehouse in (IEnumerable<string>)(ViewBag.ReturnWarehouseOptions |
| | | 669 | | { |
| | | 670 | | <li> |
| | 0 | 671 | | <a href="#" data-value="@warehouse" style="font-weight: normal;"> @ |
| | | 672 | | </li> |
| | | 673 | | } |
| | | 674 | | </ul> |
| | | 675 | | </div> |
| | | 676 | | </div> |
| | | 677 | | } |
| | | 678 | | </div> |
| | | 679 | | </th> |
| | | 680 | | <th data-column="supplier"> |
| | | 681 | | <div class="two-row-cell"> |
| | 0 | 682 | | @if (L("Supplier", "Поставщик")) |
| | | 683 | | { |
| | | 684 | | <div id="thSupplier" class="cell-row"> |
| | 0 | 685 | | <a class="text-info" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.SupplierSor |
| | 0 | 686 | | <small>@getSortText("Поставщик", OrderSortState.SupplierAsc, OrderSortState.Supplier |
| | | 687 | | </a> |
| | | 688 | | <div class="position-relative w-100"> |
| | | 689 | | <input asp-for="@Model.FilterModel.Supplier" id="supplierFilter" name="Supplier" |
| | | 690 | | class="form-control form-control-sm" autocomplete="off" readonly style="curso |
| | | 691 | | <span class="filter-chevron">▾</span> |
| | | 692 | | <ul id="supplierDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 693 | | style="display:none; z-index:9999; min-width:160px; max-height:200px; overflow-y |
| | | 694 | | <li><a href="#" data-value="Все" style="font-weight: normal;"> Все</a></li> |
| | 0 | 695 | | @foreach (var supplier in ViewBag.Suppliers) |
| | | 696 | | { |
| | | 697 | | <li> |
| | 0 | 698 | | <a href="#" data-value="@supplier.Value" |
| | 0 | 699 | | style="font-weight: normal;"> @supplier.Text</a></li> |
| | | 700 | | } |
| | | 701 | | </ul> |
| | | 702 | | </div> |
| | | 703 | | </div> |
| | | 704 | | } |
| | 0 | 705 | | @if (L("SupplierOrderNumber", "Номер заказа поставщику")) |
| | | 706 | | { |
| | | 707 | | <div id="thOrderNumberToSupplierSort" class="cell-row"> |
| | 0 | 708 | | <a class="text-info" asp-action="@indexAction" |
| | 0 | 709 | | asp-route-sortOrder="@ViewBag.OrderNumberToSupplierSort" data-full-title="Номер заказ |
| | 0 | 710 | | <small>@getSortText("Номер заказа п.", OrderSortState.OrderNumberToSupplierAsc, Orde |
| | | 711 | | </a> |
| | | 712 | | <input asp-for="@Model.FilterModel.OrderNumberToSupplier" |
| | | 713 | | id="orderNumberToSupplierFilter" |
| | | 714 | | name="OrderNumberToSupplier" class="form-control form-control-sm"/> |
| | | 715 | | </div> |
| | | 716 | | } |
| | | 717 | | </div> |
| | | 718 | | </th> |
| | | 719 | | <th data-column="category"> |
| | | 720 | | <div class="two-row-cell"> |
| | 0 | 721 | | @if (L("Category", "Категория")) |
| | | 722 | | { |
| | | 723 | | <div id="thCategory" class="cell-row"> |
| | 0 | 724 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.CategorySor |
| | 0 | 725 | | <small>@getSortText("Категория", OrderSortState.CategoryAsc, OrderSortState.Category |
| | | 726 | | </a> |
| | | 727 | | <div class="position-relative w-100"> |
| | | 728 | | <input asp-for="@Model.FilterModel.CommercialCategory" id="commercialCategoryFilter" |
| | | 729 | | name="CommercialCategory" class="form-control form-control-sm" autocomplete=" |
| | | 730 | | placeholder="Введите для поиска…" data-lazy-commercial-categories="true"/> |
| | | 731 | | <span class="filter-chevron">▾</span> |
| | | 732 | | <ul id="commercialCategoryDropdown" class="dropdown-menu filter-dropdown-list" |
| | | 733 | | style="display:none; z-index:9999; min-width:200px; max-height:240px; overflow-y |
| | | 734 | | <li class="dropdown-item-text text-muted px-2 py-1"><small>Загрузка…</small></li |
| | | 735 | | </ul> |
| | | 736 | | </div> |
| | | 737 | | </div> |
| | | 738 | | } |
| | 0 | 739 | | @if (L("VolumeWeight", "Объемный вес")) |
| | | 740 | | { |
| | | 741 | | <div id="thVolume" class="cell-row"> |
| | 0 | 742 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.VolumeSort |
| | 0 | 743 | | <small>@getSortText("Вес V", OrderSortState.VolumeAsc, OrderSortState.VolumeDesc)</s |
| | | 744 | | </a> |
| | | 745 | | <input asp-for="@Model.FilterModel.VolumetricWeight" id="volumetricWeightFilter" |
| | | 746 | | name="VolumetricWeight" class="form-control form-control-sm"/> |
| | | 747 | | </div> |
| | | 748 | | } |
| | | 749 | | </div> |
| | | 750 | | </th> |
| | | 751 | | <th data-column="sitePrice"> |
| | | 752 | | <div class="two-row-cell"> |
| | 0 | 753 | | @if (L("SitePrice", "Цена сайта")) |
| | | 754 | | { |
| | | 755 | | <div id="thPurchasePrice" class="cell-row"> |
| | 0 | 756 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.CurrentPric |
| | 0 | 757 | | <small>@getSortText("Цена с.", OrderSortState.CurrentPriceAsc, OrderSortState.Curren |
| | | 758 | | </a> |
| | | 759 | | <input asp-for="@Model.FilterModel.CurrentPriceWithDiscount" |
| | | 760 | | id="currentPriceWithDiscountFilter" name="CurrentPriceWithDiscount" |
| | | 761 | | class="form-control form-control-sm"/> |
| | | 762 | | </div> |
| | | 763 | | } |
| | 0 | 764 | | @if (L("Quantity", "Количество")) |
| | | 765 | | { |
| | | 766 | | <div id="thQuantity" class="cell-row"> |
| | 0 | 767 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.QuantitySo |
| | 0 | 768 | | <small>@getSortText("Кол.", OrderSortState.QuantityAsc, OrderSortState.QuantityDesc) |
| | | 769 | | </a> |
| | | 770 | | <input asp-for="@Model.FilterModel.Quantity" id="quantityFilter" name="Quantity" |
| | | 771 | | class="form-control form-control-sm"/> |
| | | 772 | | </div> |
| | | 773 | | } |
| | | 774 | | </div> |
| | | 775 | | </th> |
| | | 776 | | <th data-column="price"> |
| | | 777 | | <div class="two-row-cell"> |
| | 0 | 778 | | @if (L("Price", "Цена")) |
| | | 779 | | { |
| | | 780 | | <div id="thPrice" class="cell-row"> |
| | 0 | 781 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.PriceSort"> |
| | 0 | 782 | | <small>@getSortText("Цена", OrderSortState.PriceAsc, OrderSortState.PriceDesc)</smal |
| | | 783 | | </a> |
| | | 784 | | <input asp-for="@Model.FilterModel.Price" id="priceFilter" name="Price" |
| | | 785 | | class="form-control form-control-sm"/> |
| | | 786 | | </div> |
| | | 787 | | } |
| | | 788 | | |
| | 0 | 789 | | @if (L("ShipmentTotal", "Сумма отправления")) |
| | | 790 | | { |
| | | 791 | | <div id="thShipmentAmount" class="cell-row"> |
| | 0 | 792 | | <a class="text-muted" asp-action="@indexAction" |
| | 0 | 793 | | asp-route-sortOrder="@ViewBag.ShipmentAmountSort" data-full-title="Сумма отправления" |
| | 0 | 794 | | <small>@getSortText("Сумма о.", OrderSortState.ShipmentAmountAsc, OrderSortState.Shi |
| | | 795 | | </a> |
| | | 796 | | <input asp-for="@Model.FilterModel.ShipmentAmount" id="shipmentAmountFilter" |
| | | 797 | | name="ShipmentAmount" class="form-control form-control-sm"/> |
| | | 798 | | </div> |
| | | 799 | | } |
| | | 800 | | </div> |
| | | 801 | | </th> |
| | | 802 | | <th data-column="purchasePrice"> |
| | | 803 | | <div class="two-row-cell"> |
| | 0 | 804 | | @if (L("PurchasePrice", "Цена закупки")) |
| | | 805 | | { |
| | | 806 | | <div id="thCurrentPrice" class="cell-row"> |
| | 0 | 807 | | <a class="text-info" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.PurchasePri |
| | 0 | 808 | | <small>@getSortText("Цена зак.", OrderSortState.PurchasePriceAsc, OrderSortState.Pur |
| | | 809 | | </a> |
| | | 810 | | <input asp-for="@Model.FilterModel.PurchasePrice" id="purchasePriceFilter" |
| | | 811 | | name="PurchasePrice" class="form-control form-control-sm"/> |
| | | 812 | | </div> |
| | | 813 | | } |
| | 0 | 814 | | @if (L("Cost", "Себестоимость")) |
| | | 815 | | { |
| | | 816 | | <div id="thCostPriceсе" class="cell-row"> |
| | 0 | 817 | | <a class="text-muted" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.CostPriceс |
| | 0 | 818 | | <small>@getSortText("Себест.", OrderSortState.CostPriceAsc, OrderSortState.CostPrice |
| | | 819 | | </a> |
| | | 820 | | <input asp-for="@Model.FilterModel.CostPrice" id="costPriceFilter" name="CostPrice" |
| | | 821 | | class="form-control form-control-sm"/> |
| | | 822 | | </div> |
| | | 823 | | } |
| | | 824 | | </div> |
| | | 825 | | </th> |
| | 0 | 826 | | @if (L("OzonCommission", "Комиссия ОЗОН")) |
| | | 827 | | { |
| | | 828 | | <th id="thOzonCommission" data-column="commission"> |
| | | 829 | | <div class="two-row-cell"> |
| | | 830 | | <div class="cell-row"> |
| | 0 | 831 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.OzonCommiss |
| | 0 | 832 | | <small>@getSortText("Комис.", OrderSortState.MinOzonCommissionAsc, OrderSortState.Mi |
| | | 833 | | </a> |
| | | 834 | | </div> |
| | | 835 | | <div class="cell-row"> |
| | | 836 | | <div style="font-size: 10px;" class="text-muted d-flex gap-1"> |
| | | 837 | | <small>min</small><small>/</small><small>max</small> |
| | | 838 | | </div> |
| | | 839 | | <input asp-for="@Model.FilterModel.OzonCommission" id="ozonCommissionFilter" |
| | | 840 | | name="OzonCommission" class="form-control form-control-sm"/> |
| | | 841 | | </div> |
| | | 842 | | </div> |
| | | 843 | | </th> |
| | | 844 | | } |
| | 0 | 845 | | @if (L("Profit", "Прибыль")) |
| | | 846 | | { |
| | | 847 | | <th id="thProfit" data-column="profit"> |
| | | 848 | | <div class="two-row-cell"> |
| | | 849 | | <div class="cell-row"> |
| | 0 | 850 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.ProfitSort" |
| | 0 | 851 | | <small>@getSortText("Приб.", OrderSortState.ProfitAsc, OrderSortState.ProfitDesc)</s |
| | | 852 | | </a> |
| | | 853 | | </div> |
| | | 854 | | <div class="cell-row"> |
| | | 855 | | <div style="font-size: 10px;" class="text-muted d-flex gap-1"> |
| | | 856 | | <small>min</small><small>/</small><small>max</small> |
| | | 857 | | </div> |
| | | 858 | | <input asp-for="@Model.FilterModel.Profit" id="profitFilter" name="Profit" |
| | | 859 | | class="form-control form-control-sm"/> |
| | | 860 | | </div> |
| | | 861 | | </div> |
| | | 862 | | </th> |
| | | 863 | | } |
| | 0 | 864 | | @if (L("MarkupPercent", "Наценка %")) |
| | | 865 | | { |
| | | 866 | | <th id="thDiscount" data-column="markup"> |
| | | 867 | | <div class="two-row-cell"> |
| | | 868 | | <div class="cell-row"> |
| | 0 | 869 | | <a class="text-dark" asp-action="@indexAction" asp-route-sortOrder="@ViewBag.DiscountSor |
| | 0 | 870 | | <small>@getSortText("Нацен.%", OrderSortState.DiscountAsc, OrderSortState.DiscountDe |
| | | 871 | | </a> |
| | | 872 | | </div> |
| | | 873 | | <div class="cell-row"> |
| | | 874 | | <div style="font-size: 10px;" class="text-muted d-flex gap-1"> |
| | | 875 | | <small>min</small><small>/</small><small>max</small> |
| | | 876 | | </div> |
| | | 877 | | <input asp-for="@Model.FilterModel.Discount" id="discountFilter" name="Discount" |
| | | 878 | | class="form-control form-control-sm"/> |
| | | 879 | | </div> |
| | | 880 | | </div> |
| | | 881 | | </th> |
| | | 882 | | } |
| | | 883 | | |
| | | 884 | | |
| | | 885 | | </tr> |
| | | 886 | | </thead> |
| | | 887 | | <button type="submit" id="submitFilterButton" style="display:none;"></button> |
| | | 888 | | <!-- Скрытая кнопка для отправки формы --> |
| | | 889 | | </form> |
| | | 890 | | <partial name="~/Areas/Studio2/Views/Partials/Orders/_IndexTableBody.cshtml" model="Model" /> |
| | | 891 | | </table> |
| | | 892 | | </div> |
| | | 893 | | |
| | | 894 | | <script> |
| | | 895 | | function setArticleFilter(article) { |
| | | 896 | | var input = document.getElementById('articleFilter'); |
| | | 897 | | if (input) { input.value = article; } |
| | | 898 | | if (typeof window.submitFilterForm === 'function') { |
| | | 899 | | window.submitFilterForm(); |
| | | 900 | | } else { |
| | | 901 | | var btn = document.getElementById('submitFilterButton'); |
| | | 902 | | if (btn) btn.click(); |
| | | 903 | | } |
| | | 904 | | } |
| | | 905 | | |
| | | 906 | | function setDeliveryCityFilter(city) { |
| | | 907 | | var input = document.getElementById('deliveryCityFilter'); |
| | | 908 | | if (input) { input.value = city; } |
| | | 909 | | if (typeof window.submitFilterForm === 'function') { |
| | | 910 | | window.submitFilterForm(); |
| | | 911 | | } else { |
| | | 912 | | var btn = document.getElementById('submitFilterButton'); |
| | | 913 | | if (btn) btn.click(); |
| | | 914 | | } |
| | | 915 | | } |
| | | 916 | | </script> |
| | | 917 | | |
| | | 918 | | @functions { |
| | | 919 | | |
| | | 920 | | public string GetPriceTextColorClass(string? colorIndex) |
| | | 921 | | { |
| | 0 | 922 | | return colorIndex?.ToUpper() switch |
| | 0 | 923 | | { |
| | 0 | 924 | | "SUPER" => "text-success fw-bold", |
| | 0 | 925 | | "GREEN" => "text-success", |
| | 0 | 926 | | "YELLOW" => "text-warning", |
| | 0 | 927 | | "RED" => "text-danger", |
| | 0 | 928 | | "INDEX" => "text-secondary", |
| | 0 | 929 | | "UNSPECIFIED" => "text-muted", |
| | 0 | 930 | | _ => "" |
| | 0 | 931 | | }; |
| | | 932 | | } |
| | | 933 | | |
| | | 934 | | public string GetClientTooltip(OzonClient? client) |
| | | 935 | | { |
| | 0 | 936 | | if (client == null) |
| | 0 | 937 | | return "Информация о клиенте отсутствует"; |
| | | 938 | | |
| | 0 | 939 | | var tooltipLines = new List<string>(); |
| | | 940 | | |
| | | 941 | | // Тип клиента |
| | 0 | 942 | | string clientTypeText = client.ClientType switch |
| | 0 | 943 | | { |
| | 0 | 944 | | ClientType.OZON => "Ozon", |
| | 0 | 945 | | ClientType.YANDEX => "Yandex", |
| | 0 | 946 | | _ => "Другое" |
| | 0 | 947 | | }; |
| | | 948 | | |
| | 0 | 949 | | string clientTypeColor = client.ClientType switch |
| | 0 | 950 | | { |
| | 0 | 951 | | ClientType.OZON => "#007bff", |
| | 0 | 952 | | ClientType.YANDEX => "#dc3545", |
| | 0 | 953 | | _ => "#6c757d" |
| | 0 | 954 | | }; |
| | | 955 | | |
| | 0 | 956 | | tooltipLines.Add($"<div style='font-weight: bold; margin-bottom: 8px;'>Тип клиента: <span style='color: {clientT |
| | | 957 | | |
| | | 958 | | // ИНН |
| | 0 | 959 | | if (!string.IsNullOrEmpty(client.INNCode)) |
| | | 960 | | { |
| | 0 | 961 | | tooltipLines.Add($"<div style='margin: 4px 0;'><b>ИНН:</b> {System.Net.WebUtility.HtmlEncode(client.INNCode) |
| | | 962 | | } |
| | | 963 | | else |
| | | 964 | | { |
| | 0 | 965 | | tooltipLines.Add($"<div style='margin: 4px 0; color: #6c757d;'><b>ИНН:</b> не указан</div>"); |
| | | 966 | | } |
| | | 967 | | |
| | | 968 | | // Склад |
| | 0 | 969 | | if (!string.IsNullOrEmpty(client.WarehouseName)) |
| | | 970 | | { |
| | 0 | 971 | | tooltipLines.Add($"<div style='margin: 4px 0;'><b>Склад:</b> {System.Net.WebUtility.HtmlEncode(client.Wareho |
| | | 972 | | } |
| | | 973 | | else |
| | | 974 | | { |
| | 0 | 975 | | tooltipLines.Add($"<div style='margin: 4px 0; color: #6c757d;'><b>Склад:</b> не указан</div>"); |
| | | 976 | | } |
| | | 977 | | |
| | | 978 | | // Валюта |
| | 0 | 979 | | tooltipLines.Add($"<div style='margin: 4px 0;'><b>Валюта:</b> {client.CurrencyCode}</div>"); |
| | | 980 | | |
| | | 981 | | // Обертка для светлого фона и ширины |
| | 0 | 982 | | return $@" |
| | 0 | 983 | | <div style=' |
| | 0 | 984 | | |
| | 0 | 985 | | background: white; |
| | 0 | 986 | | color: #333; |
| | 0 | 987 | | padding: 12px; |
| | 0 | 988 | | border-radius: 6px; |
| | 0 | 989 | | border: 1px solid #ddd; |
| | 0 | 990 | | box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
| | 0 | 991 | | min-width: 200px; |
| | 0 | 992 | | font-family: Arial, sans-serif; |
| | 0 | 993 | | font-size: 13px; |
| | 0 | 994 | | line-height: 1.4; |
| | 0 | 995 | | '> |
| | 0 | 996 | | {string.Join("", tooltipLines)} |
| | 0 | 997 | | </div>"; |
| | | 998 | | } |
| | | 999 | | |
| | | 1000 | | |
| | | 1001 | | // Метод для получения цвета индекса на основе значения |
| | | 1002 | | public string GetIndexColor(decimal indexValue) |
| | | 1003 | | { |
| | 0 | 1004 | | return indexValue switch |
| | 0 | 1005 | | { |
| | 0 | 1006 | | > 1.2m => "#28a745", // Зеленый - выгодно |
| | 0 | 1007 | | > 0.8m => "#ffc107", // Желтый - умеренно |
| | 0 | 1008 | | _ => "#dc3545" // Красный - невыгодно |
| | 0 | 1009 | | }; |
| | | 1010 | | } |
| | | 1011 | | |
| | | 1012 | | // Метод для получения HEX цвета индекса |
| | | 1013 | | public string GetColorIndexHex(string? colorIndex) |
| | | 1014 | | { |
| | 0 | 1015 | | return colorIndex?.ToUpper() switch |
| | 0 | 1016 | | { |
| | 0 | 1017 | | "SUPER" => "#198754", // Темно-зеленый |
| | 0 | 1018 | | "GREEN" => "#28a745", // Зеленый |
| | 0 | 1019 | | "YELLOW" => "#ffc107", // Желтый |
| | 0 | 1020 | | "RED" => "#dc3545", // Красный |
| | 0 | 1021 | | "INDEX" => "#6c757d", // Серый |
| | 0 | 1022 | | "UNSPECIFIED" => "#6c757d", // Серый |
| | 0 | 1023 | | _ => "#6c757d" // Серый по умолчанию |
| | 0 | 1024 | | }; |
| | | 1025 | | } |
| | | 1026 | | |
| | | 1027 | | |
| | | 1028 | | public string GetColorIndexDescription(string? colorIndex) |
| | | 1029 | | { |
| | 0 | 1030 | | return colorIndex?.ToUpper() switch |
| | 0 | 1031 | | { |
| | 0 | 1032 | | "SUPER" => "Супервыгодный", |
| | 0 | 1033 | | "GREEN" => "Выгодный", |
| | 0 | 1034 | | "YELLOW" => "Умеренный", |
| | 0 | 1035 | | "RED" => "Невыгодный", |
| | 0 | 1036 | | "INDEX" => "Отсутствует", |
| | 0 | 1037 | | "UNSPECIFIED" => "Не определён", |
| | 0 | 1038 | | _ => "Не определён" |
| | 0 | 1039 | | }; |
| | | 1040 | | } |
| | | 1041 | | |
| | | 1042 | | public string GetPriceTooltip(PriceIndexes? priceIndexes) |
| | | 1043 | | { |
| | 0 | 1044 | | if (priceIndexes == null) |
| | 0 | 1045 | | return "Информация о ценовом индексе отсутствует"; |
| | | 1046 | | |
| | 0 | 1047 | | var tooltipLines = new List<string>(); |
| | | 1048 | | |
| | | 1049 | | // Добавляем цветовой индекс с соответствующим цветом |
| | 0 | 1050 | | tooltipLines.Add($"<div style='font-weight: bold; margin-bottom: 8px;'>Ценовой индекс: <span style='color: {GetC |
| | | 1051 | | |
| | | 1052 | | // Добавляем информацию о конкурентах Ozon |
| | 0 | 1053 | | if (priceIndexes.OzonMinPrice > 0) |
| | | 1054 | | { |
| | 0 | 1055 | | tooltipLines.Add($"<div style='margin: 4px 0;'><b>Ozon:</b></div>"); |
| | 0 | 1056 | | tooltipLines.Add($"<div style='margin-left: 12px;'>Минимальная цена: <b>{priceIndexes.OzonMinPrice:N0} {pric |
| | 0 | 1057 | | tooltipLines.Add($"<div style='margin-left: 12px; margin-bottom: 8px;'>Индекс: <span style='color: {GetIndex |
| | | 1058 | | } |
| | | 1059 | | |
| | | 1060 | | // Добавляем информацию о внешних конкурентах |
| | 0 | 1061 | | if (priceIndexes.ExternalMinPrice > 0) |
| | | 1062 | | { |
| | 0 | 1063 | | tooltipLines.Add($"<div style='margin: 4px 0;'><b>Внешние конкуренты:</b></div>"); |
| | 0 | 1064 | | tooltipLines.Add($"<div style='margin-left: 12px;'>Минимальная цена: <b>{priceIndexes.ExternalMinPrice:N0} { |
| | 0 | 1065 | | tooltipLines.Add($"<div style='margin-left: 12px; margin-bottom: 8px;'>Индекс: <span style='color: {GetIndex |
| | | 1066 | | } |
| | | 1067 | | |
| | | 1068 | | // Добавляем информацию о собственных маркетплейсах |
| | 0 | 1069 | | if (priceIndexes.SelfMarketplacesMinPrice > 0) |
| | | 1070 | | { |
| | 0 | 1071 | | tooltipLines.Add($"<div style='margin: 4px 0;'><b>Собственные маркетплейсы:</b></div>"); |
| | 0 | 1072 | | tooltipLines.Add($"<div style='margin-left: 12px;'>Минимальная цена: <b>{priceIndexes.SelfMarketplacesMinPri |
| | 0 | 1073 | | tooltipLines.Add($"<div style='margin-left: 12px;'>Индекс: <span style='color: {GetIndexColor(priceIndexes.S |
| | | 1074 | | } |
| | | 1075 | | |
| | | 1076 | | // Обертка для светлого фона и ширины |
| | 0 | 1077 | | return $@" |
| | 0 | 1078 | | <div class='tooltip-box'> |
| | 0 | 1079 | | {string.Join("", tooltipLines)} |
| | 0 | 1080 | | </div>"; |
| | | 1081 | | } |
| | | 1082 | | |
| | | 1083 | | public string GetPurchasePriceTooltip(Order item) |
| | | 1084 | | { |
| | 0 | 1085 | | if (item == null) |
| | 0 | 1086 | | return "Цена закупки недоступна"; |
| | | 1087 | | |
| | 0 | 1088 | | if (item.Supplier?.CurrencyCode == CurrencyCode.RUB) |
| | 0 | 1089 | | return "Цена закупки в рублях"; |
| | | 1090 | | |
| | 0 | 1091 | | var tooltipLines = new List<string>(); |
| | 0 | 1092 | | var supplierCurrency = item.Supplier?.CurrencyCode.ToString() ?? "RUB"; |
| | 0 | 1093 | | var originalPurchasePrice = item.OriginalPurchasePrice ?? 0m; |
| | | 1094 | | |
| | 0 | 1095 | | tooltipLines.Add($"<div style='font-weight: bold; margin-bottom: 8px;'>Цена закупки: </div>"); |
| | | 1096 | | |
| | 0 | 1097 | | tooltipLines.Add($@" |
| | 0 | 1098 | | <div style='margin-left: 12px;'> |
| | 0 | 1099 | | <b>{originalPurchasePrice:N2} {supplierCurrency}</b> |
| | 0 | 1100 | | </div> |
| | 0 | 1101 | | "); |
| | | 1102 | | |
| | 0 | 1103 | | return $@" |
| | 0 | 1104 | | <div class='tooltip-box'> |
| | 0 | 1105 | | {string.Join("", tooltipLines)} |
| | 0 | 1106 | | </div>"; |
| | | 1107 | | } |
| | | 1108 | | |
| | | 1109 | | } |