< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_Orders_SetNotFullOrdersData
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Orders/SetNotFullOrdersData.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 135
Coverable lines: 135
Total lines: 3293
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 152
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%4160640%
GetContrastColor(...)0%210140%
<ExecuteAsync()100%210%
<ExecuteAsync()0%5550740%
<ExecuteAsync()100%210%

File(s)

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

#LineLine coverage
 1@using OzonOrdersWeb.ViewModels.OrderViewModels
 2
 3@model NotFullOrdersViewModel
 4
 5
 6<link rel="stylesheet" href="~/css/table.css" />
 7<link rel="stylesheet" href="~/css/responsive-tables.css" />
 8<link rel="stylesheet" href="~/css/Studio2/Orders/TransactionsTable.css" />
 9<link rel="stylesheet" href="~/css/Studio2/Orders/productImageCell.css" />
 10<link rel="stylesheet" href="~/css/Studio2/Orders/set-not-full-orders.css" />
 11<style>
 12
 13    th {
 14        white-space: nowrap;
 15    }
 16
 17    .container .row .col-md-4 {
 18        flex: 0 0 auto;
 19        max-width: 33.3333%;
 20    }
 21
 22    .container .custom-width-large {
 23        width: 265px;
 24    }
 25
 26
 27    .custom-white {
 28        background-color: #fff;
 29    }
 30
 31    .custom-gray {
 32        background-color: #585858;
 33        color: #fff;
 34    }
 35
 36        .custom-gray:hover {
 37            color: #fff;
 38        }
 39
 40    .custom-red {
 41        background-color: #DC143C;
 42        color: #fff;
 43    }
 44
 45    .custom-red2 {
 46        background-color: #FF5A75; /* Светлее, чем #DC143C */
 47        color: #fff;
 48    }
 49
 50    .custom-red:hover {
 51        color: #fff;
 52    }
 53
 54    .custom-red2:hover {
 55        color: #fff;
 56    }
 57
 58    /* Стили для раскраски строк таблицы (Bootstrap классы) */
 59    .table-success {
 60        background-color: #d1e7dd !important;
 61    }
 62
 63    .table-danger {
 64        background-color: #f8d7da !important;
 65    }
 66
 67    .table-info {
 68        background-color: #cff4fc !important;
 69    }
 70
 71    .table-warning {
 72        background-color: #fff3cd !important;
 73    }
 74
 75    .table-secondary {
 76        background-color: #e2e3e5 !important;
 77    }
 78
 79    /* Специальный цвет для заказов из Excel */
 80    .table-excel-order {
 81        background-color: #e7d4f5 !important; /* Светло-фиолетовый цвет для отличия */
 82    }
 83
 84    /* Обеспечиваем читаемость текста на цветных фонах */
 85    .table-success,
 86    .table-danger,
 87    .table-info,
 88    .table-warning,
 89    .table-secondary,
 90    .table-excel-order {
 91        color: #000 !important;
 92    }
 93
 94    /* Стили для строк с разными статусами - как на главной странице */
 95    #shipmentTable tbody tr.table-success {
 96        border-left: 3px solid #198754;
 97    }
 98
 99    #shipmentTable tbody tr.table-info {
 100        border-left: 3px solid #0dcaf0;
 101    }
 102
 103    #shipmentTable tbody tr.table-warning {
 104        border-left: 3px solid #ffc107;
 105    }
 106
 107    #shipmentTable tbody tr.table-danger {
 108        border-left: 3px solid #dc3545;
 109    }
 110
 111    #shipmentTable tbody tr.table-secondary {
 112        border-left: 3px solid #6c757d;
 113    }
 114
 115    #shipmentTable tbody tr.custom-red {
 116        border-left: 3px solid #ff6b6b;
 117    }
 118
 119    /* Стили для заголовков таблицы - как на главной странице */
 120    #shipmentTable thead th {
 121        background-color: #f8f9fa;
 122        border-bottom: 2px solid #dee2e6;
 123        font-weight: 600;
 124        vertical-align: middle;
 125        padding: 0.5rem;
 126    }
 127
 128    #shipmentTable thead th.active {
 129        background-color: #f4f4f4;
 130    }
 131
 132    /* Компактные размеры фильтров в заголовках */
 133    #shipmentTable thead input,
 134    #shipmentTable thead select {
 135        padding: 0.2rem 0.35rem;
 136        font-size: 0.85rem;
 137        height: 24px;
 138        line-height: 1.2;
 139    }
 140
 141    /* Улучшение для фильтров в заголовках */
 142    #shipmentTable thead input:focus,
 143    #shipmentTable thead select:focus {
 144        border-color: #0d6efd;
 145        box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
 146    }
 147
 148    .product-popup {
 149        display: none;
 150        position: absolute;
 151        z-index: 1;
 152    }
 153
 154    .product-cell {
 155        font-size: 11px;
 156    }
 157
 158        .product-cell:hover .product-popup {
 159            display: block;
 160        }
 161
 162    #processingDateFilter,
 163    #shippingDateFilter {
 164        width: 70px;
 165    }
 166
 167    #deliveryPeriodInput,
 168    #timeLeftDayInput {
 169        width: 50px;
 170    }
 171
 172    #statusFilter,
 173    #statusFilterHead,
 174    #appStatusFilterInput,
 175    #supplierFilter {
 176        width: 103px;
 177    }
 178
 179    #ozonClientFilterInput,
 180    #deliveryCityFilter {
 181        width: 125px;
 182    }
 183
 184    #currentPriceWithDiscountFilter,
 185    #quantityFilter,
 186    #priceFilter,
 187    #shipmentAmountFilter,
 188    #purchasePriceFilter,
 189    #costPriceFilter,
 190    #ozonCommissionFilter,
 191    #profitFilter,
 192    #discountFilter {
 193        width: 70px;
 194        max-width: 70px;
 195    }
 196
 197    a {
 198        text-decoration: none;
 199    }
 200
 201    .horizontal-line {
 202        border-top: 1px solid #ccc;
 203        margin: 20px 0;
 204    }
 205
 206    #infoWindow {
 207        position: fixed;
 208        display: none;
 209        border: 1px solid #dee2e6;
 210        background-color: #fff;
 211        border-radius: 0.375rem;
 212        box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
 213        padding: 0.5rem 0;
 214        z-index: 9999;
 215        min-width: 200px;
 216        max-width: 300px;
 217    }
 218
 219    #infoWindow .context-menu-item {
 220        display: block;
 221        padding: 0.5rem 1rem;
 222        text-decoration: none;
 223        transition: background-color 0.15s ease-in-out;
 224        border: none;
 225        background: none;
 226        width: 100%;
 227        text-align: left;
 228        font-size: 0.875rem;
 229        cursor: pointer;
 230        color: #212529;
 231    }
 232
 233    #infoWindow .context-menu-item:hover {
 234        background-color: #f8f9fa;
 235        text-decoration: none;
 236    }
 237
 238    #infoWindow .context-menu-item.disabled {
 239        pointer-events: none;
 240        opacity: 0.5;
 241        cursor: default;
 242    }
 243
 244    #infoWindow .context-menu-item.text-danger {
 245        color: #dc3545;
 246    }
 247
 248    #infoWindow .context-menu-item.text-warning {
 249        color: #ffc107;
 250    }
 251
 252    #infoWindow .context-menu-divider {
 253        height: 1px;
 254        margin: 0.5rem 0;
 255        background-color: #dee2e6;
 256        border: none;
 257    }
 258
 259    th.active {
 260        background-color: #f4f4f4; /* Цвет подсветки */
 261    }
 262
 263    .button-container {
 264        display: flex;
 265        gap: 10px; /* Расстояние между кнопками */
 266    }
 267
 268    .horizontal-inputs {
 269        display: flex;
 270        align-items: center;
 271    }
 272
 273        .horizontal-inputs > div {
 274            margin-right: 10px; /* Отступ между элементами */
 275        }
 276
 277    .vertical-align {
 278        display: flex;
 279        flex-direction: column;
 280        align-items: center;
 281        justify-content: space-between;
 282        height: 100%;
 283    }
 284
 285        .vertical-align > * {
 286            margin: 2px 0; /* Минимальный отступ между элементами */
 287        }
 288
 289    /* Центрирование чекбокса в td через table-cell механизм (text-align + vertical-align).
 290       display:flex на td схлапывало ячейку до высоты контента, оставляя белую полосу снизу. */
 291
 292    .nowrap {
 293        white-space: nowrap;
 294    }
 295
 296    .product-cell {
 297        /* Убедитесь, что ширина элемента установлена */
 298        width: 100%; /* Или установите конкретную ширину, если нужно */
 299        /* Обрезка текста и добавление многоточия */
 300        white-space: nowrap;
 301        overflow: hidden;
 302        text-overflow: ellipsis;
 303    }
 304
 305    /* Изначально скрываем кнопку */
 306    .clear-btn {
 307        display: none;
 308        position: absolute;
 309        left: 100%;
 310        transform: translateX(0) translateY(-50%); /* Прижимаем к основной кнопке и центрируем по вертикали */
 311        z-index: 10; /* Поверх других элементов */
 312        top: 50%; /* Центрируем по вертикали относительно главной кнопки */
 313        opacity: 0; /* Прозрачность кнопки */
 314        border: solid 1px;
 315    }
 316
 317        /* Показываем кнопку при наведении на основную или на саму кнопку очистки */
 318        .btn-container:hover .clear-btn,
 319        .clear-btn:hover {
 320            display: block;
 321            opacity: 1; /* Увеличиваем непрозрачность при наведении */
 322            border: solid 1px;
 323        }
 324
 325    /* Обёртка для кнопок */
 326    .btn-container {
 327        position: relative;
 328        display: inline-block;
 329    }
 330
 331    .bg-green-300 {
 332        background-color: #d4edda !important; /* Bootstrap's 'bg-success' color */
 333    }
 334
 335    .placeholder {
 336        min-height: 1em; /* Вы можете настроить минимальную высоту по вашему усмотрению */
 337    }
 338
 339    .highlight {
 340        background-color: #f0f8ff; /* Light blue background */
 341        transition: background-color 0.3s; /* Smooth transition */
 342    }
 343
 344    /* Стили для иконки информации о распределении */
 345    .distribution-info-icon {
 346        cursor: help;
 347        font-size: 0.9rem;
 348        opacity: 0.7;
 349        transition: opacity 0.2s;
 350    }
 351
 352    .distribution-info-icon:hover {
 353        opacity: 1;
 354    }
 355
 356    .d-flex.gap-1 {
 357        gap: 0.25rem;
 358    }
 359
 360    /* Стили для подсветки статусов в select */
 361    .status-select-colored {
 362        padding: 0.25rem 0.5rem;
 363    }
 364
 365    .status-select-colored option {
 366        padding: 4px 8px;
 367        border-radius: 3px;
 368    }
 369
 370    /* Стили для выбранного option в select */
 371    .status-select-colored:focus option:checked {
 372        box-shadow: 0 0 10px 100px rgba(0, 0, 0, 0.1) inset;
 373    }
 374
 375    #page-loader {
 376        position: fixed;
 377        inset: 0;
 378        z-index: 9999;
 379        background: rgba(255, 255, 255, 0.92);
 380        display: flex;
 381        flex-direction: column;
 382        align-items: center;
 383        justify-content: center;
 384        gap: 16px;
 385        transition: opacity 0.3s ease;
 386    }
 387
 388    #page-loader.hidden {
 389        opacity: 0;
 390        pointer-events: none;
 391    }
 392
 393    .page-loader-spinner {
 394        width: 52px;
 395        height: 52px;
 396        border: 5px solid #dee2e6;
 397        border-top-color: #0d6efd;
 398        border-radius: 50%;
 399        animation: page-loader-spin 0.75s linear infinite;
 400    }
 401
 402    @@keyframes page-loader-spin {
 403        to { transform: rotate(360deg); }
 404    }
 405
 406    .page-loader-text {
 407        font-size: 0.95rem;
 408        color: #6c757d;
 409    }
 410</style>
 411
 412<div id="page-loader">
 413    <div class="page-loader-spinner"></div>
 414    <div class="page-loader-text">Загрузка данных...</div>
 415</div>
 416
 417
 418
 419<nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top">
 420    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls=
 421        <span class="navbar-toggler-icon"></span>
 422    </button>
 423    <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
 424        <div class="navbar-nav">
 425            <a class="nav-link" href="#one-match">
 0426                Заказы с одним совпадением (@(Model.OrdersWithOneMatches?.Count ?? 0))
 427            </a>
 428            <a class="nav-link" href="#multiple-matches">
 0429                Заказы с несколькими совпадениями (@(Model.OrdersWithMultipleMatches?.Count ?? 0))
 430            </a>
 431            <a class="nav-link" href="#unique-orders">
 0432                Новые заказы (@(Model.UniqueOrders?.Count ?? 0))
 433            </a>
 434            <a class="nav-link" href="#sendOrders">Отправить заказы</a>
 435            <a class="nav-link" href="#exportToExcel">Экспорт в Excel</a>
 436        </div>
 437    </div>
 438</nav>
 439
 440<div id="messageContainer" class="position-fixed top-0 end-0 p-3" style="z-index: 1050;">
 441    <div id="flashMessage" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
 442        <div class="toast-header">
 443            <strong class="me-auto">Сообщение</strong>
 444            <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
 445        </div>
 446        <div class="toast-body">
 447            <span id="flashMessageText"></span>
 448        </div>
 449    </div>
 450</div>
 451
 452@using OzonDomains.Models
 453@{
 0454    var vm = Model!;
 0455    string updateStatusResult = ViewData["UpdateStatusResult"] as string ?? "";
 0456    string updateStatusResultInfo = ViewData["UpdateStatusResultInfo"] as string ?? "";
 0457    var ordersWithOneMatches = vm.OrdersWithOneMatches ?? new Dictionary<Order, Order>();
 0458    var ordersWithMultipleMatches = vm.OrdersWithMultipleMatches ?? new Dictionary<Order, List<Order>>();
 0459    var uniqueOrders = vm.UniqueOrders ?? new List<Order>();
 0460    var distributionReasons = vm.DistributionReasons ?? new Dictionary<Order, string>();
 0461    var ozonClients = vm.OzonClients ?? new List<OzonClient>();
 0462    var appStatuses = vm.AppStatuses ?? new List<AppStatus>();
 0463    var selectedAppStatus = vm.SelectedAppStatus;
 464
 465    // Создаем словарь цветов статусов
 0466    var statusColors = appStatuses
 0467        .Where(s => s.StatusColor != null && !string.IsNullOrWhiteSpace(s.StatusColor!.ColorCode))
 0468        .ToDictionary(s => s.Id, s => s.StatusColor!.ColorCode);
 469}
 470
 471@functions {
 472    public string GetContrastColor(string hexColor)
 473    {
 0474        if (string.IsNullOrEmpty(hexColor))
 0475            return "#000000";
 476
 0477        hexColor = hexColor.Trim().ToLower();
 478
 0479        if (hexColor == "transparent" || !hexColor.StartsWith("#") || (hexColor.Length != 4 && hexColor.Length != 7))
 0480            return "#000000";
 481
 0482        hexColor = hexColor.TrimStart('#');
 483
 0484        if (hexColor.Length == 3)
 0485            hexColor = string.Format("{0}{0}{1}{1}{2}{2}",
 0486                hexColor[0], hexColor[1], hexColor[2]);
 487
 488        try
 489        {
 0490            int r = Convert.ToInt32(hexColor.Substring(0, 2), 16);
 0491            int g = Convert.ToInt32(hexColor.Substring(2, 2), 16);
 0492            int b = Convert.ToInt32(hexColor.Substring(4, 2), 16);
 493
 0494            double luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
 0495            return luminance > 0.5 ? "#000000" : "#ffffff";
 496        }
 0497        catch
 498        {
 0499            return "#000000";
 500        }
 0501    }
 502}
 0503@if (updateStatusResult != null)
 504{
 505    <div class="alert alert-dismissible alert-success">
 506        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 0507        <p>@Html.Raw(updateStatusResult)</p>
 0508        <p id="statusInfo" class="d-none">@Html.Raw(updateStatusResultInfo)</p>
 509        <button type="button" id="toggleStatusInfo" class="btn btn-link">Подробно...</button>
 510    </div>
 511
 512    <script>
 513        document.getElementById('toggleStatusInfo').addEventListener('click', function () {
 514            var statusInfo = document.getElementById('statusInfo');
 515            if (statusInfo.classList.contains('d-none')) {
 516                statusInfo.classList.remove('d-none');
 517                this.textContent = 'Скрыть информацию';
 518            } else {
 519                statusInfo.classList.add('d-none');
 520                this.textContent = 'Показать информацию';
 521            }
 522        });
 523    </script>
 524
 525}
 526
 527<!-- Модальное окно для выбора статуса при загрузке страницы -->
 528<div class="modal fade" id="selectStatusModal" tabindex="-1" aria-labelledby="selectStatusModalLabel" aria-hidden="true"
 529    <div class="modal-dialog modal-dialog-centered">
 530        <div class="modal-content">
 531            <div class="modal-header">
 532                <h5 class="modal-title" id="selectStatusModalLabel">Выбор статуса для заказов</h5>
 533            </div>
 534            <div class="modal-body">
 535                <p>Пожалуйста, выберите статус для всех заказов в документе:</p>
 536                <select id="initialStatusSelect" class="form-control form-control-lg status-select-colored">
 537                    <option value="">Выберите статус</option>
 0538                    @foreach (var status in Model.AppStatuses)
 539                    {
 0540                        var statusColor = status.StatusColor?.ColorCode ?? "transparent";
 0541                        var textColor = statusColor != "transparent" ? GetContrastColor(statusColor) : "#000000";
 0542                        <option value="@status.Id"
 0543                                data-status-color="@statusColor"
 0544                                data-text-color="@textColor"
 545                                style="background-color: @statusColor; color: @textColor;">
 0546                            @status.Name
 547                        </option>
 548                    }
 549                </select>
 550            </div>
 551            <div class="modal-footer">
 552                <button type="button" class="btn btn-primary" id="applyStatusBtn" disabled>Применить</button>
 553                <button type="button" class="btn btn-secondary" id="skipStatusBtn">Пропустить</button>
 554            </div>
 555        </div>
 556    </div>
 557</div>
 558
 559
 560<div class="container-fluid mt-2">
 561    <h4 class="mb-2">Обработка неполных заказов</h4>
 562<form asp-area="Studio2" asp-controller="Orders" asp-action="SetNotFullOrdersData" method="post">
 563<div class="tx-transaction-page">
 564<div class="table-container table-edge-flush">
 565<table class="table transaction-table mb-0 set-not-full-table" id="shipmentTable">
 566    <colgroup id="tableColgroup"></colgroup>
 567    <thead>
 568        <tr>
 569
 570            <th id="thShipmentNumber" data-column="shipmentNumber">
 571                    <small>№/Номер заказа &nbsp;</small>
 572                    <input id="shipmentNumberFilter" class="form-control form-control-sm" readonly />
 573            </th>
 574            <th data-column="client">
 575
 576                    <div id="thOzonClient">
 577                        <small>Клиент &nbsp; </small>
 578                        <input id="ozonClientFilterInput" class="form-control form-control-sm" autocomplete="off" readon
 579                    </div>
 580
 581                    <div id="thDeliveryCity" class="text-muted" title="Город доставки">
 582                        <small>Город. &nbsp;</small>
 583                        <input id="deliveryCityFilter" class="form-control form-control-sm" readonly />
 584                    </div>
 585            </th>
 586            <th data-column="dates">
 587                    <div id="thProcessingDate" title="Принят в обработку">
 588                        <small>Принят.  &nbsp;</small>
 589                        <input id="processingDateFilter" class="form-control form-control-sm" type="date" readonly />
 590                    </div>
 591
 592                    <div id="thShippingDate" class="text-muted" title="Дата отгрузки">
 593                        <small>Дата отгр.  &nbsp;</small>
 594                        <input id="shippingDateFilter" class="form-control form-control-sm" type="date" readonly />
 595                    </div>
 596            </th>
 597
 598            <th style="width: 20px" data-column="period">
 599                    <div id="thDeliveryPeriod" title="Срок доставки">
 600                        <small>Срок.  &nbsp;</small>
 601                        <input id="deliveryPeriodInput" class="form-control form-control-sm" type="text" placeholder="1 
 602                    </div>
 603
 604                    <div id="thDeliveryPeriod" class="text-muted" title="Осталось времени">
 605                        <small>Остал.  &nbsp;</small>
 606                        <input id="timeLeftDayInput" class="form-control form-control-sm" type="number" placeholder="1 д
 607                    </div>
 608            </th>
 609            <th data-column="status">
 610
 611                    <div id="thStatusOzon">
 612                        <small>Статус клиента  &nbsp;</small>
 613                        <input id="statusFilterHead" class="form-control form-control-sm statusFilterHead" autocomplete=
 614                    </div>
 615
 616                    <div id="thStatus" class="text-muted">
 617                        <div class="text-muted">
 618                            <small>Статус  &nbsp;</small>
 619                            <div>
 620                                <select id="statusSelectUniversal" class="form-control form-control-sm statusFilter stat
 621                                    <option value="">Выберите статус</option>
 0622                                    @foreach (var status in Model.AppStatuses)
 623                                    {
 0624                                        var statusColor = status.StatusColor?.ColorCode ?? "transparent";
 0625                                        var textColor = statusColor != "transparent" ? GetContrastColor(statusColor) : "
 0626                                        <option value="@status.Id"
 0627                                                data-status-color="@statusColor"
 0628                                                data-text-color="@textColor"
 629                                                style="background-color: @statusColor; color: @textColor;">
 0630                                            @status.Name
 631                                        </option>
 632                                    }
 633                                </select>
 634                            </div>
 635                        </div>
 636                    </div>
 637            </th>
 638
 639            <th class="orders-product-col" data-column="productInfo">
 640                <div class="two-row-cell w-100">
 641                    <div class="cell-row orders-product-head-row1">
 642                        <div id="thArticle" class="orders-product-pair">
 643                            <small>Key &nbsp;</small>
 644                            <input id="articleFilter" class="form-control form-control-sm" readonly />
 645                        </div>
 646                        <div id="thManufacturer" class="orders-product-pair orders-product-pair-center">
 647                            <small>Производитель &nbsp;</small>
 648                            <input id="manufacturerFilter" class="form-control form-control-sm" readonly />
 649                        </div>
 650                        <div id="thArticle2" class="orders-product-pair orders-product-pair-end">
 651                            <small>Код товара &nbsp;</small>
 652                            <input id="articleFilter2" class="form-control form-control-sm" readonly />
 653                        </div>
 654                    </div>
 655                    <div id="thProductName" class="cell-row">
 656                        <small class="text-muted">Наименование товара &nbsp;</small>
 657                        <input id="productNameFilter" class="form-control form-control-sm" readonly />
 658                    </div>
 659                </div>
 660            </th>
 661
 662            <th class="checkbox-column" title="Сопоставление заказов" data-column="actionsCol">
 663                <span class="text-muted">Выб.</span>
 664            </th>
 665
 666            <th data-column="warehouse">
 667                    <div id="thShipmentWarehouse">
 668                        <small>Склад отгрузки &nbsp;</small>
 669                        <input id="shipmentWarehouseFilter" class="form-control form-control-sm" autocomplete="off" read
 670                    </div>
 671            </th>
 672            <th data-column="supplier">
 673                    <div id="thSupplier">
 674                        <small>Поставщик &nbsp;</small>
 675                        <input id="supplierFilterHead" class="form-control form-control-sm" autocomplete="off" readonly 
 676                    </div>
 677
 678                    <div id="thOrderNumberToSupplierSort" class="text-muted" title="Номер заказа поставщику">
 679                        <small>Номер зак. п. &nbsp;</small>
 680                        <input type="text" class="form-control form-control-sm universalOrderNumberToSupplie" id="univer
 681                    </div>
 682            </th>
 683            <th data-column="category">
 684                    <div id="thCategory">
 685                        <small>Категория &nbsp;</small>
 686                        <input id="commercialCategoryFilter" class="form-control form-control-sm" readonly />
 687                    </div>
 688
 689                    <div id="thVolume" class="text-muted">
 690                        <small>Вес V &nbsp;</small>
 691                        <input id="volumetricWeightFilter" class="form-control form-control-sm" readonly />
 692                    </div>
 693            </th>
 694            <th data-column="sitePrice">
 695                    <div id="thPurchasePrice" title="Цена сайта">
 696                        <small>Цена с. &nbsp;</small>
 697                        <input id="currentPriceWithDiscountFilter" class="form-control form-control-sm" readonly />
 698                    </div>
 699
 700                    <div id="thQuantity" class="text-muted" title="Количество">
 701                        <small>Кол. &nbsp;</small>
 702                        <input id="quantityFilter" class="form-control form-control-sm" readonly />
 703                    </div>
 704            </th>
 705            <th data-column="price">
 706                    <div id="thPrice">
 707                        <small>Цена &nbsp;</small>
 708                        <input id="priceFilter" class="form-control form-control-sm" readonly />
 709                    </div>
 710
 711                    <div id="thShipmentAmount" class="text-muted" title="Сумма отправления">
 712                        <small>Сумма о. &nbsp;</small>
 713                        <input id="shipmentAmountFilter" class="form-control form-control-sm" readonly />
 714                    </div>
 715            </th>
 716
 717            <th data-column="purchasePrice">
 718                    <div id="thCurrentPrice" title="Цена закупки">
 719                        <small>Цена заку. &nbsp;</small>
 720                        <input id="purchasePriceFilter" class="form-control form-control-sm" readonly />
 721                    </div>
 722
 723                    <div id="thCostPriceсе" class="text-muted" title="Себестоимость">
 724                        <small>Себест. &nbsp;</small>
 725                        <input id="costPriceFilter" class="form-control form-control-sm" readonly />
 726                    </div>
 727            </th>
 728            <th id="thOzonCommission" title="Комиссия ОЗОН" data-column="commission" class="tx-metric-col">
 729                    <div style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
 730                        <div>
 731                            <small>Комис. &nbsp;</small>
 732                        </div>
 733                        <div style="text-align: center; font-size: 10px;" class="text-muted">
 734                            <div><small>min</small></div>
 735                            <div><small>max</small></div>
 736                        </div>
 737                    </div>
 738                    <input id="ozonCommissionFilter" class="form-control form-control-sm" readonly />
 739            </th>
 740            <th id="thProfit" title="Прибыль" data-column="profit" class="tx-metric-col">
 741                    <div style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
 742                        <div>
 743                            <small>Приб. &nbsp;</small>
 744                        </div>
 745                        <div style="text-align: center; font-size: 10px;" class="text-muted">
 746                            <div><small>min</small></div>
 747                            <div><small>max</small></div>
 748                        </div>
 749                    </div>
 750                    <input id="profitFilter" class="form-control form-control-sm" readonly />
 751            </th>
 752
 753            <th id="thDiscount" title="Наценка %" data-column="markup" class="tx-metric-col">
 754                    <div style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
 755                        <div>
 756                            <small>Нацен.% &nbsp;</small>
 757                        </div>
 758                        <div style="text-align: center; font-size: 10px;" class="text-muted">
 759                            <div><small>min</small></div>
 760                            <div><small>max</small></div>
 761                        </div>
 762                    </div>
 763                    <input id="discountFilter" class="form-control form-control-sm" readonly />
 764            </th>
 765        </tr>
 766    </thead>
 767
 768    <tbody>
 769        @{
 0770            int orderNumber = 0;
 0771            decimal grandTotalPurchasePriceRub = 0;
 0772            int grandTotalQuantity = 0;
 0773            var grandTotalByCurrency = new Dictionary<string, decimal>();
 774        }
 0775        @if (ordersWithOneMatches.Count > 0)
 776        {
 777            <tr id="one-match" class="section-header-row">
 778                    <td class="table-primary">
 779                        <b>Заказы с одним совпадением в системе</b>
 780                    </td>
 781                    <td class="table-primary">
 782                        <div id="thOzonClient">
 783                            <select id="ozonClientFilterInputOneMatch" class="form-control form-control-sm" onchange="ha
 784                                <option value="">Выберите клиента</option>
 0785                                @foreach (var client in ozonClients)
 786                                {
 0787                                    <option value="@client.Id">@client.Name</option>
 788                                }
 789                            </select>
 790                        </div>
 791                    </td>
 792
 793                    <td colspan="2" class="table-primary"></td>
 794                    <td class="table-primary">
 795                    </td>
 796                    <td colspan="3" class="table-primary"></td>
 797                    <td class="table-primary">
 798                        <input type="text" class="form-control form-control-sm universalOrderNumberToSupplie" placeholde
 799                    </td>
 800                    <td colspan="7" class="table-primary"></td>
 801            </tr>
 0802            @foreach (var item in ordersWithOneMatches)
 803            {
 0804                grandTotalPurchasePriceRub += (item.Key.PurchasePrice ?? 0) * (item.Key.Quantity ?? 0);
 0805                grandTotalQuantity += (item.Key.Quantity ?? 0);
 0806                var currency = item.Key.Supplier != null ? item.Key.Supplier.CurrencyCode.ToString() : "???";
 0807                var originalPrice = (item.Key.OriginalPurchasePrice ?? 0) * (item.Key.Quantity ?? 0);
 0808                if (grandTotalByCurrency.ContainsKey(currency)) grandTotalByCurrency[currency] += originalPrice;
 0809                else grandTotalByCurrency[currency] = originalPrice;
 810
 0811                distributionReasons.TryGetValue(item.Key, out var reasonOneMatch);
 0812                FileOrderRowViewModel fileOrderRowViewModel = new FileOrderRowViewModel() { Order = item.Key,
 0813                                                                                            NumberInExcel = item.Key.Num
 0814                                                                                            Index = orderNumber,
 0815                                                                                            AppStatuses = appStatuses,
 0816                                                                                            OzonClients = ozonClients,
 0817                                                                                            SelectedAppStatus = selected
 0818                                                                                            DistributionReason = reasonO
 0819                                                                                          };
 820
 0821                @await Html.PartialAsync("_FileOrderRow", fileOrderRowViewModel)
 822
 823                <td colspan="16" style="border-top: 1px solid #585858;">
 824                </td>
 825
 0826                OrderRowViewModel orderRowViewModel = new OrderRowViewModel() { Order = item.Value,
 0827                                                                                Index = orderNumber,
 0828                                                                                OrderWithOneMatches = true,
 0829                                                                                FileOrderManufacturer = item.Key.Manufac
 0830                                                                              };
 0831                @await Html.PartialAsync("_OrderRow", orderRowViewModel)
 832
 833                <td colspan="16" style="border-top: 2px solid black;">
 834                </td>
 0835                orderNumber++;
 0836            }
 837
 838        }
 839        else
 840        {
 841            <tr>
 842                <td colspan="16" class="table-primary">
 843                    <b>Нет заказов с совпадениями</b>
 844                </td>
 845            </tr>
 846        }
 847
 0848        @if (ordersWithMultipleMatches.Count > 0)
 849        {
 850            <tr id="multiple-matches" class="section-header-row">
 851                <td colspan="8" class="table-primary">
 852                    <b>Заказы с несколькими совпадениями в системе</b>
 853                </td>
 854                <td class="table-primary">
 855                    <input type="text" class="form-control form-control-sm universalOrderNumberToSupplie" placeholder="Н
 856                </td>
 857                <td colspan="7" class="table-primary"></td>
 858            </tr>
 0859            @foreach (var item in ordersWithMultipleMatches)
 860            {
 0861                grandTotalPurchasePriceRub += (item.Key.PurchasePrice ?? 0) * (item.Key.Quantity ?? 0);
 0862                grandTotalQuantity += (item.Key.Quantity ?? 0);
 0863                var currency = item.Key.Supplier != null ? item.Key.Supplier.CurrencyCode.ToString() : "???";
 0864                var originalPrice = (item.Key.OriginalPurchasePrice ?? 0) * (item.Key.Quantity ?? 0);
 0865                if (grandTotalByCurrency.ContainsKey(currency)) grandTotalByCurrency[currency] += originalPrice;
 0866                else grandTotalByCurrency[currency] = originalPrice;
 867
 0868                distributionReasons.TryGetValue(item.Key, out var reasonMultiple);
 0869                FileOrderRowViewModel fileOrderRowViewModel = new FileOrderRowViewModel() { Order = item.Key,
 0870                                                                                            NumberInExcel = item.Key.Num
 0871                                                                                            Index = orderNumber,
 0872                                                                                            AppStatuses = appStatuses,
 0873                                                                                            OzonClients = ozonClients,
 0874                                                                                            SelectedAppStatus = selected
 0875                                                                                            DistributionReason = reasonM
 0876                                                                                            };
 877
 0878                @await Html.PartialAsync("_FileOrderRow", fileOrderRowViewModel)
 879
 880                <td colspan="16" style="border-top: 1px solid #585858;">
 881                </td>
 882
 0883                @foreach(var value in item.Value.OrderBy(o => o.ShipmentNumber))
 884                {
 0885                    OrderRowViewModel orderRowViewModel = new OrderRowViewModel() { Order = value, Index = orderNumber, 
 0886                    @await Html.PartialAsync("_OrderRow", orderRowViewModel)
 887                }
 888
 889                <td colspan="16" style="border-top: 2px solid black;">
 890                </td>
 0891                orderNumber++;
 0892            }
 893        }
 894        else
 895        {
 896            <tr>
 897                <td colspan="16">
 898                    <b>Нет заказов с совпадениями</b>
 899                </td>
 900            </tr>
 901        }
 902
 0903        @if (uniqueOrders.Count > 0)
 904        {
 905            <tr id="unique-orders" class="section-header-row">
 906                <td>
 907                    <b>Новые заказы в системе</b>
 908                    <br />
 909                    <input class="form-control form-control-sm" id="shipmentNumberFilterInput" placeholder="Номер заказа
 910                </td>
 911                <td>
 912                    <div id="thOzonClient">
 913                        <select id="ozonClientFilterInputUnique" class="form-control form-control-sm" onchange="handleOz
 914                            <option value="">Выберите клиента</option>
 0915                                @foreach (var client in ozonClients)
 916                                {
 0917                                <option value="@client.Id">@client.Name</option>
 918                                }
 919                        </select>
 920                    </div>
 921                </td>
 922
 923                <td colspan="2"></td>
 924                <td>
 925                    <div class="form-check">
 926                        <input class="form-check-input" type="checkbox" id="skipUniqueOrders" onchange="toggleUniqueOrde
 927                        <label class="form-check-label" for="skipUniqueOrders" style="font-size: 0.875rem;">
 928                            Не проводить в системе
 929                        </label>
 930                    </div>
 931                </td>
 932                <td colspan="3"></td>
 933                <td>
 934                    <input type="text" class="form-control form-control-sm universalOrderNumberToSupplie" placeholder="Н
 935                </td>
 936                <td colspan="7"></td>
 937            </tr>
 0938                @foreach(var item in uniqueOrders)
 939                {
 0940                    grandTotalPurchasePriceRub += (item.PurchasePrice ?? 0) * (item.Quantity ?? 0);
 0941                    grandTotalQuantity += (item.Quantity ?? 0);
 0942                    var currency = item.Supplier != null ? item.Supplier.CurrencyCode.ToString() : "???";
 0943                    var originalPrice = (item.OriginalPurchasePrice ?? 0) * (item.Quantity ?? 0);
 0944                    if (grandTotalByCurrency.ContainsKey(currency)) grandTotalByCurrency[currency] += originalPrice;
 0945                    else grandTotalByCurrency[currency] = originalPrice;
 946
 0947                    distributionReasons.TryGetValue(item, out var reasonUnique);
 0948                    FileOrderRowViewModel fileOrderRowViewModel = new FileOrderRowViewModel() { Order = item,
 0949                                                                                                NumberInExcel = item.Num
 0950                                                                                                Index = orderNumber,
 0951                                                                                                AppStatuses = appStatuse
 0952                                                                                                OzonClients = ozonClient
 0953                                                                                                UniqueOrder = true,
 0954                                                                                                SelectedAppStatus = sele
 0955                                                                                                DistributionReason = rea
 0956                                                                                                };
 0957                    @await Html.PartialAsync("_FileOrderRow", fileOrderRowViewModel)
 0958                    orderNumber++;
 959                }
 960        }
 961        else
 962        {
 963            <tr>
 964                <td colspan="16">
 965                    <b>Новых заказов нет</b>
 966                </td>
 967            </tr>
 968        }
 969        <!-- Общая итоговая строка для всех заказов -->
 0970        @if (grandTotalQuantity > 0)
 971        {
 972            <tr id="grand-total-summary-row" class="table-info font-weight-bold" style="background-color: #d1ecf1;">
 973                <td colspan="10" class="text-right"><b>Общий итог:</b></td>
 974                <td class="text-right" style="white-space: nowrap;">
 0975                    <b>@grandTotalQuantity</b>
 976                </td>
 977                <td></td>
 978                <td class="text-right" style="white-space: nowrap;">
 0979                    @foreach(var cur in grandTotalByCurrency)
 980                    {
 981                        <div style="font-size: 0.85em; white-space: nowrap;">
 0982                            @Html.Raw(string.Format("{0:N0}", cur.Value).Replace(",", " ")) @cur.Key
 983                        </div>
 984                    }
 985                    <div style="border-top: 1px solid #dee2e6; margin-top: 2px; padding-top: 2px;">
 0986                        <b>@Html.Raw(string.Format("{0:N0}", grandTotalPurchasePriceRub).Replace(",", " ")) RUB</b>
 987                    </div>
 988                </td>
 989                <td colspan="3"></td>
 990            </tr>
 991        }
 992    </tbody>
 993
 994</table>
 995</div>
 996</div>
 997    <input type="hidden" id="toExcel" name="toExcel" value="false" />
 998    <input type="hidden" id="onlyNewOrders" name="onlyNewOrders" value="false" />
 999    <input type="hidden" name="userName" value="@(User.Identity?.Name ?? "")" />
 1000    <br />
 1001    <div class="d-flex gap-2 flex-wrap">
 1002        <button id="sendOrders" type="submit" class="btn btn-primary" onclick="setToExcel(false); setOnlyNewOrders(false
 1003        <button id="exportToExcel" type="submit" class="btn btn-primary" onclick="setToExcel(true); setOnlyNewOrders(fal
 01004        @if (Model.UniqueOrders != null && Model.UniqueOrders.Count > 0)
 1005        {
 1006            <button id="exportNewOrdersToExcel" type="submit" class="btn btn-success" onclick="setToExcel(true); setOnly
 1007                <i class="fas fa-file-excel me-1"></i>Распечатать новые заказы в Excel
 1008            </button>
 1009        }
 1010    </div>
 1011
 1012</form>
 1013</div>
 1014
 1015<!-- Контекстное меню для заказов из системы -->
 1016<div id="infoWindow" style="display: none;"></div>
 1017
 1018<script src="~/js/table-sync.js" asp-append-version="true"></script>
 1019<script src="~/js/Studio2/Orders/set-not-full-orders.js" asp-append-version="true"></script>
 1020
 1021<script>
 1022    // Уникальный ключ партии (для различения различных загруженных файлов)
 01023    const currentBatchKey = '@(ViewBag.NotFullBatchKey ?? "")';
 1024    const storageBatchToken = currentBatchKey ? encodeURIComponent(currentBatchKey) : 'default';
 1025    const stateStorageKey = `setNotFullOrdersDataState:${storageBatchToken}`;
 1026    const statusModalSeenKey = `setNotFullOrdersDataStatusModalSeen:${storageBatchToken}`;
 1027
 1028    // Имя Excel-файла для авто-генерации номера отправления
 01029    const _excelRawFileName = '@System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode((string)(ViewBag.ExcelFileNam
 1030    const _excelFileBase = _excelRawFileName
 1031        .replace(/\.[^.]+$/, '')           // убрать расширение
 1032        .toLowerCase()
 1033        .replace(/[^a-zа-яёa-z0-9]+/gi, '_') // не-буквы/цифры → _
 1034        .replace(/_+/g, '_')               // схлопнуть двойные _
 1035        .replace(/^_|_$/g, '');            // убрать _ по краям
 1036
 1037    function buildShipmentNumber(rowNum) {
 1038        if (!_excelFileBase) return '';
 1039        return `${_excelFileBase}_${rowNum}`;
 1040    }
 1041
 1042    document.addEventListener('DOMContentLoaded', function () {
 1043        // Авто-заполнение пустых полей ShipmentNumber в уникальных и новых заказах
 1044        document.querySelectorAll('input.orderNumberToSupplie-field[data-number-in-excel]').forEach(function (inp) {
 1045            if (!inp.value) {
 1046                inp.value = buildShipmentNumber(inp.dataset.numberInExcel);
 1047            }
 1048        });
 1049    });
 1050
 1051    function hasStatusModalBeenSeen() {
 1052        try {
 1053            return localStorage.getItem(statusModalSeenKey) === '1';
 1054        } catch (e) {
 1055            return false;
 1056        }
 1057    }
 1058
 1059    function markStatusModalSeen() {
 1060        try {
 1061            localStorage.setItem(statusModalSeenKey, '1');
 1062        } catch (e) {
 1063            // ignore localStorage errors
 1064        }
 1065    }
 1066
 1067    let serverMaxIndex = null;
 1068    let saveStateTimer = null;
 1069    const SAVE_DEBOUNCE_MS = 500;
 1070    // Флаг автопроставления — пока true, изменения чекбоксов не считаются пользовательскими
 1071    let _autoCheckPhase = false;
 1072    // Индексы строк, которые пользователь изменил вручную
 1073    const _userModifiedIndices = new Set();
 1074
 1075    function getServerMaxIndex() {
 1076        if (serverMaxIndex != null) return serverMaxIndex;
 1077        let max = -1;
 1078        document.querySelectorAll('#shipmentTable tr.file-order-row').forEach(tr => {
 1079            const idx = tr.getAttribute('data-index');
 1080            if (idx != null && !tr.getAttribute('data-source-index')) {
 1081                const n = parseInt(idx, 10);
 1082                if (!isNaN(n) && n > max) max = n;
 1083            }
 1084            tr.querySelectorAll('input, select').forEach(el => {
 1085                if (el.name) {
 1086                    const m = el.name.match(/NotFullOrders\[(\d+)\]/);
 1087                    if (m && !tr.getAttribute('data-source-index')) {
 1088                        const n = parseInt(m[1], 10);
 1089                        if (!isNaN(n) && n > max) max = n;
 1090                    }
 1091                }
 1092            });
 1093        });
 1094        serverMaxIndex = max >= 0 ? max : 0;
 1095        return serverMaxIndex;
 1096    }
 1097
 1098    function collectState() {
 1099        const form = document.querySelector('form[action*="SetNotFullOrdersData"]');
 1100        if (!form) return null;
 1101        const formData = {};
 1102        form.querySelectorAll('input, select, textarea').forEach(el => {
 1103            if (el.name && el.name.includes('NotFullOrders')) {
 1104                const val = el.type === 'checkbox' ? el.checked : (el.value || '');
 1105                formData[el.name] = val;
 1106            }
 1107        });
 1108        const orderIds = {};
 1109        form.querySelectorAll('input[type="hidden"][name*="OrderIds"]').forEach(inp => {
 1110            if (inp.name && inp.name.includes('NotFullOrders') && !inp.name.includes('Order.OrderIds')) {
 1111                const m = inp.name.match(/NotFullOrders\[(\d+)\]/);
 1112                if (m) orderIds[m[1]] = inp.value || '';
 1113            }
 1114        });
 1115        document.querySelectorAll('input.record-checkbox:checked').forEach(cb => {
 1116            const idx = (cb.getAttribute('data-index') || '').split('-')[0];
 1117            if (idx) {
 1118                const ids = (orderIds[idx] || '').split(',').filter(Boolean);
 1119                if (cb.value && !ids.includes(cb.value)) ids.push(cb.value);
 1120                orderIds[idx] = ids.join(',');
 1121            }
 1122        });
 1123        const hiddenRows = [];
 1124        document.querySelectorAll('tr.file-order-row').forEach(tr => {
 1125            const hiddenInput = tr.querySelector('input[name*="Hidden"]');
 1126            if (hiddenInput && hiddenInput.value === 'True') {
 1127                const m = tr.querySelector('input[name*="NotFullOrders"]')?.name?.match(/\[(\d+)\]/);
 1128                if (m) hiddenRows.push(parseInt(m[1], 10));
 1129            }
 1130        });
 1131        const clones = [];
 1132        const smax = getServerMaxIndex();
 1133        document.querySelectorAll('tr.file-order-row').forEach(tr => {
 1134            const srcIdx = tr.getAttribute('data-source-index');
 1135            if (!srcIdx) return;
 1136            const firstInput = tr.querySelector('input[name*="NotFullOrders"]');
 1137            if (!firstInput || !firstInput.name) return;
 1138            const m = firstInput.name.match(/NotFullOrders\[(\d+)\]/);
 1139            if (!m) return;
 1140            const newIdx = parseInt(m[1], 10);
 1141            const rowData = {};
 1142            tr.querySelectorAll('input, select, textarea').forEach(el => {
 1143                if (el.name && el.name.includes('NotFullOrders')) {
 1144                    rowData[el.name] = el.type === 'checkbox' ? el.checked : (el.value || '');
 1145                }
 1146            });
 1147            clones.push({ sourceIndex: parseInt(srcIdx, 10), newIndex: newIdx, data: rowData });
 1148        });
 1149        const skipCb = document.getElementById('skipUniqueOrders');
 1150        return {
 1151            batchKey: currentBatchKey,
 1152            serverMaxIndex: smax,
 1153            formData,
 1154            orderIds,
 1155            hiddenRows,
 1156            clones,
 1157            userModifiedIndices: [..._userModifiedIndices],
 1158            skipUniqueOrders: skipCb ? skipCb.checked : false,
 1159            scrollY: window.scrollY || document.documentElement.scrollTop || 0
 1160        };
 1161    }
 1162
 1163    function saveState() {
 1164        if (!currentBatchKey) return;
 1165        const state = collectState();
 1166        if (state) {
 1167            try {
 1168                localStorage.setItem(stateStorageKey, JSON.stringify(state));
 1169            } catch (e) { /* quota exceeded */ }
 1170        }
 1171    }
 1172
 1173    window.addEventListener('beforeunload', function() {
 1174        saveState();
 1175    });
 1176
 1177    function debouncedSaveState() {
 1178        if (saveStateTimer) clearTimeout(saveStateTimer);
 1179        saveStateTimer = setTimeout(saveState, SAVE_DEBOUNCE_MS);
 1180    }
 1181
 1182    function applySavedState(state) {
 1183        if (!state || !state.batchKey || state.batchKey !== currentBatchKey) return;
 1184        const form = document.querySelector('form[action*="SetNotFullOrdersData"]');
 1185        if (!form) return;
 1186        if (state.serverMaxIndex != null) serverMaxIndex = state.serverMaxIndex;
 1187
 1188        // Восстанавливаем набор индексов, изменённых пользователем вручную
 1189        const userModifiedSet = new Set((state.userModifiedIndices || []).map(String));
 1190        (state.userModifiedIndices || []).forEach(idx => _userModifiedIndices.add(String(idx)));
 1191
 1192        if (state.formData) {
 1193            for (const [name, val] of Object.entries(state.formData)) {
 1194                const el = form.querySelector(`[name="${name}"]`);
 1195                if (el) {
 1196                    if (el.type === 'checkbox') {
 1197                        // record-checkbox: восстанавливаем только строки, изменённые пользователем
 1198                        if (el.classList.contains('record-checkbox')) {
 1199                            const m = name.match(/NotFullOrders\[(\d+)\]/);
 1200                            if (m && !userModifiedSet.has(m[1])) continue;
 1201                        }
 1202                        el.checked = !!val;
 1203                    } else {
 1204                        el.value = val;
 1205                        // Если это поле "Номер заказа поставщику" — синхронизируем остальные строки
 1206                        if (el.classList.contains('universalOrderNumberToSupplie') && val) {
 1207                            document.querySelectorAll('input.universalOrderNumberToSupplie').forEach(function(other) {
 1208                                if (other !== el) other.value = val;
 1209                            });
 1210                        }
 1211                    }
 1212                }
 1213            }
 1214        }
 1215        if (state.orderIds) {
 1216            for (const [idx, idsStr] of Object.entries(state.orderIds)) {
 1217                // Пропускаем строки без ручных изменений — их уже обработало автопроставление
 1218                if (!userModifiedSet.has(idx)) continue;
 1219                const ids = (idsStr || '').split(',').filter(Boolean);
 1220                const orderIdsInput =
 1221                    form.querySelector(`input[type="hidden"][name="NotFullOrders[${idx}].OrderIds"]`) ||
 1222                    form.querySelector(`input[name="NotFullOrders[${idx}].OrderIds"]`);
 1223                if (orderIdsInput) orderIdsInput.value = idsStr || '';
 1224                document.querySelectorAll(`input.record-checkbox[data-index^="${idx}-"]`).forEach(cb => {
 1225                    cb.checked = ids.includes(cb.value);
 1226                    cb.disabled = false;
 1227                    if (cb.checked) {
 1228                        const dataOrder = cb.getAttribute('data-order');
 1229                        if (dataOrder) {
 1230                            document.querySelectorAll(`.record-checkbox[data-order="${dataOrder}"]`).forEach(other => {
 1231                                if (other !== cb) other.disabled = true;
 1232                            });
 1233                        }
 1234                    }
 1235                });
 1236            }
 1237        }
 1238
 1239        // Re-run checkbox side effects только для строк, изменённых пользователем
 1240        if (state.orderIds) {
 1241            for (const [idx, idsStr] of Object.entries(state.orderIds)) {
 1242                if (!userModifiedSet.has(idx)) continue;
 1243                const ids = (idsStr || '').split(',').filter(Boolean);
 1244                const parsedIdx = parseInt(idx, 10);
 1245                if (isNaN(parsedIdx)) continue;
 1246
 1247                document.querySelectorAll(`input.record-checkbox[data-index^="${idx}-"]`).forEach(cb => {
 1248                    if (cb.checked && ids.includes(cb.value)) {
 1249                        updateOrderIds(cb, parsedIdx);
 1250                    }
 1251                });
 1252            }
 1253        }
 1254
 1255        if (state.clones && state.clones.length) {
 1256            const summaryRow = document.getElementById('grand-total-summary-row');
 1257            const tbody = summaryRow ? summaryRow.parentNode : form.querySelector('tbody');
 1258            state.clones.sort((a, b) => a.newIndex - b.newIndex);
 1259            state.clones.forEach(c => {
 1260                const sourceRow = document.querySelector(`tr.file-order-row[data-index="${c.sourceIndex}"]`);
 1261                if (!sourceRow || sourceRow.getAttribute('data-source-index')) return;
 1262                const newRow = sourceRow.cloneNode(true);
 1263                const newIndex = c.newIndex;
 1264                newRow.setAttribute('data-source-index', String(c.sourceIndex));
 1265                newRow.setAttribute('data-index', String(newIndex));
 1266                newRow.id = 'selectRowForMerge_' + newIndex;
 1267                newRow.querySelectorAll('input, select, textarea').forEach(el => {
 1268                    if (el.name) el.name = el.name.replace(/\[\d+\]/, `[${newIndex}]`);
 1269                    if (el.id) el.id = el.id.replace(new RegExp('_' + c.sourceIndex + '(?![\\d])', 'g'), '_' + newIndex)
 1270                });
 1271                newRow.querySelectorAll('[id]').forEach(el => {
 1272                    if (el.id && el.id.includes('_' + c.sourceIndex))
 1273                        el.id = el.id.replace(new RegExp('_' + c.sourceIndex + '(?![\\d])', 'g'), '_' + newIndex);
 1274                });
 1275                const btn = newRow.querySelector('.moveToNewButton');
 1276                if (btn) {
 1277                    const inp = document.createElement('input');
 1278                    inp.className = 'form-control form-control-sm orderNumberToSupplie-field';
 1279                    inp.name = `NotFullOrders[${newIndex}].Order.ShipmentNumber`;
 1280                    inp.id = `shipmentNumber_${newIndex}`;
 1281                    inp.placeholder = 'Номер заказа';
 1282                    // ShipmentNumber из сохранённого состояния будет применён ниже через c.data;
 1283                    // если нет — авто-номер из имени файла + строки Excel
 1284                    const numberInExcel = newRow.dataset.numberInExcel;
 1285                    inp.value = buildShipmentNumber(numberInExcel || c.sourceIndex);
 1286                    btn.replaceWith(inp);
 1287                }
 1288                let isUniqueInput = newRow.querySelector('input[name*="IsUniqueOrder"]');
 1289                if (!isUniqueInput) {
 1290                    isUniqueInput = document.createElement('input');
 1291                    isUniqueInput.type = 'hidden';
 1292                    newRow.appendChild(isUniqueInput);
 1293                }
 1294                isUniqueInput.name = `NotFullOrders[${newIndex}].IsUniqueOrder`;
 1295                isUniqueInput.value = 'true';
 1296                let orderIdsInp = newRow.querySelector('input[type="hidden"][name*="OrderIds"]:not([name*="Order.OrderId
 1297                if (!orderIdsInp) {
 1298                    // Backward-compatible fallback (но основной путь - hidden input)
 1299                    orderIdsInp = newRow.querySelector('input[name*="OrderIds"]:not([name*="Order.OrderIds"])');
 1300                }
 1301                if (!orderIdsInp) {
 1302                    orderIdsInp = document.createElement('input');
 1303                    orderIdsInp.type = 'hidden';
 1304                    newRow.appendChild(orderIdsInp);
 1305                }
 1306                orderIdsInp.name = `NotFullOrders[${newIndex}].OrderIds`;
 1307                orderIdsInp.value = '';
 1308                if (c.data) {
 1309                    for (const [name, val] of Object.entries(c.data)) {
 1310                        const nn = name.replace(/\[\d+\]/, `[${newIndex}]`);
 1311                        const el = newRow.querySelector(`[name="${nn}"]`);
 1312                        if (el) {
 1313                            if (el.type === 'checkbox') el.checked = !!val;
 1314                            else el.value = val;
 1315                        }
 1316                    }
 1317                }
 1318                const clientContainer = newRow.querySelector('[id*="clientContainer"]');
 1319                if (clientContainer) {
 1320                    clientContainer.id = 'clientContainer_' + newIndex;
 1321                    clientContainer.style.display = 'block';
 1322                }
 1323                if (summaryRow) tbody.insertBefore(newRow, summaryRow);
 1324                else tbody.appendChild(newRow);
 1325                const srcBtn = document.querySelector(`#newOrderButton_${c.sourceIndex}`);
 1326                if (srcBtn) srcBtn.style.display = 'none';
 1327            });
 1328        }
 1329        if (state.hiddenRows && state.hiddenRows.length) {
 1330            const allTrs = Array.from(document.querySelectorAll('#shipmentTable tbody tr'));
 1331            state.hiddenRows.forEach(idx => {
 1332                const idxStr = String(idx);
 1333                const fileRow = allTrs.find(tr => tr.getAttribute('data-index') === idxStr && tr.classList.contains('fil
 1334                if (!fileRow) return;
 1335                const hiddenInput = fileRow.querySelector('input[name*="Hidden"]');
 1336                if (hiddenInput) hiddenInput.value = 'True';
 1337                fileRow.style.backgroundColor = '#e9ecef';
 1338                fileRow.style.opacity = '0.6';
 1339                fileRow.style.display = 'none';
 1340                fileRow.querySelectorAll('input, select, textarea').forEach(inp => {
 1341                    if (inp.type !== 'hidden') inp.disabled = true;
 1342                });
 1343                let next = fileRow.nextElementSibling;
 1344                while (next && !next.classList.contains('quantity-row') && !next.classList.contains('file-order-row') &&
 1345                    next.style.display = 'none';
 1346                    const h = next.querySelector('input[name*="Hidden"]');
 1347                    if (h) h.value = 'True';
 1348                    next = next.nextElementSibling;
 1349                }
 1350            });
 1351        }
 1352        const skipCb = document.getElementById('skipUniqueOrders');
 1353        if (skipCb && state.skipUniqueOrders !== undefined) {
 1354            skipCb.checked = state.skipUniqueOrders;
 1355            toggleUniqueOrdersSkip(skipCb);
 1356        }
 1357        // Секции сопоставлений всегда развёрнуты — показываем все строки
 1358        const sectionIds = ['one-match', 'multiple-matches'];
 1359        const allRows = Array.from(document.querySelectorAll('#shipmentTable tbody tr'));
 1360        sectionIds.forEach(function(sectionId) {
 1361            const headerRow = document.getElementById(sectionId);
 1362            if (!headerRow) return;
 1363            for (let i = allRows.indexOf(headerRow) + 1; i < allRows.length; i++) {
 1364                const row = allRows[i];
 1365                if (sectionIds.includes(row.id) || row.id === 'unique-orders' || row.id === 'grand-total-summary-row') b
 1366                row.style.display = '';
 1367            }
 1368        });
 1369        updateGrandTotalSummary();
 1370        requestAnimationFrame(function() {
 1371            window.scrollTo(0, state.scrollY || 0);
 1372        });
 1373    }
 1374
 1375    // Прокрутка к нужному блоку (1 совпадение / несколько / новые)
 1376    function scrollToSection(sectionId) {
 1377        const headerRow = document.getElementById(sectionId);
 1378        if (headerRow) {
 1379            headerRow.scrollIntoView({ behavior: 'smooth', block: 'start' });
 1380        }
 1381    }
 1382
 1383    function setToExcel(value) {
 1384        document.getElementById('toExcel').value = value;
 1385    }
 1386
 1387    function setOnlyNewOrders(value) {
 1388        document.getElementById('onlyNewOrders').value = value;
 1389    }
 1390
 1391    // Функция для переключения состояния "не проводить в системе" для новых заказов
 1392    function toggleUniqueOrdersSkip(checkbox) {
 1393        const uniqueOrdersRow = document.getElementById('unique-orders');
 1394        if (!uniqueOrdersRow) return;
 1395
 1396        const isSkipped = checkbox.checked;
 1397
 1398        // Находим все строки новых заказов (они находятся после строки unique-orders)
 1399        // Строки новых заказов имеют класс table-excel-order и содержат поле IsUniqueOrder = true
 1400        const allRows = Array.from(document.querySelectorAll('tr'));
 1401        const uniqueOrdersRowIndex = allRows.indexOf(uniqueOrdersRow);
 1402
 1403        // Находим все строки после unique-orders до следующего раздела или конца таблицы
 1404        for (let i = uniqueOrdersRowIndex + 1; i < allRows.length; i++) {
 1405            const row = allRows[i];
 1406
 1407            // Проверяем, является ли это строкой нового заказа
 1408            // Строки новых заказов имеют класс table-excel-order и содержат скрытое поле IsUniqueOrder = true
 1409            const isUniqueOrderInput = row.querySelector('input[name*="IsUniqueOrder"][value="true"]');
 1410
 1411            if (isUniqueOrderInput) {
 1412                // Это строка нового заказа
 1413                // Получаем индекс из data-index или из имени поля
 1414                let index = row.getAttribute('data-index');
 1415                if (!index) {
 1416                    const firstInput = row.querySelector('input[name*="NotFullOrders"]');
 1417                    if (firstInput && firstInput.name) {
 1418                        const match = firstInput.name.match(/\[(\d+)\]/);
 1419                        if (match) {
 1420                            index = match[1];
 1421                        }
 1422                    }
 1423                }
 1424
 1425                if (isSkipped) {
 1426                    // Устанавливаем Hidden = true
 1427                    let hiddenInput = row.querySelector(`input[name="NotFullOrders[${index}].Hidden"]`);
 1428                    if (!hiddenInput) {
 1429                        hiddenInput = row.querySelector('input[name*="Hidden"]');
 1430                    }
 1431                    if (hiddenInput) {
 1432                        hiddenInput.value = 'True';
 1433                    }
 1434
 1435                    // Делаем строку серой
 1436                    row.style.backgroundColor = '#e9ecef';
 1437                    row.style.opacity = '0.6';
 1438
 1439                    // Сохраняем исходное состояние readonly перед блокировкой
 1440                    const inputs = row.querySelectorAll('input, select, textarea');
 1441                    inputs.forEach(input => {
 1442                        if (input.type !== 'hidden') {
 1443                            // Сохраняем исходное состояние в data-атрибуте
 1444                            if (!input.hasAttribute('data-original-readonly')) {
 1445                                input.setAttribute('data-original-readonly', input.readOnly ? 'true' : 'false');
 1446                            }
 1447                            input.disabled = true;
 1448                            input.readOnly = true;
 1449                        }
 1450                    });
 1451                } else {
 1452                    // Убираем Hidden
 1453                    let hiddenInput = row.querySelector(`input[name="NotFullOrders[${index}].Hidden"]`);
 1454                    if (!hiddenInput) {
 1455                        hiddenInput = row.querySelector('input[name*="Hidden"]');
 1456                    }
 1457                    if (hiddenInput) {
 1458                        hiddenInput.value = 'False';
 1459                    }
 1460
 1461                    // Возвращаем нормальный цвет
 1462                    row.style.backgroundColor = '';
 1463                    row.style.opacity = '';
 1464
 1465                    // Разблокируем все поля ввода и восстанавливаем исходное состояние
 1466                    const inputs = row.querySelectorAll('input, select, textarea');
 1467                    inputs.forEach(input => {
 1468                        if (input.type !== 'hidden') {
 1469                            input.disabled = false;
 1470                            // Восстанавливаем исходное состояние readonly
 1471                            const originalReadonly = input.getAttribute('data-original-readonly');
 1472                            if (originalReadonly === 'true') {
 1473                                input.readOnly = true;
 1474                            } else {
 1475                                input.readOnly = false;
 1476                            }
 1477                            // Удаляем data-атрибут после восстановления
 1478                            input.removeAttribute('data-original-readonly');
 1479                        }
 1480                    });
 1481                }
 1482            } else {
 1483                // Если встретили строку, которая не является новым заказом, значит дошли до следующего раздела
 1484                // Проверяем, не является ли это заголовком следующего раздела
 1485                if (row.id === 'one-match' || row.id === 'multiple-matches' ||
 1486                    row.classList.contains('table-primary') || row.querySelector('b')) {
 1487                    break;
 1488                }
 1489            }
 1490        }
 1491        updateGrandTotalSummary();
 1492        debouncedSaveState();
 1493    }
 1494
 1495    // Собираем OrderIds из чекбоксов перед отправкой формы
 1496    document.addEventListener('DOMContentLoaded', function() {
 1497        const form = document.querySelector('form[action*="SetNotFullOrdersData"]');
 1498        if (form) {
 1499            form.addEventListener('submit', function(e) {
 1500                // Находим все строки с заказами
 1501                const orderRows = document.querySelectorAll('tr[data-index], tr.quantity-row');
 1502                orderRows.forEach(function(row) {
 1503                    // Получаем индекс из data-index или из имени поля
 1504                    let index = row.getAttribute('data-index');
 1505                    if (!index) {
 1506                        const firstInput = row.querySelector('input[name*="NotFullOrders"]');
 1507                        if (firstInput && firstInput.name) {
 1508                            const match = firstInput.name.match(/\[(\d+)\]/);
 1509                            if (match) {
 1510                                index = match[1];
 1511                            }
 1512                        }
 1513                    }
 1514
 1515                    if (index) {
 1516                        // Проверяем, является ли это уникальным заказом (новым)
 1517                        const isUniqueOrderInput = row.querySelector(`input[name="NotFullOrders[${index}].IsUniqueOrder"
 1518                        const isUniqueOrder = isUniqueOrderInput && isUniqueOrderInput.value === 'true';
 1519
 1520                        // Для уникальных заказов OrderIds должен быть пустым
 1521                        if (isUniqueOrder) {
 1522                            let orderIdsInput = row.querySelector(`input[type="hidden"][name="NotFullOrders[${index}].Or
 1523                            if (!orderIdsInput) {
 1524                                orderIdsInput = document.createElement('input');
 1525                                orderIdsInput.type = 'hidden';
 1526                                orderIdsInput.name = `NotFullOrders[${index}].OrderIds`;
 1527                                row.appendChild(orderIdsInput);
 1528                            }
 1529                            orderIdsInput.value = '';
 1530                        } else {
 1531                            // Для не-уникальных заказов собираем OrderIds из чекбоксов
 1532                            const checkboxes = row.querySelectorAll('input[type="checkbox"].record-checkbox:checked');
 1533                            const orderIds = Array.from(checkboxes).map(cb => cb.value).filter(id => id);
 1534
 1535                            // Находим или создаем скрытое поле для OrderIds
 1536                            let orderIdsInput = row.querySelector(`input[type="hidden"][name="NotFullOrders[${index}].Or
 1537                            if (!orderIdsInput) {
 1538                                orderIdsInput = document.createElement('input');
 1539                                orderIdsInput.type = 'hidden';
 1540                                orderIdsInput.name = `NotFullOrders[${index}].OrderIds`;
 1541                                row.appendChild(orderIdsInput);
 1542                            }
 1543
 1544                            // Устанавливаем значение из чекбоксов
 1545                            orderIdsInput.value = orderIds.length > 0 ? orderIds.join(',') : '';
 1546                        }
 1547                    }
 1548                });
 1549            });
 1550        }
 1551    });
 1552
 1553    // Пересчёт итогов при изменении количества или цены в строках заказов
 1554    document.addEventListener('DOMContentLoaded', function () {
 1555        const table = document.getElementById('shipmentTable');
 1556        if (table) {
 1557            function maybeUpdateTotals(e) {
 1558                const target = e.target;
 1559                if (target.closest && target.closest('tr.file-order-row') &&
 1560                    (target.name && (target.name.includes('Order.Quantity') || target.name.includes('Order.PurchasePrice
 1561                    updateGrandTotalSummary();
 1562                }
 1563            }
 1564            table.addEventListener('input', maybeUpdateTotals);
 1565            table.addEventListener('change', maybeUpdateTotals);
 1566        }
 1567    });
 1568
 1569    // Секции "1 совпадение" и "несколько совпадений" всегда развёрнуты, сворачивание отключено
 1570    document.addEventListener('DOMContentLoaded', function () {
 1571        // Убеждаемся, что все строки секций видны (на случай восстановления состояния)
 1572        const sectionIds = ['one-match', 'multiple-matches'];
 1573        const allRows = Array.from(document.querySelectorAll('#shipmentTable tbody tr'));
 1574        sectionIds.forEach(function (sectionId) {
 1575            const headerRow = document.getElementById(sectionId);
 1576            if (!headerRow) return;
 1577            for (let i = allRows.indexOf(headerRow) + 1; i < allRows.length; i++) {
 1578                const row = allRows[i];
 1579                if (sectionIds.includes(row.id) || row.id === 'unique-orders' || row.id === 'grand-total-summary-row') b
 1580                row.style.display = '';
 1581            }
 1582        });
 1583
 1584        // Клавиатурная навигация по парам (Alt+↓ / Alt+↑)
 1585        const fileRows = Array.from(document.querySelectorAll('#shipmentTable .file-order-row'));
 1586        const groupIndexes = fileRows.map(r => parseInt(r.getAttribute('data-index'))).filter(n => !isNaN(n));
 1587        let currentGroupPos = 0;
 1588
 1589        function focusGroup(idx) {
 1590            const fileRow = document.querySelector(`#shipmentTable .file-order-row[data-index="${idx}"]`);
 1591            if (!fileRow) return;
 1592
 1593            fileRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
 1594
 1595            // Пытаемся сфокусировать первый чекбокс этой группы
 1596            const cb = document.querySelector(`input[type="checkbox"].record-checkbox[data-index^="${idx}-"]`);
 1597            if (cb && !cb.disabled) {
 1598                cb.focus();
 1599            }
 1600        }
 1601
 1602        document.addEventListener('keydown', function (e) {
 1603            if (!e.altKey) return;
 1604
 1605            if (e.key === 'ArrowDown') {
 1606                e.preventDefault();
 1607                if (groupIndexes.length === 0) return;
 1608                currentGroupPos = Math.min(currentGroupPos + 1, groupIndexes.length - 1);
 1609                focusGroup(groupIndexes[currentGroupPos]);
 1610            } else if (e.key === 'ArrowUp') {
 1611                e.preventDefault();
 1612                if (groupIndexes.length === 0) return;
 1613                currentGroupPos = Math.max(currentGroupPos - 1, 0);
 1614                focusGroup(groupIndexes[currentGroupPos]);
 1615            }
 1616        });
 1617    });
 1618</script>
 1619
 1620<script>
 1621
 1622    // Функция для показа уведомлений поверх всех окон
 1623    function showNotification(message, type = 'warning') {
 1624        // Создаем контейнер для уведомлений если его нет
 1625        let notificationContainer = document.getElementById('notification-container');
 1626        if (!notificationContainer) {
 1627            notificationContainer = document.createElement('div');
 1628            notificationContainer.id = 'notification-container';
 1629            notificationContainer.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 99999; display: fle
 1630            document.body.appendChild(notificationContainer);
 1631        }
 1632
 1633        // Создаем уведомление
 1634        const toast = document.createElement('div');
 1635        toast.className = `alert alert-${type} alert-dismissible fade show`;
 1636        toast.style.cssText = 'min-width: 300px; max-width: 400px; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);';
 1637        toast.innerHTML = `
 1638            ${message}
 1639            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 1640        `;
 1641
 1642        // Добавляем уведомление в контейнер
 1643        notificationContainer.appendChild(toast);
 1644
 1645        // Инициализируем Bootstrap alert
 1646        const bsAlert = new bootstrap.Alert(toast);
 1647
 1648        // Автоматическое скрытие через 5 секунд
 1649        setTimeout(() => {
 1650            if (toast.parentNode) {
 1651                bsAlert.close();
 1652            }
 1653        }, 5000);
 1654
 1655        // Удаляем элемент из DOM после скрытия
 1656        toast.addEventListener('closed.bs.alert', function () {
 1657            if (toast.parentNode) {
 1658                toast.parentNode.removeChild(toast);
 1659            }
 1660        });
 1661    }
 1662
 1663    function toggleClientField(checkbox, index) {
 1664        var clientContainer = document.getElementById('clientContainer_' + index);
 1665        var clientSelect = document.getElementById('clientFilter_' + index);
 1666
 1667        if (checkbox.checked) {
 1668            // Скрываем контейнер и сбрасываем значение
 1669            clientContainer.style.display = 'none';
 1670            // Важно: не очищаем value, чтобы при "Новый заказ" из этого ряда
 1671            // клиент не терял предвыбранное значение.
 1672        } else {
 1673            // Показываем контейнер
 1674            clientContainer.style.display = 'block';
 1675        }
 1676    }
 1677
 1678    /**
 1679     * Вычисляет эффективное количество с учётом кратности упаковки [N] в ProductKey.
 1680     * Например: ProductKey "PKJ20CR8=DNS[2]", Quantity 1 → 2
 1681     */
 1682    function getEffectiveQuantity(productKey, quantity) {
 1683        const qty = parseInt(quantity, 10) || 1;
 1684        if (!productKey || typeof productKey !== 'string') return qty;
 1685        const match = productKey.match(/\[(\d+)\]/);
 1686        if (match && match[1]) {
 1687            const multiplier = parseInt(match[1], 10);
 1688            if (multiplier > 0) return qty * multiplier;
 1689        }
 1690        return qty;
 1691    }
 1692
 1693    /**
 1694     * Проверяет совместимость по количеству при ручном сопоставлении.
 1695     * Показывает предупреждение, если количество в файле не совпадает с эффективным количеством заказа.
 1696     */
 1697    function checkQuantityCompatibility(excelQuantity, selectedEffectiveQuantity, productKey) {
 1698        if (excelQuantity <= 0) return;
 1699        if (excelQuantity !== selectedEffectiveQuantity) {
 1700            const mult = productKey && productKey.match(/\[(\d+)\]/) ? productKey.match(/\[(\d+)\]/)[1] : null;
 1701            const msg = mult
 1702                ? `Количество не совпадает: в файле ${excelQuantity} шт., в заказе ${selectedEffectiveQuantity} шт. (с у
 1703                : `Количество не совпадает: в файле ${excelQuantity} шт., в заказе ${selectedEffectiveQuantity} шт.`;
 1704            showNotification(msg, 'warning');
 1705        }
 1706    }
 1707
 1708    function updateOrderIds(checkbox, index) {
 1709        // Если изменение не от автопроставления — помечаем строку как изменённую пользователем
 1710        if (!_autoCheckPhase) { _userModifiedIndices.add(String(index)); }
 1711        // Важно: у чекбокса и hidden-поля совпадает name="NotFullOrders[IDX].OrderIds".
 1712        // Нам нужно работать именно с hidden-полем, иначе можно повредить checkbox.value.
 1713        let orderIdsInput = checkbox.closest('tr')?.querySelector(
 1714            `input[type="hidden"][name="NotFullOrders[${index}].OrderIds"]`
 1715        );
 1716        if (!orderIdsInput) {
 1717            orderIdsInput = document.querySelector(`input[type="hidden"][name="NotFullOrders[${index}].OrderIds"]`);
 1718        }
 1719        if (!orderIdsInput) orderIdsInput = document.getElementById('orderIds');
 1720        let ids = orderIdsInput && orderIdsInput.value ? orderIdsInput.value.split(',') : [];
 1721        const orderId = checkbox.value;
 1722        const dataOrder = checkbox.getAttribute('data-order');
 1723        const productKey = checkbox.getAttribute('data-product-key') || '';
 1724
 1725        const quantityField = document.getElementById('quantityFilter_' + index);
 1726        const hiddenQuantityField = document.getElementById('hiddenQuantity_' + index);
 1727        const excelQuantity = parseFloat(quantityField ? quantityField.value : 0) || 0;
 1728        let quantity = excelQuantity;
 1729        var newOrderButton = document.getElementById('newOrderButton_' + index);
 1730
 1731        const rawQuantity = parseFloat(checkbox.getAttribute('data-quantity') || 1);
 1732        const checkboxEffectiveQuantity = getEffectiveQuantity(productKey, rawQuantity);
 1733
 1734        if (checkbox.checked) {
 1735            if (quantity - checkboxEffectiveQuantity < 0) {
 1736                checkbox.checked = false;
 1737                showNotification('Нельзя распределить больше товара, чем есть в наличии. Остаток: ' + Math.round(quantit
 1738                return;
 1739            }
 1740
 1741            if (!ids.includes(orderId)) {
 1742                ids.push(orderId);
 1743            }
 1744
 1745            document.querySelectorAll('.record-checkbox[data-order="' + dataOrder + '"]').forEach(function (cb) {
 1746                if (cb !== checkbox) cb.disabled = true;
 1747            });
 1748            quantity -= checkboxEffectiveQuantity;
 1749
 1750            checkQuantityCompatibility(excelQuantity, checkboxEffectiveQuantity, productKey);
 1751        } else {
 1752            document.querySelectorAll('.record-checkbox[data-order="' + dataOrder + '"]').forEach(function (cb) {
 1753                cb.disabled = false;
 1754            });
 1755            ids = ids.filter(id => id !== orderId);
 1756            quantity += checkboxEffectiveQuantity;
 1757        }
 1758
 1759        if (orderIdsInput) orderIdsInput.value = ids.join(',');
 1760        quantity = Math.max(0, Math.round(parseFloat(quantity)));
 1761        if (quantityField) quantityField.value = quantity;
 1762        if (hiddenQuantityField) hiddenQuantityField.value = quantity;
 1763
 1764        toggleClientField(checkbox, index);
 1765
 1766        if (newOrderButton) {
 1767            if (parseInt(hiddenQuantityField ? hiddenQuantityField.value : 0, 10) > 0) {
 1768                newOrderButton.style.display = 'block';
 1769            } else {
 1770                newOrderButton.style.display = 'none';
 1771            }
 1772        }
 1773
 1774        updateGrandTotalSummary();
 1775        debouncedSaveState();
 1776    }
 1777
 1778    document.addEventListener("DOMContentLoaded", function () {
 1779        // Проверяем, есть ли сохраненное состояние и принадлежит ли оно текущей партии
 1780        const savedStateJson = localStorage.getItem(stateStorageKey);
 1781        let savedState = null;
 1782        if (savedStateJson) {
 1783            try {
 1784                savedState = JSON.parse(savedStateJson);
 1785            } catch (e) {
 1786                savedState = null;
 1787            }
 1788        }
 1789
 1790        const hasSameBatch =
 1791            savedState &&
 1792            savedState.batchKey &&
 1793            currentBatchKey &&
 1794            savedState.batchKey === currentBatchKey;
 1795
 1796        // Найти все чекбоксы с data-one-matches="true"
 1797        const checkboxes = document.querySelectorAll('input[type="checkbox"][data-one-matches="true"]');
 1798
 1799        // Всегда автоматически проставляем галки для одиночных совпадений
 1800        _autoCheckPhase = true;
 1801        checkboxes.forEach(function (checkbox) {
 1802            if (!checkbox.disabled) {
 1803                const index = checkbox.getAttribute('data-index');
 1804                if (index) {
 1805                    const indexParts = index.split('-');
 1806                    const rowIndex = indexParts[0];
 1807                    const quantityField = document.getElementById('quantityFilter_' + rowIndex);
 1808                    const productKey = checkbox.getAttribute('data-product-key') || '';
 1809                    const rawQty = parseFloat(checkbox.getAttribute('data-quantity') || 1);
 1810                    const checkboxEffectiveQuantity = getEffectiveQuantity(productKey, rawQty);
 1811                    const currentQuantity = parseFloat(quantityField ? quantityField.value : 0) || 0;
 1812                    if (currentQuantity - checkboxEffectiveQuantity >= 0) {
 1813                        checkbox.checked = true;
 1814                        checkbox.dispatchEvent(new Event('change'));
 1815                    }
 1816                } else {
 1817                    checkbox.checked = true;
 1818                    checkbox.dispatchEvent(new Event('change'));
 1819                }
 1820            }
 1821        });
 1822        _autoCheckPhase = false;
 1823
 1824        // Если есть сохранённое состояние текущей партии — накладываем поверх автопроставления,
 1825        // восстанавливая только те строки, которые пользователь менял вручную
 1826        if (hasSameBatch) {
 1827            applySavedState(savedState);
 1828        }
 1829
 1830        const form = document.querySelector('form[action*="SetNotFullOrdersData"]');
 1831        if (form) {
 1832            form.addEventListener('input', debouncedSaveState);
 1833            form.addEventListener('change', debouncedSaveState);
 1834        }
 1835    });
 1836
 1837
 1838    function handleOzonClientChangeUnique(selectElement) {
 1839        var selectedClientId = selectElement.value;
 1840
 1841        // Обновляем все элементы с классом .ozonClientFilter, которые не находятся внутри скрытого контейнера
 1842        document.querySelectorAll('.ozonClientFilterUnique').forEach(function (clientSelect) {
 1843            // Убедитесь, что элемент не является тем, который был изменен
 1844            if (clientSelect !== selectElement) {
 1845                // Убедитесь, что элемент не находится внутри скрытого контейнера
 1846                if (!clientSelect.closest('.client-container[style*="display:none;"]')) {
 1847                    clientSelect.value = selectedClientId;
 1848                }
 1849            }
 1850        });
 1851    }
 1852
 1853    function handleStatusChange(selectElement) {
 1854        var selectedStatusId = selectElement.value;
 1855        var selectedOption = selectElement.options[selectElement.selectedIndex];
 1856        var bgColor = selectedOption ? selectedOption.getAttribute('data-status-color') || 'transparent' : 'transparent'
 1857        var textColor = selectedOption ? selectedOption.getAttribute('data-text-color') || '#000000' : '#000000';
 1858
 1859        // Обновляем все элементы с классом .statusFilter
 1860        document.querySelectorAll('.statusFilter').forEach(function (statusSelect) {
 1861            // Убедитесь, что элемент не является тем, который был изменен
 1862            if (statusSelect !== selectElement) {
 1863                statusSelect.value = selectedStatusId;
 1864
 1865                // Обновляем стили выбранного option
 1866                updateSelectStatusColor(statusSelect, bgColor, textColor);
 1867            }
 1868        });
 1869
 1870        // Обновляем стили текущего select
 1871        updateSelectStatusColor(selectElement, bgColor, textColor);
 1872    }
 1873
 1874    // Функция для обновления цвета select элемента
 1875    function updateSelectStatusColor(selectElement, bgColor, textColor) {
 1876        if (bgColor && bgColor !== 'transparent') {
 1877            selectElement.style.backgroundColor = bgColor;
 1878            selectElement.style.color = textColor;
 1879        } else {
 1880            selectElement.style.backgroundColor = '';
 1881            selectElement.style.color = '';
 1882        }
 1883    }
 1884
 1885    // Инициализация цветов статусов при загрузке страницы
 1886    document.addEventListener('DOMContentLoaded', function() {
 1887        // Применяем цвета к существующим select элементам
 1888        document.querySelectorAll('.status-select-colored').forEach(function(selectElement) {
 1889            if (selectElement.value) {
 1890                var selectedOption = selectElement.options[selectElement.selectedIndex];
 1891                if (selectedOption) {
 1892                    var bgColor = selectedOption.getAttribute('data-status-color') || 'transparent';
 1893                    var textColor = selectedOption.getAttribute('data-text-color') || '#000000';
 1894                    updateSelectStatusColor(selectElement, bgColor, textColor);
 1895                }
 1896            }
 1897
 1898            // Добавляем обработчик изменения для каждого select
 1899            selectElement.addEventListener('change', function() {
 1900                if (this.value) {
 1901                    var selectedOption = this.options[this.selectedIndex];
 1902                    if (selectedOption) {
 1903                        var bgColor = selectedOption.getAttribute('data-status-color') || 'transparent';
 1904                        var textColor = selectedOption.getAttribute('data-text-color') || '#000000';
 1905                        updateSelectStatusColor(this, bgColor, textColor);
 1906                    }
 1907                } else {
 1908                    this.style.backgroundColor = '';
 1909                    this.style.color = '';
 1910                }
 1911            });
 1912        });
 1913    });
 1914
 1915    function handleOzonClientChange(selectElement) {
 1916        var selectedClientId = selectElement.value;
 1917
 1918        // Обновляем все элементы с классом .ozonClientFilter, которые не находятся внутри скрытого контейнера
 1919        document.querySelectorAll('.ozonClientFilter').forEach(function (clientSelect) {
 1920            // Убедитесь, что элемент не является тем, который был изменен
 1921            if (clientSelect !== selectElement) {
 1922                // Убедитесь, что элемент не находится внутри скрытого контейнера
 1923                if (!clientSelect.closest('.client-container[style*="display:none;"]')) {
 1924                    clientSelect.value = selectedClientId;
 1925                }
 1926            }
 1927        });
 1928    }
 1929
 1930    document.getElementById('shipmentNumberFilterInput').addEventListener('input', function () {
 1931        const shipmentNumber = this.value;  // Получаем значение из поля ввода
 1932        // Находим все элементы с классом orderNumberToSupplie-field и обновляем их значение
 1933        document.querySelectorAll('.orderNumberToSupplie-field').forEach(function (element) {
 1934            element.value = shipmentNumber;
 1935        });
 1936    });
 1937
 1938    // Синхронизация поля "Номер заказа" (OrderNumberToSupplier) - по аналогии с handleStatusChange
 1939    function handleOrderNumberToSupplierChange(inputElement) {
 1940        if (!inputElement) return;
 1941
 1942        var selectedValue = inputElement.value || '';
 1943
 1944        // Обновляем все элементы с классом .universalOrderNumberToSupplie
 1945        document.querySelectorAll('input.universalOrderNumberToSupplie').forEach(function (orderNumberInput) {
 1946            // Убедитесь, что элемент не является тем, который был изменен
 1947            if (orderNumberInput !== inputElement) {
 1948                orderNumberInput.value = selectedValue;
 1949            }
 1950        });
 1951
 1952        // Сохраняем состояние — прямое присвоение .value не триггерит событие input на форме
 1953        debouncedSaveState();
 1954    }
 1955
 1956    // Инициализация обработчиков для полей в строках заказов
 1957    document.addEventListener('DOMContentLoaded', function() {
 1958        document.querySelectorAll('input.universalOrderNumberToSupplie').forEach(function(inputElement) {
 1959            // Пропускаем универсальное поле, так как оно уже имеет oninput в HTML
 1960            if (inputElement.id !== 'universalOrderNumberToSupplier') {
 1961                inputElement.addEventListener('input', function() {
 1962                    handleOrderNumberToSupplierChange(this);
 1963                });
 1964            }
 1965        });
 1966    });
 1967    ///////////////////////////////////////////////
 1968
 1969    /**
 1970     * Пересчитывает и обновляет строку «Общий итог» на основе всех заказов (включая перемещённые в «новые»).
 1971     */
 1972    function updateGrandTotalSummary() {
 1973        const summaryRow = document.getElementById('grand-total-summary-row');
 1974        if (!summaryRow) return;
 1975
 1976        let totalQuantity = 0;
 1977        let totalPurchasePriceRub = 0;
 1978        const totalByCurrency = {};
 1979
 1980        const fileRows = document.querySelectorAll('#shipmentTable tr.file-order-row');
 1981        fileRows.forEach(tr => {
 1982            const hiddenInput = tr.querySelector('input[name*="Hidden"]');
 1983            if (hiddenInput && hiddenInput.value === 'True') return;
 1984
 1985            const firstInput = tr.querySelector('input[name*="NotFullOrders"]');
 1986            if (!firstInput || !firstInput.name) return;
 1987            const indexMatch = firstInput.name.match(/NotFullOrders\[(\d+)\]/);
 1988            if (!indexMatch) return;
 1989            const idx = indexMatch[1];
 1990
 1991            const qtyInput = tr.querySelector(`input[type="hidden"][name="NotFullOrders[${idx}].Order.Quantity"]`) ||
 1992                tr.querySelector(`input[name="NotFullOrders[${idx}].Order.Quantity"]`);
 1993            const qty = parseFloat((qtyInput && qtyInput.value) || 0) || 0;
 1994            if (qty <= 0) return;
 1995
 1996            const priceInput = tr.querySelector(`input[name="NotFullOrders[${idx}].Order.PurchasePrice"]`);
 1997            const price = parseFloat((priceInput && priceInput.value || '').replace(/\s/g, '').replace(',', '.')) || 0;
 1998
 1999            const origPriceInput = tr.querySelector(`input[name="NotFullOrders[${idx}].Order.OriginalPurchasePrice"]`);
 2000            const origPrice = parseFloat((origPriceInput && origPriceInput.value || '').replace(/\s/g, '').replace(',', 
 2001
 2002            const currencySpan = tr.querySelector(`span[id="currencyCode_origin_${idx}"]`);
 2003            const currency = (currencySpan && currencySpan.textContent && currencySpan.textContent.trim()) || 'RUB';
 2004
 2005            // Если OriginalPurchasePrice не задан — используем PurchasePrice (RUB)
 2006            const effectiveOrigPrice = origPrice > 0 ? origPrice : price;
 2007            const effectiveCurrency = origPrice > 0 ? currency : 'RUB';
 2008
 2009            totalQuantity += qty;
 2010            totalPurchasePriceRub += price * qty;
 2011            if (effectiveCurrency) {
 2012                totalByCurrency[effectiveCurrency] = (totalByCurrency[effectiveCurrency] || 0) + effectiveOrigPrice * qt
 2013            }
 2014        });
 2015
 2016        const qtyTd = summaryRow.querySelector('td:nth-child(2)');
 2017        if (qtyTd) {
 2018            const b = qtyTd.querySelector('b');
 2019            if (b) b.textContent = Math.round(totalQuantity);
 2020        }
 2021
 2022        const amountsTd = summaryRow.querySelector('td:nth-child(4)');
 2023        if (amountsTd) {
 2024            let html = '';
 2025            for (const [cur, val] of Object.entries(totalByCurrency)) {
 2026                if (cur && val > 0) {
 2027                    const formatted = Math.round(val).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '\u00A0');
 2028                    html += `<div style="font-size: 0.85em; white-space: nowrap;">${formatted} ${cur}</div>`;
 2029                }
 2030            }
 2031            const rubFormatted = Math.round(totalPurchasePriceRub).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '\u00A0')
 2032            html += `<div style="border-top: 1px solid #dee2e6; margin-top: 2px; padding-top: 2px;"><b>${rubFormatted} R
 2033            amountsTd.innerHTML = html;
 2034        }
 2035    }
 2036
 2037    function cloneRow(button) {
 2038        // Получаем родительскую строку таблицы
 2039        const row = button.closest('tr');
 2040        // Клонируем строку
 2041        const newRow = row.cloneNode(true);
 2042
 2043        const buttonId = button.id;
 2044        const index = buttonId.split('_')[1];
 2045
 2046        console.log(index);
 2047
 2048        // Получаем максимальный индекс для обновления
 2049        const allRows = document.querySelectorAll('tr');
 2050        let maxIndex = 0;
 2051        allRows.forEach(r => {
 2052            r.querySelectorAll('input, select').forEach(el => {
 2053                const match = el.name.match(/\[(\d+)\]/);
 2054                if (match) {
 2055                    const index = parseInt(match[1], 10);
 2056                    if (index > maxIndex) {
 2057                        maxIndex = index;
 2058                    }
 2059                }
 2060            });
 2061        });
 2062        const newIndex = maxIndex + 1;
 2063
 2064        // Получаем все элементы input и select в клонированной строке
 2065        const inputs = newRow.querySelectorAll('input, select');
 2066        inputs.forEach(input => {
 2067            const name = input.name;
 2068            if (name) {
 2069                input.name = name.replace(/\[\d+\]/, `[${newIndex}]`);
 2070            }
 2071            if (input.id) {
 2072                input.id = input.id.replace(new RegExp('_' + index + '(?![\\d])', 'g'), '_' + newIndex);
 2073            }
 2074        });
 2075        // Обновляем id у span и других элементов (currencyCode_origin и т.д.)
 2076        newRow.querySelectorAll('[id]').forEach(el => {
 2077            if (el.id && el.id.includes('_' + index)) {
 2078                el.id = el.id.replace(new RegExp('_' + index + '(?![\\d])', 'g'), '_' + newIndex);
 2079            }
 2080        });
 2081
 2082        // Замена кнопки на input в клонированной строке
 2083        const buttonElement = newRow.querySelector('.moveToNewButton');
 2084        if (buttonElement) {
 2085            const inputField = document.createElement('input');
 2086            inputField.className = 'form-control form-control-sm orderNumberToSupplie-field';
 2087            inputField.name = `NotFullOrders[${newIndex}].Order.ShipmentNumber`;
 2088            inputField.id = `shipmentNumber_${newIndex}`;
 2089            inputField.placeholder = 'Номер заказа';
 2090            // Авто-номер: имя_файла_строка (напр. orders_june_15)
 2091            const numberInExcel = row.dataset.numberInExcel;
 2092            inputField.value = buildShipmentNumber(numberInExcel || index);
 2093
 2094            buttonElement.replaceWith(inputField);
 2095        }
 2096
 2097        // Устанавливаем IsUniqueOrder = true для нового заказа
 2098        // Ищем поле после обновления имен (может быть уже обновлено в цикле выше)
 2099        let isUniqueOrderInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2100            input => input.name && input.name.includes('IsUniqueOrder') && input.name.includes(`[${newIndex}]`)
 2101        );
 2102        if (!isUniqueOrderInput) {
 2103            // Ищем по старому паттерну или создаем новое
 2104            isUniqueOrderInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2105                input => input.name && input.name.includes('IsUniqueOrder')
 2106            );
 2107            if (isUniqueOrderInput) {
 2108                isUniqueOrderInput.name = `NotFullOrders[${newIndex}].IsUniqueOrder`;
 2109            } else {
 2110                isUniqueOrderInput = document.createElement('input');
 2111                isUniqueOrderInput.type = 'hidden';
 2112                isUniqueOrderInput.name = `NotFullOrders[${newIndex}].IsUniqueOrder`;
 2113                newRow.appendChild(isUniqueOrderInput);
 2114            }
 2115        }
 2116        isUniqueOrderInput.value = 'true';
 2117
 2118        // Очищаем OrderIds для нового заказа (не Order.OrderIds, а именно OrderIds)
 2119        let orderIdsInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2120            input => input.name && input.name.includes('OrderIds') &&
 2121                     !input.name.includes('Order.OrderIds') && input.name.includes(`[${newIndex}]`)
 2122        );
 2123        if (!orderIdsInput) {
 2124            // Ищем по старому паттерну или создаем новое
 2125            orderIdsInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2126                input => input.name && input.name.includes('OrderIds') && !input.name.includes('Order.OrderIds')
 2127            );
 2128            if (orderIdsInput) {
 2129                orderIdsInput.name = `NotFullOrders[${newIndex}].OrderIds`;
 2130            } else {
 2131                orderIdsInput = document.createElement('input');
 2132                orderIdsInput.type = 'hidden';
 2133                orderIdsInput.name = `NotFullOrders[${newIndex}].OrderIds`;
 2134                newRow.appendChild(orderIdsInput);
 2135            }
 2136        }
 2137        orderIdsInput.value = '';
 2138
 2139        // Обновляем блок clientContainer только в новой строке
 2140        // Важно: выше по коду уже переименованы все `id` на `newIndex`,
 2141        // поэтому контейнер теперь имеет id = clientContainer_${newIndex}.
 2142        const newClientContainer = newRow.querySelector(`#clientContainer_${newIndex}`) ||
 2143            newRow.querySelector(`#clientContainer_${index}`);
 2144        if (newClientContainer) {
 2145            newClientContainer.id = `clientContainer_${newIndex}`;
 2146            newClientContainer.style.display = 'block';  // Разблокируем в клонированной строке
 2147        }
 2148
 2149        // Обновляем значения полей количества
 2150        const quantityField = document.getElementById('quantityFilter_' + index);
 2151        const hiddenQuantityField = document.getElementById('hiddenQuantity_' + index);
 2152
 2153        if (quantityField && hiddenQuantityField) {
 2154            const newQuantityField = newRow.querySelector(`#quantityFilter_${newIndex}`);
 2155            const newHiddenQuantityField = newRow.querySelector(`#hiddenQuantity_${newIndex}`);
 2156
 2157            if (newQuantityField) {
 2158                newQuantityField.value = Math.round(parseFloat(quantityField.value));
 2159            }
 2160
 2161            if (newHiddenQuantityField) {
 2162                newHiddenQuantityField.value = Math.round(parseFloat(quantityField.value));
 2163            }
 2164
 2165            quantityField.value = 0;
 2166            hiddenQuantityField.value = 0;
 2167        }
 2168
 2169        newRow.setAttribute('data-source-index', index);
 2170
 2171        // Вставляем новую строку перед строкой «Общий итог», чтобы она учитывалась в расчётах
 2172        const summaryRow = document.getElementById('grand-total-summary-row');
 2173        if (summaryRow) {
 2174            row.parentNode.insertBefore(newRow, summaryRow);
 2175        } else {
 2176            row.parentNode.appendChild(newRow);
 2177        }
 2178
 2179        // Скрываем кнопку в основной строке
 2180        button.style.display = 'none';
 2181
 2182        updateGrandTotalSummary();
 2183        debouncedSaveState();
 2184    }
 2185
 2186
 2187
 2188    ///////////////////////////////////////////////////////////////
 2189
 2190    function updateColumnValuesFromMainOrder(checkbox) {
 2191        console.log('Checkbox:', checkbox);
 2192
 2193        // Получаем индекс из data-атрибута чекбокса
 2194        let index = checkbox.getAttribute('data-index');
 2195        console.log('Original Index:', index);
 2196        splitIindex = index.split('-')[0]; // Берём только часть до дефиса
 2197
 2198        // Получаем оригинальные значения
 2199        const originalPurchasePriceElement = document.getElementById('originalPurchasePrice_origin_' + splitIindex);
 2200        const currencyCodeElement = document.getElementById('currencyCode_origin_' + splitIindex);
 2201        const purchasePriceElement = document.getElementById('purchasePrice_origin_' + splitIindex);
 2202        const costPriceElement = document.getElementById('costPrice_origin_' + splitIindex);
 2203
 2204        // Проверяем существование элементов перед доступом к textContent
 2205        if (originalPurchasePriceElement && currencyCodeElement && purchasePriceElement && costPriceElement) {
 2206            const originalPurchasePrice = originalPurchasePriceElement.textContent;
 2207            const currencyCode = currencyCodeElement.textContent;
 2208            const purchasePrice = purchasePriceElement.textContent;
 2209            const costPrice = costPriceElement.textContent;
 2210
 2211            // Получаем элементы с новыми id для полей
 2212            const currentPriceElement = document.getElementById('currentPriceWithDiscount_' + index);
 2213            const quantityElement = document.getElementById('quantity_' + index);
 2214            const priceElement = document.getElementById('price_' + index);
 2215            const shipmentAmountElement = document.getElementById('shipmentAmount_' + index);
 2216            const minOzonCommissionElement = document.getElementById('minOzonCommission_' + index);
 2217            const maxOzonCommissionElement = document.getElementById('maxOzonCommission_' + index);
 2218            const minProfitElement = document.getElementById('minProfit_' + index);
 2219            const maxProfitElement = document.getElementById('maxProfit_' + index);
 2220            const minDiscountElement = document.getElementById('minDiscount_' + index);
 2221            const maxDiscountElement = document.getElementById('maxDiscount_' + index);
 2222
 2223            // Приведение к decimal (в JS это будет float)
 2224            const currentPriceWithDiscount = parseFloat(currentPriceElement.textContent.replace(/\s/g, '')) || 0;
 2225            const quantity = parseFloat(quantityElement.textContent.replace(/\s/g, '')) || 0;
 2226            const price = currentPriceWithDiscount > 0
 2227                ? currentPriceWithDiscount
 2228                : parseFloat(priceElement.textContent.replace(/\s/g, '')) || 0;
 2229            const shipmentAmount = parseFloat(shipmentAmountElement.textContent.replace(/\s/g, '')) || 0;
 2230            const minOzonCommission = parseFloat(minOzonCommissionElement.textContent.replace(/\s/g, '')) || 0;
 2231            const maxOzonCommission = parseFloat(maxOzonCommissionElement.textContent.replace(/\s/g, '')) || 0;
 2232            const minProfit = parseFloat(minProfitElement.textContent.replace(/\s/g, '')) || 0;
 2233            const maxProfit = parseFloat(maxProfitElement.textContent.replace(/\s/g, '')) || 0;
 2234            const minDiscount = parseFloat(minDiscountElement.textContent.replace(/\s/g, '')) || 0;
 2235            const maxDiscount = parseFloat(maxDiscountElement.textContent.replace(/\s/g, '')) || 0;
 2236
 2237            if (checkbox.checked) {
 2238                // Обновляем значения из оригинальных данных
 2239                document.getElementById('originalPurchasePrice_' + index).textContent = originalPurchasePrice;
 2240                document.getElementById('currencyCode_' + index).textContent = currencyCode;
 2241                document.getElementById('purchasePrice_' + index).textContent = purchasePrice;
 2242                document.getElementById('costPrice_' + index).textContent = costPrice;
 2243
 2244                var startCommission = price - parseFloat(costPriceElement.textContent.replace(/\s/g, '')) || 0;
 2245                var newMinProfit = startCommission - maxOzonCommission;
 2246                var newMaxProfit = startCommission - minOzonCommission;
 2247
 2248                var newMinDiscount = (newMinProfit / parseFloat(costPriceElement.textContent.replace(/\s/g, '')) || 0) *
 2249                var newMaxDiscount = (newMaxProfit / parseFloat(costPriceElement.textContent.replace(/\s/g, '')) || 0) *
 2250
 2251                document.getElementById('minProfit_' + index).textContent = Math.round(newMinProfit);
 2252                document.getElementById('maxProfit_' + index).textContent = Math.round(newMaxProfit);
 2253
 2254                document.getElementById('minDiscount_' + index).textContent = Math.round(newMinDiscount);
 2255                document.getElementById('maxDiscount_' + index).textContent = Math.round(newMaxDiscount);
 2256
 2257                // Обновление прибыли с проверкой на отрицательные значения
 2258                minProfitElement.textContent = Math.round(newMinProfit);
 2259                if (newMinProfit < 0) {
 2260                    minProfitElement.style.color = "red";
 2261                } else {
 2262                    minProfitElement.style.color = "";
 2263                }
 2264
 2265                maxProfitElement.textContent = Math.round(newMaxProfit);
 2266                if (newMaxProfit < 0) {
 2267                    maxProfitElement.style.color = "red";
 2268                } else {
 2269                    maxProfitElement.style.color = "";
 2270                }
 2271
 2272                // Обновление скидки с проверкой на отрицательные значения
 2273                minDiscountElement.textContent = Math.round(newMinDiscount);
 2274                if (newMinDiscount < 0) {
 2275                    minDiscountElement.style.color = "red";
 2276                } else {
 2277                    minDiscountElement.style.color = "";
 2278                }
 2279
 2280                maxDiscountElement.textContent = Math.round(newMaxDiscount);
 2281                if (newMaxDiscount < 0) {
 2282                    maxDiscountElement.style.color = "red";
 2283                } else {
 2284                    maxDiscountElement.style.color = "";
 2285                }
 2286
 2287                console.log('Updated values for index ' + index);
 2288            }
 2289            else {
 2290                // Восстанавливаем исходные значения из скрытых контейнеров (origin_row)
 2291                const originalPurchasePriceElementRow = document.getElementById('originalPurchasePrice_origin_row_' + in
 2292                const currencyCodeElementRow = document.getElementById('currencyCode_origin_row_' + index);
 2293                const purchasePriceElementRow = document.getElementById('purchasePrice_origin_row_' + index);
 2294                const costPriceElementRow = document.getElementById('costPrice_origin_row_' + index);
 2295
 2296                // Восстанавливаем значения прибыли и скидки
 2297                const maxProfitElementRow = document.getElementById('maxProfit_origin_row_' + index);
 2298                const minProfitElementRow = document.getElementById('minProfit_origin_row_' + index);
 2299                const maxDiscountElementRow = document.getElementById('maxDiscount_origin_row_' + index);
 2300                const minDiscountElementRow = document.getElementById('minDiscount_origin_row_' + index);
 2301
 2302                if (originalPurchasePriceElementRow && currencyCodeElementRow && purchasePriceElementRow && costPriceEle
 2303                    maxProfitElementRow && minProfitElementRow && maxDiscountElementRow && minDiscountElementRow) {
 2304
 2305                    document.getElementById('originalPurchasePrice_' + index).textContent = originalPurchasePriceElement
 2306                    document.getElementById('currencyCode_' + index).textContent = currencyCodeElementRow.textContent;
 2307                    document.getElementById('purchasePrice_' + index).textContent = purchasePriceElementRow.textContent;
 2308                    document.getElementById('costPrice_' + index).textContent = costPriceElementRow.textContent;
 2309
 2310                    // Восстанавливаем значения прибыли и подсвечиваем отрицательные
 2311                    const maxProfitElement = document.getElementById('maxProfit_' + index);
 2312                    const minProfitElement = document.getElementById('minProfit_' + index);
 2313                    maxProfitElement.textContent = maxProfitElementRow.textContent;
 2314                    if (parseFloat(maxProfitElementRow.textContent) < 0) {
 2315                        maxProfitElement.style.color = "red";
 2316                    } else {
 2317                        maxProfitElement.style.color = "";
 2318                    }
 2319
 2320                    minProfitElement.textContent = minProfitElementRow.textContent;
 2321                    if (parseFloat(minProfitElementRow.textContent) < 0) {
 2322                        minProfitElement.style.color = "red";
 2323                    } else {
 2324                        minProfitElement.style.color = "";
 2325                    }
 2326
 2327                    // Восстанавливаем значения скидки и подсвечиваем отрицательные
 2328                    const maxDiscountElement = document.getElementById('maxDiscount_' + index);
 2329                    const minDiscountElement = document.getElementById('minDiscount_' + index);
 2330                    maxDiscountElement.textContent = maxDiscountElementRow.textContent;
 2331                    if (parseFloat(maxDiscountElementRow.textContent) < 0) {
 2332                        maxDiscountElement.style.color = "red";
 2333                    } else {
 2334                        maxDiscountElement.style.color = "";
 2335                    }
 2336
 2337                    minDiscountElement.textContent = minDiscountElementRow.textContent;
 2338                    if (parseFloat(minDiscountElementRow.textContent) < 0) {
 2339                        minDiscountElement.style.color = "red";
 2340                    } else {
 2341                        minDiscountElement.style.color = "";
 2342                    }
 2343
 2344                    console.log('Restored original values for index ' + index);
 2345                } else {
 2346                    console.error('Original values for index ' + index + ' are missing');
 2347                }
 2348            }
 2349        } else {
 2350            console.error('One or more elements are missing for index ' + index);
 2351        }
 2352    }
 2353
 2354    function updateFieldsBasedOnPrice(index, price) {
 2355        // Получаем элементы с исходными значениями
 2356        const maxProfitElementRow = document.getElementById('maxProfit_origin_' + index);
 2357        const minProfitElementRow = document.getElementById('minProfit_origin_' + index);
 2358        const maxDiscountElementRow = document.getElementById('maxDiscount_origin_' + index);
 2359        const minDiscountElementRow = document.getElementById('minDiscount_origin_' + index);
 2360        const costPriceElement = document.getElementById('costPrice_origin_' + index);
 2361
 2362        // Получаем элементы с комиссиями
 2363        const minOzonCommissionElement = document.getElementById('minOzonCommission_hidden_' + index);
 2364        const maxOzonCommissionElement = document.getElementById('maxOzonCommission_hidden_' + index);
 2365
 2366        // Получаем элементы для обновления
 2367        const maxProfitElement = document.getElementById('maxProfit_origin_' + index);
 2368        const minProfitElement = document.getElementById('minProfit_origin_' + index);
 2369        const maxDiscountElement = document.getElementById('maxDiscount_origin_' + index);
 2370        const minDiscountElement = document.getElementById('minDiscount_origin_' + index);
 2371
 2372        if (price == null || isNaN(price)) {
 2373            // Если цена не задана, сбрасываем значения и стили
 2374            if (maxProfitElement) {
 2375                maxProfitElement.textContent = '';
 2376                maxProfitElement.style.color = '';
 2377            }
 2378            if (minProfitElement) {
 2379                minProfitElement.textContent = '';
 2380                minProfitElement.style.color = '';
 2381            }
 2382            if (maxDiscountElement) {
 2383                maxDiscountElement.textContent = '';
 2384                maxDiscountElement.style.color = '';
 2385            }
 2386            if (minDiscountElement) {
 2387                minDiscountElement.textContent = '';
 2388                minDiscountElement.style.color = '';
 2389            }
 2390            return;
 2391        }
 2392
 2393        // Убедимся, что все элементы существуют
 2394        if (maxProfitElementRow && minProfitElementRow && maxDiscountElementRow && minDiscountElementRow &&
 2395            costPriceElement && minOzonCommissionElement && maxOzonCommissionElement) {
 2396
 2397            const costPrice = parseFloat(costPriceElement.textContent.replace(/\s/g, '')) || 0;
 2398            const minOzonCommission = parseFloat(minOzonCommissionElement.value.replace(/\s/g, '')) || 0;
 2399            const maxOzonCommission = parseFloat(maxOzonCommissionElement.value.replace(/\s/g, '')) || 0;
 2400
 2401            // Вычисляем значения
 2402            const startCommission = price - costPrice;
 2403            const newMinProfit = startCommission - maxOzonCommission;
 2404            const newMaxProfit = startCommission - minOzonCommission;
 2405
 2406            const newMinDiscount = (newMinProfit / costPrice) * 100 || 0;
 2407            const newMaxDiscount = (newMaxProfit / costPrice) * 100 || 0;
 2408
 2409            // Обновляем значения и стили
 2410            if (maxProfitElement) {
 2411                maxProfitElement.textContent = Math.round(newMaxProfit);
 2412                maxProfitElement.style.color = newMaxProfit < 0 ? "red" : "";
 2413            }
 2414
 2415            if (minProfitElement) {
 2416                minProfitElement.textContent = Math.round(newMinProfit);
 2417                minProfitElement.style.color = newMinProfit < 0 ? "red" : "";
 2418            }
 2419
 2420            if (maxDiscountElement) {
 2421                maxDiscountElement.textContent = Math.round(newMaxDiscount);
 2422                maxDiscountElement.style.color = newMaxDiscount < 0 ? "red" : "";
 2423            }
 2424
 2425            if (minDiscountElement) {
 2426                minDiscountElement.textContent = Math.round(newMinDiscount);
 2427                minDiscountElement.style.color = newMinDiscount < 0 ? "red" : "";
 2428            }
 2429        } else {
 2430            console.error('Некоторые элементы отсутствуют для индекса ' + index);
 2431        }
 2432    }
 2433
 2434    // Пример использования
 2435    document.querySelectorAll('[id^="priceFilter_"]').forEach(function (inputElement) {
 2436        inputElement.addEventListener('input', function () {
 2437            const elementId = this.id;
 2438            const index = elementId.split('_')[1];
 2439            const price = parseFloat(this.value.replace(/\s/g, ''));
 2440
 2441            // Обновляем поля на основе новой цены
 2442            updateFieldsBasedOnPrice(index, isNaN(price) ? null : price);
 2443        });
 2444    });
 2445</script>
 2446
 2447<script>
 2448    function showToast(message) {
 2449        const toastBody = document.getElementById('flashMessageText');
 2450        toastBody.textContent = message;
 2451        const toast = new bootstrap.Toast(document.getElementById('flashMessage'));
 2452        toast.show();
 2453    }
 2454
 2455    let selectedRow = null;
 2456
 2457
 2458    function selectRowForMerge(event, index) {
 2459        console.log(event.target.tagName)
 2460
 2461        if (event.target.tagName === 'TD' ||
 2462            event.target.tagName === 'DIV' ||
 2463            event.target.tagName === 'SPAN') {
 2464            const selectedRowElement = document.getElementById(`selectRowForMerge_${index}`);
 2465
 2466            if (selectedRow === null) {
 2467                selectedRow = index;
 2468
 2469                if (selectedRowElement) {
 2470                    selectedRowElement.classList.remove("table-info");
 2471                    selectedRowElement.classList.add("table-warning");
 2472                }
 2473
 2474                showToast("Выберите строку для объединения с первой выбранной строкой.");
 2475            } else {
 2476                if (selectedRow === index) {
 2477                    // Deselect the first row if clicked again
 2478                    if (selectedRowElement) {
 2479                        selectedRowElement.classList.add("table-info");
 2480                        selectedRowElement.classList.remove("table-warning");
 2481                    }
 2482                    selectedRow = null;
 2483                } else {
 2484                    // Merge with the second row and reset the first row's highlight
 2485                    mergeQuantities(selectedRow, index);
 2486                    const firstRowElement = document.getElementById(`selectRowForMerge_${selectedRow}`);
 2487                    if (firstRowElement) {
 2488                        firstRowElement.classList.add("table-info");
 2489                        firstRowElement.classList.remove("table-warning");
 2490                    }
 2491                    selectedRow = null;
 2492                }
 2493            }
 2494        }
 2495    }
 2496
 2497    // Функция для объединения значений Quantity и скрытия строк ниже второй строки
 2498    function mergeQuantities(firstIndex, secondIndex) {
 2499        // Проверяем совпадение артикула
 2500        const firstRow = document.querySelector(`tr.quantity-row[data-index="${firstIndex}"]`);
 2501        const secondRow = document.querySelector(`tr.quantity-row[data-index="${secondIndex}"]`);
 2502
 2503        if (firstRow && secondRow) {
 2504            const firstArticle = (firstRow.getAttribute('data-article') || '').trim();
 2505            const secondArticle = (secondRow.getAttribute('data-article') || '').trim();
 2506            if (!firstArticle || !secondArticle || firstArticle !== secondArticle) {
 2507                showToast(`Объединение невозможно: артикулы не совпадают (${firstArticle || '—'} ≠ ${secondArticle || '—
 2508                return;
 2509            }
 2510        }
 2511
 2512        if (secondRow) {
 2513            const checkboxInSecondRow = secondRow.querySelector('.record-checkbox');
 2514
 2515            // Проверяем, если установлен чекбокс в самой второй строке
 2516            if (checkboxInSecondRow && checkboxInSecondRow.checked) {
 2517                showToast("Операция объединения прервана: выбрана строка для которой выбрано сопрставление.");
 2518                return; // Прерываем функцию до внесения каких-либо изменений
 2519            }
 2520
 2521            // Проверяем строки ниже второй строки на наличие установленного чекбокса
 2522            let nextRow = secondRow.nextElementSibling;
 2523            while (nextRow && !nextRow.classList.contains('quantity-row')) {
 2524                const checkboxInRow = nextRow.querySelector('.record-checkbox');
 2525
 2526                // Если установлен чекбокс в одной из строк ниже
 2527                if (checkboxInRow && checkboxInRow.checked) {
 2528                    showToast("Операция объединения прервана: выбрана строка для которой выбрано сопрставление.");
 2529                    return;
 2530                }
 2531                nextRow = nextRow.nextElementSibling;
 2532            }
 2533        }
 2534
 2535        // Если нет выбранных чекбоксов, выполняем объединение Quantity и скрытие строк
 2536        const firstQuantity = parseFloat(document.getElementById(`hiddenQuantity_${firstIndex}`).value) || 0;
 2537        const secondQuantity = parseFloat(document.getElementById(`quantityFilter_${secondIndex}`).value) || 0;
 2538        const newQuantity = firstQuantity + secondQuantity;
 2539
 2540        // Обновляем значение Quantity в первой строке
 2541        document.getElementById(`hiddenQuantity_${firstIndex}`).value = newQuantity;
 2542        document.getElementById(`quantityFilter_${firstIndex}`).value = newQuantity;
 2543
 2544        // Скрываем вторую строку и обновляем поле Hidden
 2545        secondRow.style.display = 'none';
 2546        const hiddenField = secondRow.querySelector('.hiddenField');
 2547        if (hiddenField) {
 2548            hiddenField.value = 'True';
 2549        }
 2550
 2551        // Скрываем строки ниже второй строки, пока не встретим строку с классом quantity-row
 2552        let nextRow = secondRow.nextElementSibling;
 2553        while (nextRow && !nextRow.classList.contains('quantity-row')) {
 2554            nextRow.style.display = 'none'; // Скрываем строку
 2555            const hiddenFieldBelow = nextRow.querySelector('.hiddenField');
 2556            if (hiddenFieldBelow) {
 2557                hiddenFieldBelow.value = 'True'; // Устанавливаем значение Hidden в true
 2558            }
 2559
 2560            nextRow = nextRow.nextElementSibling; // Переходим к следующей строке
 2561        }
 2562
 2563        showToast(`Значение объединено Кол: ${newQuantity}`);
 2564        updateGrandTotalSummary();
 2565        debouncedSaveState();
 2566    }
 2567</script>
 2568
 2569<script>
 2570    // Универсальная функция для расчета суммы элементов по переданному классу
 2571    function calculateTotalSum(className) {
 2572        const elements = document.querySelectorAll(`.${className}`);
 2573        let totalSum = 0;
 2574
 2575        elements.forEach(element => {
 2576            // Убираем пробелы из текста и преобразуем его в число
 2577            const numericValue = parseFloat(element.textContent.replace(/\s/g, '')) || 0;
 2578            totalSum += numericValue;
 2579        });
 2580
 2581        console.log(`Сумма для класса .${className}:`, totalSum);
 2582    }
 2583
 2584    // Функция для отслеживания изменений значений элементов по переданному классу
 2585    function observeChanges(className) {
 2586        const elements = document.querySelectorAll(`.${className}`);
 2587
 2588        elements.forEach(element => {
 2589            // Создаем новый MutationObserver для отслеживания изменений в элементе
 2590            const observer = new MutationObserver(() => calculateTotalSum(className));
 2591
 2592            // Настраиваем наблюдатель на изменения в тексте элемента
 2593            observer.observe(element, { characterData: true, subtree: true, childList: true });
 2594        });
 2595    }
 2596
 2597    // Пример использования
 2598    observeChanges("purchasePrice"); // Отслеживаем изменения для элементов с классом 'purchasePrice'
 2599
 2600    // Выполняем начальный расчет для класса 'purchasePrice'
 2601    calculateTotalSum("purchasePrice");
 2602
 2603    // Функция для показа модального окна разделения заказа
 2604    function showSplitOptionsForSetNotFull(rowIndex, quantity) {
 2605        // Создаем модальное окно
 2606        let modal = document.createElement('div');
 2607        modal.style.position = 'fixed';
 2608        modal.style.top = '0';
 2609        modal.style.left = '0';
 2610        modal.style.width = '100%';
 2611        modal.style.height = '100%';
 2612        modal.style.backgroundColor = 'rgba(0,0,0,0.5)';
 2613        modal.style.display = 'flex';
 2614        modal.style.justifyContent = 'center';
 2615        modal.style.alignItems = 'center';
 2616        modal.style.zIndex = '1000';
 2617
 2618        // Создаем контент модального окна
 2619        let content = document.createElement('div');
 2620        content.style.backgroundColor = 'white';
 2621        content.style.padding = '25px';
 2622        content.style.borderRadius = '8px';
 2623        content.style.maxWidth = '500px';
 2624        content.style.width = '90%';
 2625        content.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
 2626        content.style.maxHeight = '80vh';
 2627        content.style.overflowY = 'auto';
 2628
 2629        let title = document.createElement('h4');
 2630        title.textContent = 'Разделение заказа';
 2631        title.style.marginTop = '0';
 2632        title.style.marginBottom = '20px';
 2633        title.style.color = '#333';
 2634        title.style.fontWeight = '600';
 2635        content.appendChild(title);
 2636
 2637        let description = document.createElement('p');
 2638        description.textContent = `Выберите как разделить заказ (${quantity} шт.):`;
 2639        description.style.marginBottom = '15px';
 2640        description.style.color = '#666';
 2641        content.appendChild(description);
 2642
 2643        let optionsDiv = document.createElement('div');
 2644        optionsDiv.style.margin = '15px 0';
 2645        optionsDiv.style.display = 'grid';
 2646        optionsDiv.style.gridTemplateColumns = 'repeat(auto-fill, minmax(100px, 1fr))';
 2647        optionsDiv.style.gap = '10px';
 2648
 2649        // Генерируем все возможные варианты разделения
 2650        for (let i = 1; i < quantity; i++) {
 2651            let option = document.createElement('button');
 2652            option.textContent = `${i} + ${quantity - i}`;
 2653            option.style.padding = '10px 15px';
 2654            option.style.borderRadius = '5px';
 2655            option.style.cursor = 'pointer';
 2656            option.style.transition = 'all 0.2s';
 2657            option.style.border = '1px solid #007bff';
 2658            option.style.background = 'none';
 2659            option.style.color = '#007bff';
 2660            option.style.display = 'flex';
 2661            option.style.justifyContent = 'center';
 2662            option.style.alignItems = 'center';
 2663            option.style.flexDirection = 'column';
 2664
 2665            // Подсветка при наведении
 2666            option.onmouseover = function () {
 2667                this.style.backgroundColor = '#007bff';
 2668                this.style.color = 'white';
 2669            };
 2670            option.onmouseout = function () {
 2671                this.style.backgroundColor = 'transparent';
 2672                this.style.color = '#007bff';
 2673            };
 2674
 2675            option.onclick = function () {
 2676                splitOrderInTable(rowIndex, i, quantity - i);
 2677                document.body.removeChild(modal);
 2678            };
 2679
 2680            optionsDiv.appendChild(option);
 2681        }
 2682
 2683        content.appendChild(optionsDiv);
 2684
 2685        // Группа кнопок внизу
 2686        let buttonGroup = document.createElement('div');
 2687        buttonGroup.style.display = 'flex';
 2688        buttonGroup.style.justifyContent = 'flex-end';
 2689        buttonGroup.style.marginTop = '20px';
 2690        buttonGroup.style.gap = '10px';
 2691
 2692        // Кнопка ручного ввода
 2693        let customBtn = document.createElement('button');
 2694        customBtn.textContent = 'Свой вариант';
 2695        customBtn.style.padding = '8px 16px';
 2696        customBtn.style.border = '1px solid #6c757d';
 2697        customBtn.style.borderRadius = '5px';
 2698        customBtn.style.background = 'none';
 2699        customBtn.style.color = '#6c757d';
 2700        customBtn.style.cursor = 'pointer';
 2701        customBtn.style.transition = 'all 0.2s';
 2702        customBtn.onmouseover = function () {
 2703            this.style.backgroundColor = '#6c757d';
 2704            this.style.color = 'white';
 2705        };
 2706        customBtn.onmouseout = function () {
 2707            this.style.backgroundColor = 'transparent';
 2708            this.style.color = '#6c757d';
 2709        };
 2710        customBtn.onclick = function () {
 2711            let customValue = prompt(`Введите количество для первой части (всего ${quantity} шт.):`);
 2712            if (customValue && !isNaN(customValue) && customValue > 0 && customValue < quantity) {
 2713                let firstPart = parseInt(customValue);
 2714                splitOrderInTable(rowIndex, firstPart, quantity - firstPart);
 2715                document.body.removeChild(modal);
 2716            } else if (customValue !== null) {
 2717                alert('Пожалуйста, введите корректное число между 1 и ' + (quantity - 1));
 2718            }
 2719        };
 2720        buttonGroup.appendChild(customBtn);
 2721
 2722        // Кнопка отмены
 2723        let cancelBtn = document.createElement('button');
 2724        cancelBtn.textContent = 'Отмена';
 2725        cancelBtn.style.padding = '8px 16px';
 2726        cancelBtn.style.border = '1px solid #6c757d';
 2727        cancelBtn.style.borderRadius = '5px';
 2728        cancelBtn.style.background = 'none';
 2729        cancelBtn.style.color = '#6c757d';
 2730        cancelBtn.style.cursor = 'pointer';
 2731        cancelBtn.style.transition = 'all 0.2s';
 2732        cancelBtn.onmouseover = function () {
 2733            this.style.backgroundColor = '#6c757d';
 2734            this.style.color = 'white';
 2735        };
 2736        cancelBtn.onmouseout = function () {
 2737            this.style.backgroundColor = 'transparent';
 2738            this.style.color = '#6c757d';
 2739        };
 2740        cancelBtn.onclick = function () {
 2741            document.body.removeChild(modal);
 2742        };
 2743        buttonGroup.appendChild(cancelBtn);
 2744
 2745        content.appendChild(buttonGroup);
 2746
 2747        modal.appendChild(content);
 2748        document.body.appendChild(modal);
 2749
 2750        // Закрытие по клику вне окна
 2751        modal.onclick = function (e) {
 2752            if (e.target === modal) {
 2753                document.body.removeChild(modal);
 2754            }
 2755        };
 2756    }
 2757
 2758    // Функция для разделения заказа в таблице (создание новой строки)
 2759    function splitOrderInTable(rowIndex, part1, part2) {
 2760        // Находим строку заказа по индексу
 2761        const orderRow = document.getElementById(`selectRowForMerge_${rowIndex}`);
 2762        if (!orderRow) {
 2763            console.error('Строка заказа не найдена');
 2764            return;
 2765        }
 2766
 2767        // Получаем текущее количество
 2768        const quantityField = document.getElementById(`quantityFilter_${rowIndex}`);
 2769        const hiddenQuantityField = document.getElementById(`hiddenQuantity_${rowIndex}`);
 2770
 2771        if (!quantityField || !hiddenQuantityField) {
 2772            console.error('Поля количества не найдены');
 2773            return;
 2774        }
 2775
 2776        // Обновляем количество в исходной строке
 2777        quantityField.value = part1;
 2778        hiddenQuantityField.value = part1;
 2779
 2780        // Клонируем строку для создания нового заказа
 2781        const newRow = orderRow.cloneNode(true);
 2782
 2783        // Получаем максимальный индекс для обновления
 2784        const allRows = document.querySelectorAll('tr');
 2785        let maxIndex = 0;
 2786        allRows.forEach(r => {
 2787            r.querySelectorAll('input, select').forEach(el => {
 2788                const match = el.name ? el.name.match(/\[(\d+)\]/) : null;
 2789                if (match) {
 2790                    const index = parseInt(match[1], 10);
 2791                    if (index > maxIndex) {
 2792                        maxIndex = index;
 2793                    }
 2794                }
 2795            });
 2796        });
 2797        const newIndex = maxIndex + 1;
 2798
 2799        // Обновляем все элементы в новой строке
 2800        const allElements = newRow.querySelectorAll('*');
 2801        allElements.forEach(element => {
 2802            // Обновляем name атрибуты
 2803            if (element.name) {
 2804                element.name = element.name.replace(/\[(\d+)\]/, `[${newIndex}]`);
 2805            }
 2806            // Обновляем id атрибуты - заменяем все вхождения старого индекса
 2807            if (element.id) {
 2808                // Используем более точную замену с учетом границ слова
 2809                element.id = element.id.replace(new RegExp(`_${rowIndex}(?![\\d])`, 'g'), `_${newIndex}`);
 2810                // Также заменяем в конце строки
 2811                if (element.id.endsWith(`_${rowIndex}`)) {
 2812                    element.id = element.id.replace(`_${rowIndex}`, `_${newIndex}`);
 2813                }
 2814            }
 2815            // Обновляем for атрибуты в label (если есть)
 2816            if (element.htmlFor) {
 2817                element.htmlFor = element.htmlFor.replace(new RegExp(`_${rowIndex}(?![\\d])`, 'g'), `_${newIndex}`);
 2818            }
 2819            // Обновляем атрибуты data-*
 2820            Array.from(element.attributes).forEach(attr => {
 2821                if (attr.name.startsWith('data-') && attr.value) {
 2822                    if (attr.value.includes(`_${rowIndex}`) && !attr.value.includes(`_${rowIndex}` + '0')) {
 2823                        attr.value = attr.value.replace(new RegExp(`_${rowIndex}(?![\\d])`, 'g'), `_${newIndex}`);
 2824                    }
 2825                }
 2826            });
 2827        });
 2828
 2829        // Обновляем количество в новой строке
 2830        const newQuantityField = newRow.querySelector(`input[id*="quantityFilter_${newIndex}"]`) ||
 2831                                 newRow.querySelector(`input[id*="quantityFilter"][name*="[${newIndex}]"]`);
 2832        const newHiddenQuantityField = newRow.querySelector(`input[id*="hiddenQuantity_${newIndex}"]`) ||
 2833                                       newRow.querySelector(`input[type="hidden"][name*="Quantity"][name*="[${newIndex}]
 2834
 2835        if (newQuantityField) {
 2836            newQuantityField.value = part2;
 2837        } else {
 2838            // Ищем по name атрибуту
 2839            const quantityInputs = newRow.querySelectorAll(`input[name*="[${newIndex}].Order.Quantity"]`);
 2840            quantityInputs.forEach(input => {
 2841                if (input.id && input.id.includes('quantityFilter')) {
 2842                    input.value = part2;
 2843                }
 2844            });
 2845        }
 2846
 2847        if (newHiddenQuantityField) {
 2848            newHiddenQuantityField.value = part2;
 2849        } else {
 2850            // Ищем скрытое поле по name
 2851            const hiddenInputs = newRow.querySelectorAll(`input[type="hidden"][name*="[${newIndex}].Order.Quantity"]`);
 2852            hiddenInputs.forEach(input => {
 2853                if (input.id && input.id.includes('hiddenQuantity')) {
 2854                    input.value = part2;
 2855                }
 2856            });
 2857        }
 2858
 2859        // Обновляем id строки
 2860        newRow.id = `selectRowForMerge_${newIndex}`;
 2861        newRow.setAttribute('data-index', newIndex);
 2862
 2863        // Обновляем обработчики событий для новой строки
 2864        newRow.onclick = function(event) {
 2865            selectRowForMerge(event, newIndex);
 2866        };
 2867        newRow.oncontextmenu = function(event) {
 2868            event.preventDefault();
 2869            const qtyField = document.getElementById(`quantityFilter_${newIndex}`);
 2870            if (qtyField) {
 2871                const quantity = parseInt(qtyField.value) || 0;
 2872                if (quantity > 1) {
 2873                    showSplitOptionsForSetNotFull(newIndex, quantity);
 2874                }
 2875            }
 2876        };
 2877
 2878        // Обновляем обработчики для всех элементов в новой строке
 2879        const newRowInputs = newRow.querySelectorAll('input, select');
 2880        newRowInputs.forEach(input => {
 2881            // Обновляем обработчики для полей
 2882            if (input.name && input.name.includes('OrderNumberToSupplier')) {
 2883                input.oninput = function() {
 2884                    handleOrderNumberToSupplierChange(this);
 2885                };
 2886            }
 2887            if (input.id && input.id.includes('statusFilter')) {
 2888                input.onchange = function() {
 2889                    handleStatusChange(this);
 2890                };
 2891            }
 2892            if (input.id && input.id.includes('clientFilter')) {
 2893                input.onchange = function() {
 2894                    if (input.classList.contains('ozonClientFilterUnique')) {
 2895                        handleOzonClientChangeUnique(this);
 2896                    } else {
 2897                        handleOzonClientChange(this);
 2898                    }
 2899                };
 2900            }
 2901        });
 2902
 2903        // Замена кнопки на input в новой строке (если есть)
 2904        const buttonElement = newRow.querySelector('.moveToNewButton');
 2905        if (buttonElement) {
 2906            const inputField = document.createElement('input');
 2907            inputField.className = 'form-control form-control-sm orderNumberToSupplie-field';
 2908            inputField.name = `NotFullOrders[${newIndex}].Order.ShipmentNumber`;
 2909            inputField.id = `shipmentNumber_${newIndex}`;
 2910            inputField.placeholder = 'Номер заказа';
 2911            const numberInExcel = newRow.dataset.numberInExcel;
 2912            inputField.value = buildShipmentNumber(numberInExcel || rowIndex);
 2913            buttonElement.replaceWith(inputField);
 2914        }
 2915
 2916        // Устанавливаем IsUniqueOrder = true для нового заказа
 2917        // Ищем поле после обновления имен (может быть уже обновлено в цикле выше)
 2918        let isUniqueOrderInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2919            input => input.name && input.name.includes('IsUniqueOrder') && input.name.includes(`[${newIndex}]`)
 2920        );
 2921        if (!isUniqueOrderInput) {
 2922            // Ищем по старому паттерну или создаем новое
 2923            isUniqueOrderInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2924                input => input.name && input.name.includes('IsUniqueOrder')
 2925            );
 2926            if (isUniqueOrderInput) {
 2927                isUniqueOrderInput.name = `NotFullOrders[${newIndex}].IsUniqueOrder`;
 2928            } else {
 2929                isUniqueOrderInput = document.createElement('input');
 2930                isUniqueOrderInput.type = 'hidden';
 2931                isUniqueOrderInput.name = `NotFullOrders[${newIndex}].IsUniqueOrder`;
 2932                newRow.appendChild(isUniqueOrderInput);
 2933            }
 2934        }
 2935        isUniqueOrderInput.value = 'true';
 2936
 2937        // Очищаем OrderIds для нового заказа (не Order.OrderIds, а именно OrderIds)
 2938        let orderIdsInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2939            input => input.name && input.name.includes('OrderIds') &&
 2940                     !input.name.includes('Order.OrderIds') && input.name.includes(`[${newIndex}]`)
 2941        );
 2942        if (!orderIdsInput) {
 2943            // Ищем по старому паттерну или создаем новое
 2944            orderIdsInput = Array.from(newRow.querySelectorAll('input[type="hidden"]')).find(
 2945                input => input.name && input.name.includes('OrderIds') && !input.name.includes('Order.OrderIds')
 2946            );
 2947            if (orderIdsInput) {
 2948                orderIdsInput.name = `NotFullOrders[${newIndex}].OrderIds`;
 2949            } else {
 2950                orderIdsInput = document.createElement('input');
 2951                orderIdsInput.type = 'hidden';
 2952                orderIdsInput.name = `NotFullOrders[${newIndex}].OrderIds`;
 2953                newRow.appendChild(orderIdsInput);
 2954            }
 2955        }
 2956        orderIdsInput.value = '';
 2957
 2958        // Обновляем блок clientContainer в новой строке
 2959        const oldClientContainerId = `clientContainer_${rowIndex}`;
 2960        const newClientContainer =
 2961            newRow.querySelector(`#${oldClientContainerId}`) ||
 2962            newRow.querySelector(`#clientContainer_${newIndex}`);
 2963        if (newClientContainer) {
 2964            newClientContainer.id = `clientContainer_${newIndex}`;
 2965            newClientContainer.style.display = 'block';
 2966        }
 2967
 2968        // Вставляем новую строку после исходной
 2969        orderRow.parentNode.insertBefore(newRow, orderRow.nextSibling);
 2970
 2971        // Инициализируем обработчики для новой строки
 2972        initializeSplitHandlers();
 2973
 2974        showToast(`Заказ разделен: ${part1} + ${part2}`);
 2975        updateGrandTotalSummary();
 2976        debouncedSaveState();
 2977    }
 2978
 2979    // Функция для показа модального окна разделения заказа из системы
 2980    function showSplitOptionsForSystemOrder(orderId, orderIndex, quantity) {
 2981        hideContextMenu();
 2982
 2983        function showStep1() {
 2984            let modal = document.createElement('div');
 2985            modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display
 2986            let content = document.createElement('div');
 2987            content.style.cssText = 'background:white;padding:25px;border-radius:8px;max-width:400px;width:90%;box-shado
 2988            content.innerHTML = '<h4 style="margin:0 0 15px 0;color:#333;font-weight:600">Способ разделения</h4>' +
 2989                '<p style="margin-bottom:15px;color:#666">Разделить на Озон и в системе / только в системе</p>' +
 2990                '<div style="display:flex;flex-direction:column;gap:10px;margin-bottom:15px">' +
 2991                '<button class="split-mode-btn" data-only="false" style="padding:10px 15px;border-radius:5px;cursor:poin
 2992                '<button class="split-mode-btn" data-only="true" style="padding:10px 15px;border-radius:5px;cursor:point
 2993                '</div>' +
 2994                '<button class="split-cancel-btn" style="padding:8px 16px;border:1px solid #6c757d;border-radius:5px;bac
 2995            modal.appendChild(content);
 2996            document.body.appendChild(modal);
 2997            content.querySelectorAll('.split-mode-btn').forEach(function(btn) {
 2998                btn.onclick = function() {
 2999                    document.body.removeChild(modal);
 3000                    showStep2(this.getAttribute('data-only') === 'true');
 3001                };
 3002            });
 3003            content.querySelector('.split-cancel-btn').onclick = function() { document.body.removeChild(modal); };
 3004            modal.onclick = function(e) { if (e.target === modal) document.body.removeChild(modal); };
 3005        }
 3006
 3007        function showStep2(onlyInSystem) {
 3008            let modal = document.createElement('div');
 3009            modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display
 3010            let content = document.createElement('div');
 3011            content.style.cssText = 'background:white;padding:25px;border-radius:8px;max-width:500px;width:90%;box-shado
 3012            let title = document.createElement('h4');
 3013            title.textContent = 'Разделение заказа';
 3014            title.style.cssText = 'margin:0 0 20px 0;color:#333;font-weight:600';
 3015            let description = document.createElement('p');
 3016            description.textContent = 'Выберите как разделить заказ (' + quantity + ' шт.):';
 3017            description.style.cssText = 'margin-bottom:15px;color:#666';
 3018            let optionsDiv = document.createElement('div');
 3019            optionsDiv.style.cssText = 'margin:15px 0;display:grid;grid-template-columns:repeat(auto-fill, minmax(100px,
 3020            for (let i = 1; i < quantity; i++) {
 3021                let option = document.createElement('button');
 3022                option.textContent = i + ' + ' + (quantity - i);
 3023                option.style.cssText = 'padding:10px 15px;border-radius:5px;cursor:pointer;border:1px solid #007bff;back
 3024                option.onclick = function() {
 3025                    splitSystemOrder(orderId, orderIndex, i, quantity - i, onlyInSystem);
 3026                    document.body.removeChild(modal);
 3027                };
 3028                optionsDiv.appendChild(option);
 3029            }
 3030            let buttonGroup = document.createElement('div');
 3031            buttonGroup.style.cssText = 'display:flex;justify-content:flex-end;margin-top:20px;gap:10px';
 3032            let customBtn = document.createElement('button');
 3033            customBtn.textContent = 'Свой вариант';
 3034            customBtn.style.cssText = 'padding:8px 16px;border:1px solid #6c757d;border-radius:5px;background:none;color
 3035            customBtn.onclick = function() {
 3036                let customValue = prompt('Введите количество для первой части (всего ' + quantity + ' шт.):');
 3037                if (customValue && !isNaN(customValue) && customValue > 0 && customValue < quantity) {
 3038                    let firstPart = parseInt(customValue);
 3039                    splitSystemOrder(orderId, orderIndex, firstPart, quantity - firstPart, onlyInSystem);
 3040                    document.body.removeChild(modal);
 3041                } else if (customValue !== null) {
 3042                    alert('Пожалуйста, введите корректное число между 1 и ' + (quantity - 1));
 3043                }
 3044            };
 3045            let cancelBtn = document.createElement('button');
 3046            cancelBtn.textContent = 'Отмена';
 3047            cancelBtn.style.cssText = 'padding:8px 16px;border:1px solid #6c757d;border-radius:5px;background:none;color
 3048            cancelBtn.onclick = function() { document.body.removeChild(modal); };
 3049            buttonGroup.appendChild(customBtn);
 3050            buttonGroup.appendChild(cancelBtn);
 3051            content.appendChild(title);
 3052            content.appendChild(description);
 3053            content.appendChild(optionsDiv);
 3054            content.appendChild(buttonGroup);
 3055            modal.appendChild(content);
 3056            document.body.appendChild(modal);
 3057            modal.onclick = function(e) { if (e.target === modal) document.body.removeChild(modal); };
 3058        }
 3059        showStep1();
 3060    }
 3061
 3062    // Функция для разделения заказа из системы (вызов серверного метода)
 3063    async function splitSystemOrder(orderId, orderIndex, part1, part2, onlyInSystem) {
 3064        try {
 3065            showToast('Разделение заказа...');
 3066
 3067            const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value ||
 3068                         document.querySelector('input[type="hidden"][name*="RequestVerificationToken"]')?.value || '';
 3069            const onlyParam = onlyInSystem ? '&onlyInSystem=true' : '';
 3070
 3071            const response = await fetch('/Studio2/Orders/SplitOrderFromSetNotFull?orderId=' + orderId + '&splitOption='
 3072                method: 'POST',
 3073                headers: {
 3074                    'RequestVerificationToken': token,
 3075                    'Content-Type': 'application/x-www-form-urlencoded'
 3076                },
 3077                body: '__RequestVerificationToken=' + encodeURIComponent(token)
 3078            });
 3079
 3080            if (!response.ok) {
 3081                throw new Error(`HTTP error! status: ${response.status}`);
 3082            }
 3083
 3084            const result = await response.json();
 3085
 3086            if (result.success) {
 3087                showToast(result.message);
 3088                // Перезагружаем страницу для отображения разделенных заказов
 3089                setTimeout(() => {
 3090                    window.location.reload();
 3091                }, 1000);
 3092            } else {
 3093                showToast(result.message || 'Ошибка при разделении заказа');
 3094            }
 3095        } catch (error) {
 3096            console.error('Ошибка при разделении заказа:', error);
 3097            showToast('Произошла ошибка при разделении заказа: ' + error.message);
 3098        }
 3099    }
 3100
 3101    // Функция для показа контекстного меню для заказов из системы
 3102    function showContextMenuForSystemOrder(orderId, orderIndex, quantity, event) {
 3103        // Предотвращаем стандартное контекстное меню браузера
 3104        if (event) {
 3105            event.preventDefault();
 3106            event.stopPropagation();
 3107        }
 3108
 3109        // Получаем или создаем окно контекстного меню
 3110        var infoWindow = document.getElementById("infoWindow");
 3111        if (!infoWindow) {
 3112            return;
 3113        }
 3114
 3115        // Задаем позицию окна в соответствии с положением курсора
 3116        if (event) {
 3117            infoWindow.style.left = (event.clientX + 10) + "px";
 3118            infoWindow.style.top = (event.clientY + 10) + "px";
 3119        }
 3120
 3121        // Заполняем окно информацией
 3122        infoWindow.innerHTML = '';
 3123
 3124        // Добавляем пункт "Разделить" только если количество > 1
 3125        if (quantity > 1) {
 3126            infoWindow.innerHTML += '<a href="#" onclick="hideContextMenu(); showSplitOptionsForSystemOrder(' + orderId 
 3127        } else {
 3128            infoWindow.innerHTML += '<span class="context-menu-item disabled">Разделить (недоступно)</span>';
 3129        }
 3130
 3131        // Показываем окно
 3132        infoWindow.style.display = "block";
 3133
 3134        // Скрытие окна при клике вне его
 3135        setTimeout(function() {
 3136            document.addEventListener("click", function hideMenuOnClick(e) {
 3137                if (!infoWindow.contains(e.target)) {
 3138                    hideContextMenu();
 3139                    document.removeEventListener("click", hideMenuOnClick);
 3140                }
 3141            }, { once: true });
 3142        }, 100);
 3143    }
 3144
 3145    // Функция для скрытия контекстного меню
 3146    function hideContextMenu() {
 3147        var infoWindow = document.getElementById("infoWindow");
 3148        if (infoWindow) {
 3149            infoWindow.style.display = "none";
 3150        }
 3151    }
 3152
 3153    // Инициализация обработчиков правого клика для строк заказов из системы
 3154    function initializeSystemOrderSplitHandlers() {
 3155        // Добавляем обработчики правого клика для всех строк с заказами из системы
 3156        document.querySelectorAll('tr.system-order-row').forEach(function(row) {
 3157            const orderId = row.getAttribute('data-order-id');
 3158            const orderIndex = row.getAttribute('data-order-index');
 3159            const quantity = parseInt(row.getAttribute('data-order-quantity')) || 0;
 3160
 3161            if (orderId && orderIndex && !row.hasAttribute('data-system-split-handler-initialized')) {
 3162                row.setAttribute('data-system-split-handler-initialized', 'true');
 3163                row.oncontextmenu = function(event) {
 3164                    event.preventDefault();
 3165                    event.stopPropagation();
 3166                    showContextMenuForSystemOrder(orderId, orderIndex, quantity, event);
 3167                    return false;
 3168                };
 3169            }
 3170        });
 3171    }
 3172
 3173    // Инициализация обработчиков правого клика для строк заказов из Excel (старая функция)
 3174    function initializeSplitHandlers() {
 3175        // Добавляем обработчики правого клика для всех строк с заказами из Excel
 3176        document.querySelectorAll('tr.quantity-row').forEach(function(row) {
 3177            const rowIndex = row.getAttribute('data-index');
 3178            if (rowIndex && !row.hasAttribute('data-split-handler-initialized')) {
 3179                row.setAttribute('data-split-handler-initialized', 'true');
 3180                row.oncontextmenu = function(event) {
 3181                    event.preventDefault();
 3182                    const quantityField = document.getElementById(`quantityFilter_${rowIndex}`);
 3183                    if (quantityField) {
 3184                        const quantity = parseInt(quantityField.value) || 0;
 3185                        if (quantity > 1) {
 3186                            showSplitOptionsForSetNotFull(rowIndex, quantity);
 3187                        } else {
 3188                            showToast('Невозможно разделить заказ с количеством 1 или меньше');
 3189                        }
 3190                    }
 3191                };
 3192            }
 3193        });
 3194    }
 3195
 3196    // Инициализация обработчиков при загрузке страницы
 3197    document.addEventListener('DOMContentLoaded', function() {
 3198        // Инициализируем обработчики разделения заказов из Excel
 3199        initializeSplitHandlers();
 3200        // Инициализируем обработчики разделения заказов из системы
 3201        initializeSystemOrderSplitHandlers();
 3202
 3203        // Инициализация tooltip для иконок информации о распределении
 3204        var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
 3205        var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
 3206            return new bootstrap.Tooltip(tooltipTriggerEl);
 3207        });
 3208
 3209        // Показываем модальное окно для выбора статуса только если есть строки без выбранного статуса
 3210        var selectStatusModal = new bootstrap.Modal(document.getElementById('selectStatusModal'));
 3211        var statusSelects = document.querySelectorAll('select.statusFilter:not(.statusFilterHead)');
 3212        var allStatusesFilled = statusSelects.length > 0 && Array.from(statusSelects).every(function(sel) {
 3213            var row = sel.closest('tr');
 3214            if (!row || (row.style && row.style.display === 'none')) return true;
 3215            return !!sel.value;
 3216        });
 3217        if (!allStatusesFilled && !hasStatusModalBeenSeen()) {
 3218            markStatusModalSeen();
 3219            selectStatusModal.show();
 3220        }
 3221
 3222        // Элементы модального окна
 3223        var initialStatusSelect = document.getElementById('initialStatusSelect');
 3224        var applyStatusBtn = document.getElementById('applyStatusBtn');
 3225        var skipStatusBtn = document.getElementById('skipStatusBtn');
 3226
 3227        // Включаем/отключаем кнопку "Применить" в зависимости от выбора
 3228        initialStatusSelect.addEventListener('change', function() {
 3229            applyStatusBtn.disabled = !this.value;
 3230        });
 3231
 3232        // Обработчик кнопки "Применить"
 3233        applyStatusBtn.addEventListener('click', function() {
 3234            var selectedStatusId = initialStatusSelect.value;
 3235            if (selectedStatusId) {
 3236                var selectedOption = initialStatusSelect.options[initialStatusSelect.selectedIndex];
 3237                var bgColor = selectedOption ? selectedOption.getAttribute('data-status-color') || 'transparent' : 'tran
 3238                var textColor = selectedOption ? selectedOption.getAttribute('data-text-color') || '#000000' : '#000000'
 3239
 3240                // Находим основной select статуса в заголовке таблицы
 3241                // Ищем основной select статуса в шапке таблицы
 3242                var mainStatusSelect = document.querySelector('select#statusSelectUniversal.statusFilter:not(.statusFilt
 3243                if (mainStatusSelect) {
 3244                    mainStatusSelect.value = selectedStatusId;
 3245                    // Обновляем стили основного select
 3246                    updateSelectStatusColor(mainStatusSelect, bgColor, textColor);
 3247                    // Вызываем handleStatusChange для применения статуса ко всем заказам
 3248                    handleStatusChange(mainStatusSelect);
 3249                } else {
 3250                    // Если не нашли по селектору, ищем первый select со статусами
 3251                    var allStatusSelects = document.querySelectorAll('select.statusFilter:not(.statusFilterHead)');
 3252                    if (allStatusSelects.length > 0) {
 3253                        var firstStatusSelect = allStatusSelects[0];
 3254                        firstStatusSelect.value = selectedStatusId;
 3255                        updateSelectStatusColor(firstStatusSelect, bgColor, textColor);
 3256                        handleStatusChange(firstStatusSelect);
 3257                    }
 3258                }
 3259            }
 3260
 3261            // Закрываем модальное окно
 3262            markStatusModalSeen();
 3263            selectStatusModal.hide();
 3264        });
 3265
 3266        // Обновляем цвет модального select при изменении
 3267        initialStatusSelect.addEventListener('change', function() {
 3268            if (this.value) {
 3269                var selectedOption = this.options[this.selectedIndex];
 3270                var bgColor = selectedOption ? selectedOption.getAttribute('data-status-color') || 'transparent' : 'tran
 3271                var textColor = selectedOption ? selectedOption.getAttribute('data-text-color') || '#000000' : '#000000'
 3272                updateSelectStatusColor(this, bgColor, textColor);
 3273            } else {
 3274                this.style.backgroundColor = '';
 3275                this.style.color = '';
 3276            }
 3277        });
 3278
 3279        // Обработчик кнопки "Пропустить"
 3280        skipStatusBtn.addEventListener('click', function() {
 3281            markStatusModalSeen();
 3282            selectStatusModal.hide();
 3283        });
 3284
 3285        // Скрываем оверлей загрузки после завершения всей инициализации
 3286        const loader = document.getElementById('page-loader');
 3287        if (loader) {
 3288            loader.classList.add('hidden');
 3289            loader.addEventListener('transitionend', function() { loader.remove(); }, { once: true });
 3290        }
 3291    });
 3292
 3293</script>