< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_Orders_PrepaExcelTable
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Orders/PrepaExcelTable.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 110
Coverable lines: 110
Total lines: 1487
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 154
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%2550500%
<ExecuteAsync()100%210%
<ExecuteAsync()100%210%
<ExecuteAsync()0%117721080%

File(s)

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

#LineLine coverage
 1@using Newtonsoft.Json
 2@using OzonOrdersWeb.ViewModels.OrderViewModels
 3
 4@model ExelDataViewModel
 5
 6<style>
 7    .scrollable-list {
 8        border: 1px solid #dee2e6;
 9        border-radius: 0.25rem;
 10    }
 11
 12    .scrollable-list .list-group-item {
 13        border-left: none;
 14        border-right: none;
 15    }
 16
 17    .scrollable-list .list-group-item:first-child {
 18        border-top: none;
 19    }
 20
 21    .scrollable-list .list-group-item:last-child {
 22        border-bottom: none;
 23    }
 24
 25    .scrollable-list::-webkit-scrollbar {
 26        width: 8px;
 27    }
 28
 29    .scrollable-list::-webkit-scrollbar-track {
 30        background: #f1f1f1;
 31        border-radius: 4px;
 32    }
 33
 34    .scrollable-list::-webkit-scrollbar-thumb {
 35        background: #888;
 36        border-radius: 4px;
 37    }
 38
 39    .scrollable-list::-webkit-scrollbar-thumb:hover {
 40        background: #555;
 41    }
 42
 43    .loading-overlay {
 44        display: none;
 45        position: fixed;
 46        top: 0;
 47        left: 0;
 48        width: 100%;
 49        height: 100%;
 50        background-color: rgba(0, 0, 0, 0.5);
 51        z-index: 9999;
 52        justify-content: center;
 53        align-items: center;
 54    }
 55
 56    .loading-overlay.show {
 57        display: flex;
 58    }
 59
 60    .loading-spinner {
 61        background-color: white;
 62        border-radius: 10px;
 63        padding: 30px 40px;
 64        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 65        text-align: center;
 66    }
 67
 68    .spinner-border {
 69        width: 3rem;
 70        height: 3rem;
 71        border: 4px solid #f3f3f3;
 72        border-top: 4px solid #0d6efd;
 73        border-radius: 50%;
 74        animation: spin 1s linear infinite;
 75        margin: 0 auto 15px;
 76    }
 77
 78    @@keyframes spin {
 79        0% { transform: rotate(0deg); }
 80        100% { transform: rotate(360deg); }
 81    }
 82
 83    .loading-text {
 84        color: #333;
 85        font-size: 16px;
 86        font-weight: 500;
 87        margin-top: 10px;
 88    }
 89
 90    .file-loading-indicator {
 91        display: none;
 92        margin-left: 8px;
 93        vertical-align: middle;
 94    }
 95
 96    .file-loading-indicator.show {
 97        display: inline-flex;
 98        align-items: center;
 99        gap: 4px;
 100    }
 101
 102    .file-loading-spinner {
 103        width: 14px;
 104        height: 14px;
 105        border: 2px solid #dee2e6;
 106        border-top: 2px solid #0d6efd;
 107        border-radius: 50%;
 108        animation: spin 0.8s linear infinite;
 109        display: inline-block;
 110    }
 111
 112    .file-loading-text {
 113        font-size: 12px;
 114        color: #6c757d;
 115    }
 116
 117    .table-responsive {
 118        overflow-x: auto;
 119        -webkit-overflow-scrolling: touch;
 120        width: 100%;
 121    }
 122
 123    .mapping-table-container {
 124        max-height: none;
 125        overflow: visible;
 126    }
 127
 128    .preview-table-container {
 129        max-height: 400px;
 130        overflow: auto;
 131    }
 132
 133    .preview-table-container thead th {
 134        position: sticky;
 135        top: 0;
 136        background: #f8f9fa;
 137        z-index: 2;
 138    }
 139
 140    .form-section-card {
 141        border: 1px solid #e9ecef;
 142        border-radius: .75rem;
 143        box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, .05);
 144    }
 145
 146    .form-section-card .card-header {
 147        background: #f8f9fa;
 148        border-bottom: 1px solid #e9ecef;
 149        border-radius: .75rem .75rem 0 0;
 150    }
 151
 152    .table-success {
 153        background-color: #d4edda !important;
 154    }
 155
 156    /* Карточки-опции в нижней секции */
 157    .option-card {
 158        cursor: pointer;
 159        transition: border-color 0.15s, background-color 0.15s;
 160        border: 1.5px solid #dee2e6 !important;
 161        user-select: none;
 162        min-height: 80px;
 163    }
 164
 165    .option-card:hover {
 166        border-color: #0d6efd !important;
 167        background-color: #f0f4ff;
 168    }
 169
 170    .option-card.active {
 171        border-color: #0d6efd !important;
 172        background-color: #ebf2ff;
 173    }
 174
 175    .option-card input[type="checkbox"] {
 176        margin-top: 2px;
 177        flex-shrink: 0;
 178        cursor: pointer;
 179    }
 180
 181    /* Предпросмотр валютной таблицы в модалке */
 182    .currency-preview-table th {
 183        background: #f8f9fa;
 184        font-size: 0.85rem;
 185        white-space: nowrap;
 186    }
 187
 188    .currency-preview-table td {
 189        font-size: 0.85rem;
 190    }
 191
 192    .price-converted {
 193        color: #198754;
 194        font-weight: 600;
 195    }
 196
 197    .price-original {
 198        color: #6c757d;
 199        text-decoration: line-through;
 200        font-size: 0.8rem;
 201    }
 202
 203    .rate-info-badge {
 204        background: #e8f4fd;
 205        border: 1px solid #bee3f8;
 206        border-radius: 6px;
 207        padding: 8px 12px;
 208        font-size: 0.875rem;
 209    }
 210</style>
 211
 212@{
 0213    var currencyCodeMap = new Dictionary<int, string>
 0214    {
 0215        { 1, "USD" },
 0216        { 2, "EUR" },
 0217        { 3, "RUB" },
 0218        { 4, "BYN" }
 0219    };
 220
 0221    var currencyCodeMapJson = JsonConvert.SerializeObject(currencyCodeMap);
 0222    var previewDataJson = JsonConvert.SerializeObject(Model.TableData.Take(5).ToList());
 223
 0224    decimal rateUSD = ViewBag.RateUSD ?? 0m;
 0225    decimal rateEUR = ViewBag.RateEUR ?? 0m;
 0226    decimal rateBYN = ViewBag.RateBYN ?? 0m;
 0227    var cbRatesJson = JsonConvert.SerializeObject(new { USD = rateUSD, EUR = rateEUR, BYN = rateBYN, RUB = 1m });
 228}
 229
 230<!-- Сброс localStorage-состояния шага SetNotFullOrdersData при открытии новой предобработки файла -->
 231<script>
 232    (function () {
 233        try {
 234            const prefixes = ['setNotFullOrdersDataState:', 'setNotFullOrdersDataStatusModalSeen:'];
 235            for (let i = localStorage.length - 1; i >= 0; i--) {
 236                const key = localStorage.key(i);
 237                if (!key) continue;
 238                if (prefixes.some(p => key.startsWith(p))) localStorage.removeItem(key);
 239            }
 240        } catch (e) {}
 241    })();
 242</script>
 243
 244<!-- Индикатор загрузки -->
 245<div class="loading-overlay" id="loadingOverlay">
 246    <div class="loading-spinner">
 247        <div class="spinner-border" role="status">
 248            <span class="visually-hidden">Загрузка...</span>
 249        </div>
 250        <div class="loading-text">Обработка файла, пожалуйста подождите...</div>
 251    </div>
 252</div>
 253
 254<!-- Модальное окно: Конвертация валюты -->
 255<div class="modal fade" id="currencyModal" tabindex="-1" aria-labelledby="currencyModalLabel" aria-hidden="true">
 256    <div class="modal-dialog modal-lg modal-dialog-centered">
 257        <div class="modal-content">
 258            <div class="modal-header">
 259                <h5 class="modal-title" id="currencyModalLabel">
 260                    <i class="bi bi-currency-exchange me-2"></i>Код валюты отправления
 261                </h5>
 262                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 263            </div>
 264            <div class="modal-body">
 265                <div class="row g-3 mb-3">
 266                    <div class="col-md-5">
 267                        <label class="form-label fw-semibold">Валюта цен в файле</label>
 268                        <select class="form-select" id="currencySelect" onchange="onCurrencyChange()">
 0269                            @foreach (var c in Model.CurrencyCodes)
 270                            {
 0271                                <option value="@c.Item2">@c.Item2</option>
 272                            }
 273                        </select>
 274                    </div>
 275                    <div class="col-md-7" id="rateBlock" style="display:none;">
 276                        <label class="form-label fw-semibold">Курс к рублю</label>
 277                        <div class="input-group">
 278                            <input type="number" class="form-control" id="currencyRateInput"
 279                                   min="0.0001" step="0.01" placeholder="Введите курс"
 280                                   oninput="updateCurrencyPreview()">
 281                            <span class="input-group-text text-muted" id="rateSourceLabel">ЦБ РФ</span>
 282                        </div>
 283                        <div class="rate-info-badge mt-2" id="rateInfoBadge"></div>
 284                    </div>
 285                </div>
 286
 287                <!-- Предпросмотр пересчёта цен -->
 288                <div id="currencyPreviewBlock">
 289                    <h6 class="text-muted mb-2">
 290                        <i class="bi bi-table me-1"></i>Предпросмотр данных
 291                        <small class="fw-normal">(первые 5 строк)</small>
 292                    </h6>
 293                    <div class="table-responsive" style="max-height:280px; overflow:auto;">
 294                        <table class="table table-sm table-bordered table-hover mb-0 currency-preview-table">
 295                            <thead id="currencyPreviewHead"></thead>
 296                            <tbody id="currencyPreviewBody"></tbody>
 297                        </table>
 298                    </div>
 299                    <p class="text-muted mt-2 mb-0" id="currencyPreviewEmpty" style="display:none;">
 300                        <i class="bi bi-info-circle me-1"></i>Сопоставьте столбец «Цена» в таблице выше, чтобы увидеть п
 301                    </p>
 302                </div>
 303            </div>
 304            <div class="modal-footer">
 305                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
 306                <button type="button" class="btn btn-primary" onclick="confirmCurrencyAndProceed()">
 307                    Продолжить <i class="bi bi-arrow-right ms-1"></i>
 308                </button>
 309            </div>
 310        </div>
 311    </div>
 312</div>
 313
 314<!-- Модальное окно: Сохранение маппинга -->
 315<div class="modal fade" id="saveMappingModal" tabindex="-1" role="dialog" aria-labelledby="saveMappingModalLabel" aria-h
 316    <div class="modal-dialog" role="document">
 317        <div class="modal-content">
 318            <div class="modal-header">
 319                <h5 class="modal-title" id="saveMappingModalLabel">Сохранить сопоставление столбцов</h5>
 320            </div>
 321            <div class="modal-body">
 322                <p class="text-muted">Введите название для сохранения (необязательно):</p>
 323                <input type="text" id="saveMappingName" class="form-control" placeholder="Например: Мой поставщик">
 324            </div>
 325            <div class="modal-footer">
 326                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
 327                <button type="button" class="btn btn-outline-primary" onclick="submitFormWithoutSave()">Продолжить без с
 328                <button type="button" class="btn btn-primary" onclick="submitFormWithSave()">Сохранить и загрузить</butt
 329            </div>
 330        </div>
 331    </div>
 332</div>
 333
 334<div class="container">
 335    <h2 class="mb-1">Выбор сопоставлений столбцов</h2>
 336    <p class="text-muted mb-3">Укажите, какие столбцы файла соответствуют полям системы, затем нажмите «Загрузить заказы
 337
 0338    @if (TempData["MappingWarning"] != null)
 339    {
 0340        var warnings = TempData["MappingWarning"]!.ToString()!.Split(';');
 341        <div class="alert alert-warning alert-dismissible fade show" role="alert">
 342            <div class="d-flex justify-content-between align-items-start">
 343                <div class="flex-grow-1">
 344                    <strong>Внимание!</strong> Некоторые сохраненные сопоставления не могут быть использованы с текущим 
 345                    <button type="button" class="btn btn-link btn-sm p-0 ms-2" onclick="toggleWarningDetails()" id="togg
 346                        Подробно
 347                    </button>
 348                    <div id="warningDetails" class="mt-2" style="display: none;">
 349                        <ul class="mb-0">
 0350                            @foreach (var warning in warnings)
 351                            {
 0352                                <li>@warning</li>
 353                            }
 354                        </ul>
 355                    </div>
 356                </div>
 357                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 358            </div>
 359        </div>
 360        <script>
 361            function toggleWarningDetails() {
 362                var details = document.getElementById('warningDetails');
 363                var btn = document.getElementById('toggleDetailsBtn');
 364                if (details.style.display === 'none') {
 365                    details.style.display = 'block';
 366                    btn.textContent = 'Скрыть';
 367                } else {
 368                    details.style.display = 'none';
 369                    btn.textContent = 'Подробно';
 370                }
 371            }
 372        </script>
 373    }
 374
 375    <div class="form-group mb-3">
 376        <label for="savedMappings" class="form-label fw-semibold">Сохранённое сопоставление</label>
 377        <select id="savedMappings" class="form-select"
 378                onchange="loadSavedMapping(this.value); updateSavedMappingSummary(this.value); ensureSelectedVisible(thi
 379            <option value="">— выберите сохранённое сопоставление —</option>
 380            <option value="none">Без сопоставления</option>
 0381            @foreach (var mapping in Model.SavedMappings)
 382            {
 0383                var mappingPairs = mapping.ColumnMappings != null
 0384                    ? string.Join(", ", mapping.ColumnMappings
 0385                        .Where(kv => !string.IsNullOrWhiteSpace(kv.Value))
 0386                        .Select(kv => $"{kv.Key} → {kv.Value}"))
 0387                    : "";
 0388                <option value="@mapping.Id" title="@mappingPairs">@mapping.MappingName</option>
 389            }
 390        </select>
 391        <div class="form-text text-muted mt-1" id="savedMappingSummary"></div>
 392    </div>
 393
 394    <form asp-area="Studio2" asp-controller="Orders" asp-action="CheckFile" method="post"
 395          onsubmit="handleFormSubmit(event)" id="saveForm">
 396
 397        <!-- Таблица сопоставления -->
 398        <div class="card form-section-card mb-4">
 399            <div class="card-header">
 400                <h5 class="mb-0">Сопоставление столбцов</h5>
 401            </div>
 402            <div class="card-body p-0">
 403                <div class="table-responsive mapping-table-container">
 404                    <table class="table table-striped table-bordered mb-0">
 405                        <thead>
 406                        <tr>
 407                            <th style="width: 40%;">Поле системы</th>
 408                            <th>Столбец файла / Фиксированное значение</th>
 409                        </tr>
 410                        </thead>
 411                        <tbody>
 0412                        @for (int i = 0; i < Model.MainTableHeaders.Count; i++)
 413                        {
 0414                            var mainHeader = Model.MainTableHeaders[i];
 0415                            var inputId = "input-" + mainHeader.Replace(" ", "-");
 416
 417                            // Строку "Код валюты отправления" скрываем — она выбирается в модалке
 0418                            if (mainHeader == "Код валюты отправления")
 419                            {
 420                                continue;
 421                            }
 422
 423                            <tr id="row-@i">
 0424                                <td class="main-header">@mainHeader</td>
 425                                <td>
 0426                                    @if (mainHeader == "Клиент")
 427                                    {
 428                                        <div class="d-flex align-items-center gap-2">
 429                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 430                                                    id="input-Клиент-Таблица"
 0431                                                    data-row-id="@i"
 0432                                                    data-main-header="@mainHeader"
 433                                                    onchange="updateTableHeader(this)">
 434                                                <option value="">Выберите столбец</option>
 0435                                                @foreach (var tempHeader in Model.TableHeaders)
 436                                                {
 0437                                                    <option value="@tempHeader">@tempHeader</option>
 438                                                }
 439                                            </select>
 440                                            <select name="SelectedClient.Id" class="form-select"
 441                                                    id="input-Клиент"
 0442                                                    data-row-id="@i"
 0443                                                    data-main-header="@mainHeader"
 444                                                    onchange="updateClient(this)">
 445                                                <option value="">Выберите клиента</option>
 0446                                                @foreach (var client in Model.OzonClients)
 447                                                {
 0448                                                    <option value="@client.Id">@client.Name</option>
 449                                                }
 450                                            </select>
 451                                        </div>
 452                                    }
 0453                                    else if (mainHeader == "Принят в обработку")
 454                                    {
 455                                        <div class="d-flex align-items-center gap-2">
 456                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 457                                                    id="@inputId"
 0458                                                    data-row-id="@i"
 0459                                                    data-main-header="@mainHeader"
 460                                                    onchange="updateTableHeader(this)">
 461                                                <option value="">Выберите столбец</option>
 0462                                                @foreach (var tempHeader in Model.TableHeaders)
 463                                                {
 0464                                                    <option value="@tempHeader">@tempHeader</option>
 465                                                }
 466                                            </select>
 467                                            <input asp-for="@Model.ColumnMappings" name="SelectedProcessingDate"
 468                                                   class="form-control" type="date"
 469                                                   id="input-Принят-в-обработку-Календарь"/>
 470                                        </div>
 471                                    }
 0472                                    else if (mainHeader == "Дата отгрузки")
 473                                    {
 474                                        <div class="d-flex align-items-center gap-2">
 475                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 476                                                    id="@inputId"
 0477                                                    data-row-id="@i"
 0478                                                    data-main-header="@mainHeader"
 479                                                    onchange="updateTableHeader(this)">
 480                                                <option value="">Выберите столбец</option>
 0481                                                @foreach (var tempHeader in Model.TableHeaders)
 482                                                {
 0483                                                    <option value="@tempHeader">@tempHeader</option>
 484                                                }
 485                                            </select>
 486                                            <input asp-for="@Model.ColumnMappings" name="SelectedShippingDate"
 487                                                   class="form-control" type="date"
 488                                                   id="input-Дата-отгрузки-Календарь"/>
 489                                        </div>
 490                                    }
 0491                                    else if (mainHeader == "Статус клинта")
 492                                    {
 493                                        <div class="d-flex align-items-center gap-2">
 494                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 495                                                    id="input-Статус-Клиента-Таблица"
 0496                                                    data-row-id="@i"
 0497                                                    data-main-header="@mainHeader"
 498                                                    onchange="updateTableHeader(this)">
 499                                                <option value="">Выберите столбец</option>
 0500                                                @foreach (var tempHeader in Model.TableHeaders)
 501                                                {
 0502                                                    <option value="@tempHeader">@tempHeader</option>
 503                                                }
 504                                            </select>
 505                                            <select name="SelectedStatus" class="form-select"
 506                                                    id="input-Статус-Клиента"
 0507                                                    data-row-id="@i"
 0508                                                    data-main-header="@mainHeader"
 509                                                    onchange="updateClient(this)">
 510                                                <option value="">Выберите статус</option>
 0511                                                @foreach (var status in Model.Statuses)
 512                                                {
 0513                                                    <option value="@status.Value">@status.Text</option>
 514                                                }
 515                                            </select>
 516                                        </div>
 517                                    }
 0518                                    else if (mainHeader == "Производитель")
 519                                    {
 520                                        <div class="d-flex align-items-center gap-2">
 521                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 522                                                    id="input-Производитель-Таблица"
 0523                                                    data-row-id="@i"
 0524                                                    data-main-header="@mainHeader"
 525                                                    onchange="updateTableHeader(this)">
 526                                                <option value="">Выберите столбец</option>
 0527                                                @foreach (var tempHeader in Model.TableHeaders)
 528                                                {
 0529                                                    <option value="@tempHeader">@tempHeader</option>
 530                                                }
 531                                            </select>
 532                                            <select name="SelectedManufacturer.Id" class="form-select"
 533                                                    id="input-Производитель"
 0534                                                    data-row-id="@i"
 0535                                                    data-main-header="@mainHeader"
 536                                                    onchange="updateClient(this)">
 537                                                <option value="">Выберите производителя</option>
 538                                                <option value="-1">На основе трёх последних символов артикула</option>
 539                                                <option value="-2">Из названия</option>
 0540                                                @foreach (var manufacturer in Model.Manufacturers)
 541                                                {
 0542                                                    <option value="@manufacturer.Id">@manufacturer.Name</option>
 543                                                }
 544                                            </select>
 545                                        </div>
 546                                    }
 0547                                    else if (mainHeader == "Склад отгрузки")
 548                                    {
 549                                        <div class="d-flex align-items-center gap-2">
 550                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 551                                                    id="input-Склад-Oтгрузки-Таблица"
 0552                                                    data-row-id="@i"
 0553                                                    data-main-header="@mainHeader"
 554                                                    onchange="updateTableHeader(this)">
 555                                                <option value="">Выберите столбец</option>
 0556                                                @foreach (var tempHeader in Model.TableHeaders)
 557                                                {
 0558                                                    <option value="@tempHeader">@tempHeader</option>
 559                                                }
 560                                            </select>
 561                                            <select name="SelectedWarehouse.Id" class="form-select"
 562                                                    id="input-Склад-Oтгрузки"
 0563                                                    data-row-id="@i"
 0564                                                    data-main-header="@mainHeader"
 565                                                    onchange="updateClient(this)">
 566                                                <option value="">Выберите склад отгрузки</option>
 0567                                                @foreach (var warehouse in Model.Warehouses)
 568                                                {
 0569                                                    <option value="@warehouse.Id">@warehouse.Name</option>
 570                                                }
 571                                            </select>
 572                                        </div>
 573                                    }
 0574                                    else if (mainHeader == "Поставщик")
 575                                    {
 576                                        <div class="d-flex align-items-center gap-2">
 577                                            <select class="form-select" style="visibility: hidden;"></select>
 578                                            <select name="SelectedSupplier.Id" class="form-select"
 579                                                    id="input-Поставщик"
 0580                                                    data-row-id="@i"
 0581                                                    data-main-header="@mainHeader"
 582                                                    onchange="updateClient(this)">
 583                                                <option value="">Выберите поставщика</option>
 0584                                                @foreach (var supplier in Model.Suppliers)
 585                                                {
 0586                                                    <option value="@supplier.Id">@supplier.Name</option>
 587                                                }
 588                                            </select>
 589                                        </div>
 590                                    }
 591                                    else
 592                                    {
 593                                        <div class="d-flex align-items-center">
 594                                            <select name="ColumnMappings[@mainHeader]" class="form-select"
 595                                                    id="@inputId"
 0596                                                    data-row-id="@i"
 0597                                                    data-main-header="@mainHeader"
 598                                                    onchange="updateTableHeader(this)">
 599                                                <option value="">Выберите столбец</option>
 0600                                                @foreach (var tempHeader in Model.TableHeaders)
 601                                                {
 0602                                                    <option value="@tempHeader">@tempHeader</option>
 603                                                }
 604                                            </select>
 605                                        </div>
 606                                    }
 607                                </td>
 608                            </tr>
 0609                        }
 610                        </tbody>
 611                    </table>
 612                </div>
 613            </div>
 614        </div>
 615
 616        <!-- Предпросмотр данных файла -->
 617        <div class="card form-section-card mb-4">
 618            <div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
 619                <h5 class="mb-0">Предпросмотр данных файла</h5>
 620                <div class="d-flex align-items-center gap-3">
 0621                    <span class="badge bg-secondary">@Model.TableData.Count строк</span>
 0622                    <small class="text-muted">@Model.FileName</small>
 623                </div>
 624            </div>
 625            <div class="card-body p-0">
 626                <div class="table-responsive preview-table-container">
 627                    <table class="table table-striped table-bordered table-sm mb-0">
 628                        <thead>
 629                        <tr>
 0630                            @foreach (var header in Model.TableHeaders)
 631                            {
 632                                <th id="header-@header.Replace(" ", "-")">
 0633                                    @header
 634                                    <span id="mapping-@header.Replace(" ", "-")" class="badge bg-success ms-1" style="fo
 635                                </th>
 636                            }
 637                        </tr>
 638                        </thead>
 639                        <tbody>
 0640                        @for (int i = 0; i < Math.Min(5, Model.TableData.Count); i++)
 641                        {
 642                            <tr>
 0643                                @foreach (var header in Model.TableHeaders)
 644                                {
 0645                                    <td class="table-cell" data-header="@header">
 0646                                        @(Model.TableData[i].ContainsKey(header) ? Model.TableData[i][header] : "")
 647                                    </td>
 648                                }
 649                            </tr>
 650                        }
 0651                        @if (Model.TableData.Count > 5)
 652                        {
 653                            <tr>
 654                                <td colspan="@Model.TableHeaders.Count" class="text-center text-muted py-2">
 0655                                    <i class="bi bi-three-dots me-1"></i>ещё @(Model.TableData.Count - 5) строк не показ
 656                                </td>
 657                            </tr>
 658                        }
 659                        </tbody>
 660                    </table>
 661                </div>
 662            </div>
 663        </div>
 664
 665        <!-- Скрытые поля -->
 666        <input type="hidden" id="saveName" name="saveName" value="">
 667        <input type="hidden" id="FilePath" name="FilePath" value="@Model.FilePath">
 668        <input type="hidden" id="FileName" name="FileName" value="@Model.FileName">
 669        <input type="hidden" id="isGroupHidden" name="isGroup" value="false">
 670        <input type="hidden" id="hiddenCurrencyCode" name="SelectedCurrencyCode" value="RUB">
 671
 672        <!-- Параметры загрузки -->
 673        <div class="card form-section-card mt-4">
 674            <div class="card-header">
 675                <h5 class="mb-0">Параметры загрузки</h5>
 676            </div>
 677            <div class="card-body">
 678                <div class="row g-3 align-items-stretch">
 679                    <div class="col-md-6">
 680                        <!-- for= убран намеренно: checkbox уже внутри label, for= вызывал двойной toggle -->
 681                        <label class="d-flex align-items-start gap-3 p-3 border rounded option-card h-100 w-100 mb-0"
 682                               id="notFullCard">
 683                            <input class="form-check-input mt-1" type="checkbox" id="notFullCheckbox"
 684                                   name="notFull" value="false"
 685                                   onclick="this.value = this.checked ? 'true' : 'false'; toggleOptionCard('notFullCard'
 686                            <div>
 687                                <div class="fw-semibold"><i class="bi bi-check2-circle me-1 text-primary"></i>Проверить 
 688                                <small class="text-muted d-block mt-1">Сравнит загружаемые позиции с уже имеющимися в си
 689                            </div>
 690                        </label>
 691                    </div>
 692                    <div class="col-md-6">
 693                        <label class="d-flex align-items-start gap-3 p-3 border rounded option-card h-100 w-100 mb-0"
 694                               id="groupCard">
 695                            <input class="form-check-input mt-1" type="checkbox" id="groupCheckbox"
 696                                   value="true" onchange="handleGroupCheckboxChange(this); toggleOptionCard('groupCard',
 697                            <div>
 698                                <div class="fw-semibold">
 699                                    <i class="bi bi-collection me-1 text-primary"></i>Сгруппировать
 700                                    <span class="file-loading-indicator" id="fileLoadingIndicator">
 701                                        <span class="file-loading-spinner"></span>
 702                                        <span class="file-loading-text">Обработка...</span>
 703                                    </span>
 704                                </div>
 705                                <small class="text-muted d-block mt-1">Объединит одинаковые позиции и скачает сгруппиров
 706                            </div>
 707                        </label>
 708                    </div>
 709                </div>
 710                <div id="filters-warning" class="alert alert-warning mt-3 mb-0 d-none py-2" role="alert">
 711                    <i class="bi bi-exclamation-triangle me-1"></i>
 712                    На главной странице применены фильтры — проверка покажет данные с учётом этих фильтров.
 713                </div>
 714                <div class="mt-3 d-flex flex-wrap gap-2 align-items-center">
 715                    <button type="submit" id="submitButton" class="btn btn-primary btn-lg px-4">
 716                        <i class="bi bi-upload me-2"></i>Загрузить заказы
 717                    </button>
 718                    <button type="button" class="btn btn-outline-secondary" id="appendBtn">
 719                        <i class="bi bi-file-earmark-plus me-1"></i>Дополнить файл
 720                    </button>
 721                </div>
 722            </div>
 723        </div>
 724    </form>
 725</div>
 726
 727<!-- Модальное окно для выбора столбцов (Дополнить файл) -->
 728<div class="modal fade" id="columnsModal" tabindex="-1" aria-hidden="true">
 729    <div class="modal-dialog modal-lg">
 730        <div class="modal-content">
 731            <div class="modal-header">
 732                <h5 class="modal-title">Выберите столбцы для дополнения</h5>
 733                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 734            </div>
 735            <div class="modal-body">
 736                <div class="row">
 737                    <div class="col-md-6">
 738                        <h6>Сравнить с (ключевые столбцы):</h6>
 739                        <div class="list-group scrollable-list" id="compareColumns"
 740                             style="max-height: 400px; overflow-y: auto;">
 0741                            @for (int i = 0; i < Model.MainTableHeaders.Count; i++)
 742                            {
 0743                                var mainHeader = Model.MainTableHeaders[i];
 744                                <label class="list-group-item">
 745                                    <input class="form-check-input me-1" type="checkbox" name="compareColumns"
 746                                           value="@mainHeader">
 0747                                    @mainHeader
 748                                </label>
 749                            }
 750                        </div>
 751                    </div>
 752                    <div class="col-md-6">
 753                        <h6>Добавить (новые столбцы):</h6>
 754                        <div class="list-group scrollable-list" id="addColumns"
 755                             style="max-height: 400px; overflow-y: auto;">
 0756                            @for (int i = 0; i < Model.MainTableHeaders.Count; i++)
 757                            {
 0758                                var mainHeader = Model.MainTableHeaders[i];
 759                                <label class="list-group-item">
 760                                    <input class="form-check-input me-1" type="checkbox" name="addColumns"
 761                                           value="@mainHeader">
 0762                                    @mainHeader
 763                                </label>
 764                            }
 765                        </div>
 766                    </div>
 767                </div>
 768            </div>
 769            <div class="modal-footer">
 770                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
 771                <button type="button" class="btn btn-primary" id="confirmAppend">Подтвердить</button>
 772            </div>
 773        </div>
 774    </div>
 775</div>
 776
 777<script>
 778    // ===== Данные из сервера =====
 0779    var currencyCodeMap = @Html.Raw(currencyCodeMapJson);
 0780    var previewData = @Html.Raw(previewDataJson);
 0781    var cbRates = @Html.Raw(cbRatesJson);
 782
 783    // ===== Валюта: модалка =====
 784
 785    function onCurrencyChange() {
 786        var currency = document.getElementById('currencySelect').value;
 787        var rateBlock = document.getElementById('rateBlock');
 788        var rateInput = document.getElementById('currencyRateInput');
 789        var badgeEl = document.getElementById('rateInfoBadge');
 790
 791        if (currency && currency !== 'RUB') {
 792            rateBlock.style.display = '';
 793            var cbRate = cbRates[currency];
 794            if (cbRate && cbRate > 0) {
 795                rateInput.value = cbRate.toFixed(4);
 796                badgeEl.innerHTML = '<i class="bi bi-bank me-1"></i>Курс ЦБ РФ на сегодня: <strong>1 ' + currency + ' = 
 797                badgeEl.style.display = '';
 798            } else {
 799                rateInput.value = '';
 800                badgeEl.innerHTML = '<i class="bi bi-exclamation-circle me-1 text-warning"></i>Курс ЦБ не получен — введ
 801                badgeEl.style.display = '';
 802            }
 803        } else {
 804            rateBlock.style.display = 'none';
 805            if (badgeEl) badgeEl.style.display = 'none';
 806        }
 807        updateCurrencyPreview();
 808    }
 809
 810    function updateCurrencyPreview() {
 811        var currency = document.getElementById('currencySelect').value;
 812        var rateInput = document.getElementById('currencyRateInput');
 813        var rate = (currency === 'RUB') ? 1 : (parseFloat(rateInput.value) || 0);
 814
 815        var head = document.getElementById('currencyPreviewHead');
 816        var body = document.getElementById('currencyPreviewBody');
 817        var emptyMsg = document.getElementById('currencyPreviewEmpty');
 818
 819        if (previewData.length === 0) {
 820            emptyMsg.style.display = '';
 821            head.innerHTML = '';
 822            body.innerHTML = '';
 823            return;
 824        }
 825
 826        // Собираем все замапленные колонки (системное поле → столбец файла)
 827        var mappedCols = []; // [{label, fileCol, isPrice}]
 828        var priorityIds = [
 829            { id: 'input-Артикул', label: 'Артикул' },
 830            { id: 'input-Key', label: 'Key' },
 831            { id: 'input-Наименование-товара', label: 'Наименование' },
 832            { id: 'input-Цена-закупки', label: 'Цена закупки', isPrice: true }, // приоритет для пересчёта
 833            { id: 'input-Цена', label: 'Цена', isPrice: true },
 834            { id: 'input-Кол.', label: 'Кол.' },
 835            { id: 'input-Номер-заказа', label: '№ заказа' },
 836        ];
 837
 838        // Сначала приоритетные поля
 839        priorityIds.forEach(function (f) {
 840            var el = document.getElementById(f.id);
 841            if (el && el.value) mappedCols.push({ label: f.label, fileCol: el.value, isPrice: !!f.isPrice });
 842        });
 843
 844        // Затем остальные замапленные (из всех select с data-main-header и value)
 845        document.querySelectorAll('select[data-main-header]').forEach(function (sel) {
 846            if (!sel.value) return;
 847            var mh = sel.getAttribute('data-main-header');
 848            var alreadyAdded = mappedCols.some(function (c) { return c.fileCol === sel.value; });
 849            if (!alreadyAdded) mappedCols.push({ label: mh, fileCol: sel.value, isPrice: false });
 850        });
 851
 852        // Ограничиваем 6 колонками для читаемости
 853        mappedCols = mappedCols.slice(0, 6);
 854
 855        var priceCol = mappedCols.find(function (c) { return c.isPrice; });
 856        var showConversion = currency !== 'RUB' && rate > 0;
 857
 858        if (mappedCols.length === 0) {
 859            emptyMsg.innerHTML = '<i class="bi bi-info-circle me-1"></i>Сопоставьте столбцы в таблице выше — здесь появи
 860            emptyMsg.style.display = '';
 861            head.innerHTML = '';
 862            body.innerHTML = '';
 863            return;
 864        }
 865        emptyMsg.style.display = 'none';
 866
 867        // Заголовок
 868        var headCols = mappedCols.map(function (c) {
 869            var label = c.isPrice ? c.label + ' (' + currency + ')' : c.label;
 870            return '<th>' + label + '</th>';
 871        });
 872        if (showConversion) {
 873            var convLabel = priceCol ? '→ RUB' : '→ RUB <span class="text-warning" title="Сопоставьте столбец «Цена заку
 874            headCols.push('<th class="price-converted">' + convLabel + '</th>');
 875        }
 876        head.innerHTML = '<tr>' + headCols.join('') + '</tr>';
 877
 878        // Строки
 879        var rows = '';
 880        for (var i = 0; i < previewData.length; i++) {
 881            var row = previewData[i];
 882            var cells = mappedCols.map(function (c) {
 883                var val = row[c.fileCol] || '';
 884                if (c.label === 'Наименование' && val.length > 35) val = val.substring(0, 35) + '…';
 885                return '<td>' + val + '</td>';
 886            });
 887            if (showConversion) {
 888                if (!priceCol) {
 889                    cells.push('<td class="text-muted fst-italic">не определена</td>');
 890                } else {
 891                    var rawPrice = row[priceCol.fileCol] || '';
 892                    var numPrice = parseFloat(rawPrice.replace(',', '.').replace(/\s/g, ''));
 893                    if (!isNaN(numPrice) && numPrice > 0) {
 894                        cells.push('<td class="price-converted">' + (numPrice * rate).toFixed(2) + ' ₽</td>');
 895                    } else {
 896                        cells.push('<td class="text-muted">—</td>');
 897                    }
 898                }
 899            }
 900            rows += '<tr>' + cells.join('') + '</tr>';
 901        }
 902        body.innerHTML = rows;
 903    }
 904
 905    function confirmCurrencyAndProceed() {
 906        var currency = document.getElementById('currencySelect').value;
 907        document.getElementById('hiddenCurrencyCode').value = currency || 'RUB';
 908
 909        var currencyModalEl = document.getElementById('currencyModal');
 910        var currencyModal = bootstrap.Modal.getInstance(currencyModalEl);
 911        if (currencyModal) currencyModal.hide();
 912
 913        var selectedMappingId = document.getElementById('savedMappings').value;
 914        var mappingSelected = selectedMappingId && selectedMappingId !== '' && selectedMappingId !== 'none';
 915
 916        currencyModalEl.addEventListener('hidden.bs.modal', function handler() {
 917            currencyModalEl.removeEventListener('hidden.bs.modal', handler);
 918            if (mappingSelected) {
 919                // Выбрано существующее сопоставление — сохранять нечего, сразу отправляем
 920                submitFormWithoutSave();
 921            } else {
 922                var saveModal = new bootstrap.Modal(document.getElementById('saveMappingModal'));
 923                saveModal.show();
 924            }
 925        });
 926    }
 927
 928    // Синхронизируем предпросмотр при открытии модалки валюты
 929    document.getElementById('currencyModal').addEventListener('show.bs.modal', function () {
 930        onCurrencyChange();
 931    });
 932
 933    // ===== Опция-карточка: подсветка при выборе =====
 934    function toggleOptionCard(cardId, checked) {
 935        var card = document.getElementById(cardId);
 936        if (card) {
 937            card.classList.toggle('active', checked);
 938        }
 939    }
 940
 941    // ===== Дополнить файл =====
 942    document.getElementById('appendBtn').addEventListener('click', function () {
 943        new bootstrap.Modal(document.getElementById('columnsModal')).show();
 944    });
 945
 946    document.getElementById('confirmAppend').addEventListener('click', function () {
 947        const compareColumns = Array.from(document.querySelectorAll('input[name="compareColumns"]:checked')).map(el => e
 948        const addColumns = Array.from(document.querySelectorAll('input[name="addColumns"]:checked')).map(el => el.value)
 949
 950        if (compareColumns.length === 0 || addColumns.length === 0) {
 951            alert('Пожалуйста, выберите хотя бы один столбец в каждом списке');
 952            return;
 953        }
 954
 955        const intersection = compareColumns.filter(value => addColumns.includes(value));
 956        if (intersection.length > 0) {
 957            alert('Следующие столбцы выбраны в обоих списках: ' + intersection.join(', ') + '\nПожалуйста, выберите стол
 958            return;
 959        }
 960
 961        const form = document.getElementById('saveForm');
 962        document.querySelectorAll('input[name="compareColumnsList"]').forEach(el => el.remove());
 963        document.querySelectorAll('input[name="addColumnsList"]').forEach(el => el.remove());
 964
 965        compareColumns.forEach(col => {
 966            const input = document.createElement('input');
 967            input.type = 'hidden';
 968            input.name = 'compareColumnsList';
 969            input.value = col;
 970            form.appendChild(input);
 971        });
 972
 973        addColumns.forEach(col => {
 974            const input = document.createElement('input');
 975            input.type = 'hidden';
 976            input.name = 'addColumnsList';
 977            input.value = col;
 978            form.appendChild(input);
 979        });
 980
 0981        form.action = '@Url.Action("AppendFile", "Orders", new { area = "Studio2" })';
 982        const hiddenInput = document.createElement('input');
 983        hiddenInput.type = 'hidden';
 984        hiddenInput.name = 'actionType';
 985        hiddenInput.value = 'append';
 986        form.appendChild(hiddenInput);
 987
 988        bootstrap.Modal.getInstance(document.getElementById('columnsModal')).hide();
 989        showLoadingOverlay();
 990        form.submit();
 991    });
 992
 993    // ===== Группировка =====
 994    function setActionButtonsDisabled(disabled) {
 995        ['submitButton', 'appendBtn'].forEach(id => {
 996            const btn = document.getElementById(id);
 997            if (btn) btn.disabled = disabled;
 998        });
 999    }
 1000
 1001    function showFileLoadingIndicator() {
 1002        document.getElementById('fileLoadingIndicator')?.classList.add('show');
 1003        setActionButtonsDisabled(true);
 1004    }
 1005
 1006    function hideFileLoadingIndicator() {
 1007        document.getElementById('fileLoadingIndicator')?.classList.remove('show');
 1008        setActionButtonsDisabled(false);
 1009    }
 1010
 1011    function downloadFile(blob, filename) {
 1012        const url = window.URL.createObjectURL(blob);
 1013        const a = document.createElement('a');
 1014        a.style.display = 'none';
 1015        a.href = url;
 1016        a.download = filename;
 1017        a.dataset.noLoading = '';  // запрещаем PageLoadingBar перехватывать этот клик
 1018        document.body.appendChild(a);
 1019        a.click();
 1020        window.URL.revokeObjectURL(url);
 1021        document.body.removeChild(a);
 1022    }
 1023
 1024    function handleGroupCheckboxChange(checkbox) {
 1025        const hiddenField = document.getElementById('isGroupHidden');
 1026        if (hiddenField) hiddenField.value = checkbox.checked ? 'true' : 'false';
 1027        if (checkbox.checked) submitToAlternativeMethod(checkbox);
 1028    }
 1029
 1030    async function submitToAlternativeMethod(checkbox) {
 1031        showFileLoadingIndicator();
 1032        checkbox.disabled = true;
 1033        try {
 1034            let form = document.getElementById("saveForm");
 1035            const formData = new FormData(form);
 1036            const response = await fetch('/Studio2/Orders/GroupFileData', { method: 'POST', body: formData });
 1037            if (!response.ok) throw new Error('Ошибка при обработке файла');
 1038            const blob = await response.blob();
 1039            const contentDisposition = response.headers.get('Content-Disposition');
 1040            let filename = 'GroupOrders_' + new Date().toLocaleDateString('ru-RU').replace(/\./g, '_') + '.xlsx';
 1041            if (contentDisposition) {
 1042                const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
 1043                if (match && match[1]) {
 1044                    try { filename = decodeURIComponent(match[1].replace(/['"]/g, '')); } catch (e) {}
 1045                }
 1046            }
 1047            downloadFile(blob, filename);
 1048        } catch (error) {
 1049            console.error('Ошибка при обработке файла:', error);
 1050            alert('Произошла ошибка при обработке файла. Пожалуйста, попробуйте ещё раз.');
 1051            checkbox.checked = false;
 1052            checkbox.value = 'false';
 1053            toggleOptionCard('groupCard', false);
 1054        } finally {
 1055            hideFileLoadingIndicator();
 1056            checkbox.disabled = false;
 1057        }
 1058    }
 1059
 1060    // ===== Логика блокировки взаимозависимых полей =====
 1061    document.addEventListener('DOMContentLoaded', function () {
 1062        const statusSelect = document.getElementById('input-Статус-Клиента');
 1063        const statusColumnSelect = document.getElementById('input-Статус-Клиента-Таблица');
 1064        const clientSelect = document.getElementById('input-Клиент');
 1065        const clientColumnSelect = document.getElementById('input-Клиент-Таблица');
 1066        const manufacturerSelect = document.getElementById('input-Производитель');
 1067        const manufacturerColumnSelect = document.getElementById('input-Производитель-Таблица');
 1068        const warehouseSelect = document.getElementById('input-Склад-Oтгрузки');
 1069        const warehouseColumnSelect = document.getElementById('input-Склад-Oтгрузки-Таблица');
 1070        const selectedProcessingDate = document.getElementById('input-Принят-в-обработку');
 1071        const selectedColumnProcessingDate = document.getElementById('input-Принят-в-обработку-Календарь');
 1072        const selectedShippingDate = document.getElementById('input-Дата-отгрузки');
 1073        const selectedColumnShippingDate = document.getElementById('input-Дата-отгрузки-Календарь');
 1074        const keySelect = document.getElementById('input-Key');
 1075        const articleSelect = document.getElementById('input-Артикул');
 1076
 1077        function handleSelectChange(primarySelect, dependentSelect, additionalFields = []) {
 1078            primarySelect.addEventListener('change', function () {
 1079                const isSelected = primarySelect.value !== "";
 1080                dependentSelect.disabled = isSelected;
 1081                if (primarySelect === keySelect) {
 1082                    additionalFields.forEach(field => { field.disabled = isSelected; });
 1083                    if (isSelected && manufacturerSelect) manufacturerSelect.value = "-1";
 1084                    else if (!isSelected) additionalFields.forEach(f => { f.disabled = false; });
 1085                }
 1086            });
 1087            dependentSelect.addEventListener('change', function () {
 1088                primarySelect.disabled = dependentSelect.value !== "";
 1089            });
 1090        }
 1091
 1092        if (statusSelect && statusColumnSelect) handleSelectChange(statusSelect, statusColumnSelect);
 1093        if (clientSelect && clientColumnSelect) handleSelectChange(clientSelect, clientColumnSelect);
 1094        if (manufacturerSelect && manufacturerColumnSelect) handleSelectChange(manufacturerSelect, manufacturerColumnSel
 1095        if (warehouseSelect && warehouseColumnSelect) handleSelectChange(warehouseSelect, warehouseColumnSelect);
 1096        if (selectedProcessingDate && selectedColumnProcessingDate) handleSelectChange(selectedProcessingDate, selectedC
 1097        if (selectedShippingDate && selectedColumnShippingDate) handleSelectChange(selectedShippingDate, selectedColumnS
 1098        if (keySelect && articleSelect) handleSelectChange(keySelect, articleSelect, [manufacturerSelect, manufacturerCo
 1099
 1100        // Предупреждение о фильтрах
 1101        const filtersWarning = document.getElementById('filters-warning');
 1102        const notFullCheckbox = document.getElementById('notFullCheckbox');
 01103        const hasFilters = @JsonConvert.SerializeObject(ViewBag.HasActiveFilters ?? false);
 1104        if (filtersWarning && notFullCheckbox) {
 1105            const toggleFiltersWarning = () => {
 1106                if (hasFilters && notFullCheckbox.checked) filtersWarning.classList.remove('d-none');
 1107                else filtersWarning.classList.add('d-none');
 1108            };
 1109            notFullCheckbox.addEventListener('change', toggleFiltersWarning);
 1110            toggleFiltersWarning();
 1111        }
 1112
 1113        // Инициализация data-selected-value и маппинг-бейджей
 1114        document.querySelectorAll('select').forEach(function (sel) {
 1115            var val = sel.value;
 1116            sel.setAttribute('data-selected-value', val);
 1117            if (val) {
 1118                var mh = sel.getAttribute('data-main-header');
 1119                updateHeaders(val, mh);
 1120            }
 1121        });
 1122
 1123        var savedMappingsSelect = document.getElementById('savedMappings');
 1124        var selectedMappingId = savedMappingsSelect ? savedMappingsSelect.value : '';
 1125        updateSavedMappingSummary(selectedMappingId);
 1126
 1127        if (selectedMappingId && selectedMappingId !== "none" && selectedMappingId !== "") {
 1128            setTimeout(() => toggleUnusedRows(true), 200);
 1129        }
 1130        if (savedMappingsSelect) ensureSelectedVisible(savedMappingsSelect);
 1131    });
 1132
 1133    // ===== Сохранённые сопоставления =====
 1134    function loadSavedMapping(mappingId) {
 1135        if (mappingId === "none" || mappingId === "") {
 1136            resetAllSelections();
 1137            toggleUnusedRows(false);
 1138            return;
 1139        }
 01140        var mappings = @Html.Raw(JsonConvert.SerializeObject(Model.SavedMappings));
 1141        var selectedMapping = mappings.find(m => m.Id == mappingId);
 1142        resetAllSelections();
 1143        if (selectedMapping) {
 1144            handleMapping(selectedMapping);
 1145            setTimeout(() => toggleUnusedRows(true), 100);
 1146        } else {
 1147            toggleUnusedRows(false);
 1148        }
 1149    }
 1150
 1151    function ensureSelectedVisible(selectElement) {
 1152        if (selectElement && selectElement.selectedIndex >= 0) {
 1153            selectElement.selectedOptions[0].scrollIntoView({ block: 'nearest', behavior: 'smooth' });
 1154        }
 1155    }
 1156
 1157    function toggleUnusedRows(hideUnused) {
 1158        var allRows = document.querySelectorAll('#saveForm table tbody tr[id^="row-"]');
 1159        allRows.forEach(function (row) {
 1160            var hasMapping = false;
 1161            row.querySelectorAll('select:not([style*="visibility: hidden"])').forEach(function (sel) {
 1162                if (sel.value && sel.value !== "" && sel.value !== "0") hasMapping = true;
 1163            });
 1164            if (row.classList.contains('table-success')) hasMapping = true;
 1165            row.querySelectorAll('input[type="date"]').forEach(function (inp) {
 1166                if (inp.value) hasMapping = true;
 1167            });
 1168            row.querySelectorAll('select[data-selected-value]').forEach(function (sel) {
 1169                if (sel.getAttribute('data-selected-value')) hasMapping = true;
 1170            });
 1171            row.style.display = hideUnused ? (hasMapping ? '' : 'none') : '';
 1172        });
 1173    }
 1174
 1175    function updateSavedMappingSummary(mappingId) {
 01176        var mappings = @Html.Raw(JsonConvert.SerializeObject(Model.SavedMappings));
 1177        var summaryElement = document.getElementById('savedMappingSummary');
 1178        if (!summaryElement) return;
 1179        if (mappingId === "none" || mappingId === "" || !mappingId) {
 1180            summaryElement.textContent = "";
 1181            toggleUnusedRows(false);
 1182            return;
 1183        }
 1184        var selected = mappings.find(m => m.Id == mappingId);
 1185        if (!selected || !selected.ColumnMappings) {
 1186            summaryElement.textContent = "";
 1187            toggleUnusedRows(false);
 1188            return;
 1189        }
 1190        const pairs = [];
 1191        for (const key in selected.ColumnMappings) {
 1192            if (selected.ColumnMappings[key]) pairs.push(key + " → " + selected.ColumnMappings[key]);
 1193        }
 1194        summaryElement.textContent = pairs.length ? "Соответствия: " + pairs.join(", ") : "";
 1195    }
 1196
 1197    function resetAllSelections() {
 1198        document.querySelectorAll('select').forEach(function (select) {
 1199            select.disabled = false;
 1200            var previousValue = select.getAttribute('data-selected-value');
 1201            if (previousValue) {
 1202                findElementsByDataHeader(previousValue).forEach(cell => cell.classList.remove('table-success'));
 1203                updateHeaders(previousValue, "");
 1204            }
 1205            select.value = "";
 1206            select.setAttribute('data-selected-value', "");
 1207        });
 1208        document.querySelectorAll('tr.table-success').forEach(row => row.classList.remove('table-success'));
 1209        toggleUnusedRows(false);
 1210    }
 1211
 1212    function handleMapping(selectedMapping) {
 1213        updateClientSelection(selectedMapping);
 1214        updateStatusSelection(selectedMapping);
 1215        updateManufacturerSelection(selectedMapping);
 1216        updateWarehouseSelection(selectedMapping);
 1217        updateCurrencySelectionFromMapping(selectedMapping);
 1218        updateColumnMappings(selectedMapping);
 1219        updateDateFields(selectedMapping);
 1220        updateSupplierSelection(selectedMapping);
 1221    }
 1222
 1223    function updateClientSelection(selectedMapping) {
 1224        var clientElement = document.getElementById("input-Клиент");
 1225        var clientTableElement = document.getElementById("input-Клиент-Таблица");
 1226        if (!clientElement) return;
 1227        if (selectedMapping.SelectedClientId) {
 1228            clientElement.value = selectedMapping.SelectedClientId;
 1229            clientTableElement.disabled = true;
 1230        } else if (selectedMapping.ColumnMappings["Клиент"]) {
 1231            clientTableElement.value = selectedMapping.ColumnMappings["Клиент"];
 1232            clientElement.disabled = true;
 1233        }
 1234        updateTableHeader(clientTableElement);
 1235    }
 1236
 1237    function updateSupplierSelection(selectedMapping) {
 1238        var supplierElement = document.getElementById("input-Поставщик");
 1239        if (supplierElement && selectedMapping.SelectedSupplier != null)
 1240            supplierElement.value = selectedMapping.SelectedSupplierId;
 1241    }
 1242
 1243    function updateStatusSelection(selectedMapping) {
 1244        var statusElement = document.getElementById("input-Статус-Клиента");
 1245        var statusTableElement = document.getElementById("input-Статус-Клиента-Таблица");
 1246        if (!statusElement) return;
 1247        if (selectedMapping.SelectedStatus) {
 1248            statusElement.value = selectedMapping.SelectedStatus;
 1249            statusTableElement.disabled = true;
 1250        } else if (selectedMapping.ColumnMappings["Статус клиента"]) {
 1251            statusTableElement.value = selectedMapping.ColumnMappings["Статус клиента"];
 1252            statusElement.disabled = true;
 1253        }
 1254        updateTableHeader(statusTableElement);
 1255    }
 1256
 1257    function updateManufacturerSelection(selectedMapping) {
 1258        var manufacturerElement = document.getElementById("input-Производитель");
 1259        var manufacturerTableElement = document.getElementById("input-Производитель-Таблица");
 1260        if (!manufacturerElement) return;
 1261        if (selectedMapping.ManufacturerFromArticle) {
 1262            manufacturerElement.value = "-1";
 1263            manufacturerTableElement.disabled = true;
 1264        } else if (selectedMapping.SelectedManufacturerId) {
 1265            manufacturerElement.value = selectedMapping.SelectedManufacturerId;
 1266            manufacturerTableElement.disabled = true;
 1267        } else if (selectedMapping.ColumnMappings["Производитель"]) {
 1268            manufacturerTableElement.value = selectedMapping.ColumnMappings["Производитель"];
 1269            manufacturerElement.disabled = true;
 1270        }
 1271        updateTableHeader(manufacturerTableElement);
 1272    }
 1273
 1274    function updateWarehouseSelection(selectedMapping) {
 1275        var warehouseElement = document.getElementById("input-Склад-Oтгрузки");
 1276        var warehouseTableElement = document.getElementById("input-Склад-Oтгрузки-Таблица");
 1277        if (!warehouseElement) return;
 1278        if (selectedMapping.SelectedWarehouseId) {
 1279            warehouseElement.value = selectedMapping.SelectedWarehouseId;
 1280            warehouseTableElement.disabled = true;
 1281        } else if (selectedMapping.ColumnMappings["Склад отгрузки"]) {
 1282            warehouseTableElement.value = selectedMapping.ColumnMappings["Склад отгрузки"];
 1283            warehouseElement.disabled = true;
 1284        }
 1285        updateTableHeader(warehouseTableElement);
 1286    }
 1287
 1288    function updateCurrencySelectionFromMapping(selectedMapping) {
 1289        if (selectedMapping.SelectedCurrencyCode) {
 1290            var currencyCode = currencyCodeMap[selectedMapping.SelectedCurrencyCode];
 1291            if (currencyCode) {
 1292                document.getElementById('hiddenCurrencyCode').value = currencyCode;
 1293                var currSel = document.getElementById('currencySelect');
 1294                if (currSel) currSel.value = currencyCode;
 1295            }
 1296        }
 1297    }
 1298
 1299    function normalizeHeaderName(headerName) {
 1300        if (!headerName) return '';
 1301        return headerName
 1302            .replace(/\r\n/g, ' ').replace(/\n/g, ' ').replace(/\r/g, ' ')
 1303            .replace(/\t/g, ' ').replace(/ /g, ' ').replace(/ /g, ' ')
 1304            .replace(/ /g, ' ').replace(/ /g, ' ').replace(/ /g, ' ')
 1305            .replace(/\s+/g, ' ').trim();
 1306    }
 1307
 1308    function findOptionByNormalizedValue(selectElement, targetValue) {
 1309        if (!targetValue) return null;
 1310        var normalizedTarget = normalizeHeaderName(targetValue);
 1311        var options = selectElement.options;
 1312        for (var i = 0; i < options.length; i++) {
 1313            if (options[i].value === targetValue) return options[i];
 1314        }
 1315        for (var i = 0; i < options.length; i++) {
 1316            if (normalizeHeaderName(options[i].value) === normalizedTarget) return options[i];
 1317        }
 1318        for (var i = 0; i < options.length; i++) {
 1319            if (normalizeHeaderName(options[i].text) === normalizedTarget) return options[i];
 1320        }
 1321        return null;
 1322    }
 1323
 1324    function updateColumnMappings(selectedMapping) {
 1325        for (var mainHeader in selectedMapping.ColumnMappings) {
 1326            var fileHeader = selectedMapping.ColumnMappings[mainHeader];
 1327            var selectElement = document.querySelector('select[data-main-header="' + mainHeader + '"]');
 1328            if (selectElement && fileHeader) {
 1329                var matchingOption = findOptionByNormalizedValue(selectElement, fileHeader);
 1330                if (matchingOption) {
 1331                    selectElement.value = matchingOption.value;
 1332                    selectElement.setAttribute('data-selected-value', matchingOption.value);
 1333                } else {
 1334                    selectElement.value = fileHeader;
 1335                    selectElement.setAttribute('data-selected-value', fileHeader);
 1336                }
 1337                updateTableHeader(selectElement);
 1338            }
 1339        }
 1340    }
 1341
 1342    function updateDateFields(selectedMapping) {
 1343        const selectedProcessingDate = document.getElementById('input-Принят-в-обработку');
 1344        const selectedColumnProcessingDate = document.getElementById('input-Принят-в-обработку-Календарь');
 1345        const selectedShippingDate = document.getElementById('input-Дата-отгрузки');
 1346        const selectedColumnShippingDate = document.getElementById('input-Дата-отгрузки-Календарь');
 1347        const keySelect = document.getElementById('input-Key');
 1348        const articleSelect = document.getElementById('input-Артикул');
 1349        const manufacturerSelect = document.getElementById('input-Производитель');
 1350        const manufacturerColumnSelect = document.getElementById('input-Производитель-Таблица');
 1351
 1352        if (selectedProcessingDate && selectedColumnProcessingDate && (selectedProcessingDate.value || selectedColumnPro
 1353            selectedProcessingDate.value ? selectedColumnProcessingDate.disabled = true : selectedProcessingDate.disable
 1354        }
 1355        if (selectedShippingDate && selectedColumnShippingDate && (selectedShippingDate.value || selectedColumnShippingD
 1356            selectedShippingDate.value ? selectedColumnShippingDate.disabled = true : selectedShippingDate.disabled = tr
 1357        }
 1358        if (keySelect && articleSelect) {
 1359            const isKeySelected = keySelect.value !== "";
 1360            const isArticleSelected = articleSelect.value !== "";
 1361            if (isKeySelected) {
 1362                articleSelect.disabled = true;
 1363                if (manufacturerSelect) manufacturerSelect.disabled = true;
 1364                if (manufacturerColumnSelect) manufacturerColumnSelect.disabled = true;
 1365            } else if (isArticleSelected) {
 1366                keySelect.disabled = true;
 1367                if (manufacturerSelect) manufacturerSelect.disabled = false;
 1368                if (manufacturerColumnSelect) manufacturerColumnSelect.disabled = false;
 1369            }
 1370        }
 1371    }
 1372
 1373    function findElementsByDataHeader(headerValue) {
 1374        if (!headerValue) return [];
 1375        var allCells = document.querySelectorAll('td[data-header]');
 1376        return Array.from(allCells).filter(cell => cell.getAttribute('data-header') === headerValue);
 1377    }
 1378
 1379    function updateTableHeader(selectElement) {
 1380        var rowId = selectElement.getAttribute('data-row-id');
 1381        var selectedValue = selectElement.value;
 1382        var rowElement = document.getElementById('row-' + rowId);
 1383        var mainHeader = selectElement.getAttribute('data-main-header');
 1384        var previousValue = selectElement.getAttribute('data-selected-value');
 1385
 1386        if (previousValue) {
 1387            findElementsByDataHeader(previousValue).forEach(cell => cell.classList.remove('table-success'));
 1388            updateHeaders(previousValue, "");
 1389        }
 1390
 1391        if (selectedValue !== "") {
 1392            findElementsByDataHeader(selectedValue).forEach(cell => cell.classList.add('table-success'));
 1393        }
 1394
 1395        selectElement.setAttribute('data-selected-value', selectedValue);
 1396
 1397        if (rowElement) {
 1398            rowElement.classList.toggle('table-success', selectedValue !== "");
 1399        }
 1400
 1401        updateHeaders(selectedValue, mainHeader);
 1402
 1403        var savedMappingsSelect = document.getElementById('savedMappings');
 1404        if (savedMappingsSelect && savedMappingsSelect.value) {
 1405            setTimeout(() => toggleUnusedRows(true), 50);
 1406        }
 1407    }
 1408
 1409    function updateHeaders(selectedValue, mainHeader) {
 1410        var spanElement = document.getElementById('mapping-' + selectedValue.replace(/\s+/g, "-"));
 1411        if (spanElement) {
 1412            if (mainHeader) {
 1413                spanElement.textContent = mainHeader;
 1414                spanElement.style.display = '';
 1415            } else {
 1416                spanElement.textContent = '';
 1417                spanElement.style.display = 'none';
 1418            }
 1419        }
 1420    }
 1421
 1422    // ===== Отправка формы =====
 1423    function handleFormSubmit(event) {
 1424        event.preventDefault();
 1425        validateForm();
 1426        return false;
 1427    }
 1428
 1429    function validateForm() {
 1430        var numberInput = document.getElementById("input-Номер-заказа");
 1431        var clientInput = document.getElementById("input-Клиент");
 1432        var columnInput = document.getElementById("input-Клиент-Таблица");
 1433        var orderNameInput = document.getElementById("input-Наименование-товара");
 1434        var articleInput = document.getElementById("input-Артикул");
 1435        var quntInput = document.getElementById("input-Кол.");
 1436        var priceInput = document.getElementById("input-Цена");
 1437        var keySelect = document.getElementById('input-Key');
 1438        var notFullCheckbox = document.getElementById('notFullCheckbox');
 1439
 1440        var missingFields = [];
 1441
 1442        if (notFullCheckbox.checked) {
 1443            if (!articleInput.value && !keySelect.value) missingFields.push("Артикул или Key");
 1444        } else {
 1445            if (!numberInput.value) missingFields.push("Номер заказа");
 1446            if (!clientInput.value && !columnInput.value) missingFields.push("Клиент");
 1447            if (!orderNameInput.value) missingFields.push("Наименование товара");
 1448            if (!articleInput.value && !keySelect.value) missingFields.push("Артикул или Key");
 1449            if (!priceInput.value) missingFields.push("Цена");
 1450            if (!quntInput.value) missingFields.push("Кол.");
 1451        }
 1452
 1453        if (missingFields.length > 0) {
 1454            alert("Не заполнены обязательные поля:\n" + missingFields.map(f => "• " + f).join("\n") + "\n\nПожалуйста, в
 1455            if (!numberInput.value) numberInput.focus();
 1456            else if (!clientInput.value && !columnInput.value) clientInput.focus();
 1457            else if (!orderNameInput.value) orderNameInput.focus();
 1458            else if (!articleInput.value) articleInput.focus();
 1459            else if (!priceInput.value) priceInput.focus();
 1460            return false;
 1461        }
 1462
 1463        // Открываем модалку выбора валюты
 1464        new bootstrap.Modal(document.getElementById('currencyModal')).show();
 1465        return false;
 1466    }
 1467
 1468    function showLoadingOverlay() {
 1469        document.getElementById('loadingOverlay')?.classList.add('show');
 1470    }
 1471
 1472    function hideLoadingOverlay() {
 1473        document.getElementById('loadingOverlay')?.classList.remove('show');
 1474    }
 1475
 1476    function submitFormWithSave() {
 1477        document.getElementById('saveName').value = document.getElementById('saveMappingName').value;
 1478        showLoadingOverlay();
 1479        document.getElementById('saveForm').submit();
 1480    }
 1481
 1482    function submitFormWithoutSave() {
 1483        document.getElementById('saveName').value = null;
 1484        showLoadingOverlay();
 1485        document.getElementById('saveForm').submit();
 1486    }
 1487</script>