< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_OzonCards_Views_Excel_Index
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/OzonCards/Views/Excel/Index.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 7
Coverable lines: 7
Total lines: 615
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
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%2040%
<ExecuteAsync()0%342180%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/OzonCards/Views/Excel/Index.cshtml

#LineLine coverage
 1@model OzonOrdersWeb.Areas.OzonCards.ViewModels.ExcelMappingRequest
 2
 3@{
 04ViewData["Title"] = "Обработка Excel файлов";
 5}
 6
 7<div class="container mt-4">
 8    <div class="card">
 9        <div class="card-header bg-primary text-white">
 10            <h4 class="mb-0">
 11                <i class="fas fa-exchange-alt me-2"></i>Обработка Excel файлов
 12            </h4>
 13        </div>
 14        <div class="card-body">
 15            <p class="text-muted">Загрузите исходный и целевой файлы для сопоставления данных</p>
 16
 17            <!-- Форма загрузки файлов -->
 18            <form id="uploadForm" enctype="multipart/form-data" class="mb-4">
 19                <div class="row">
 20                    <div class="col-md-6">
 21                        <div class="card h-100">
 22                            <div class="card-header bg-info text-white">
 23                                <h6 class="mb-0">
 24                                    <i class="fas fa-file-export me-1"></i>Исходный файл
 25                                </h6>
 26                            </div>
 27                            <div class="card-body">
 28                                <div class="form-group">
 29                                    <label class="form-label fw-bold">Исходный файл:</label>
 30                                    <input type="file" id="sourceFile" name="sourceFile" class="form-control" accept=".x
 31                                    <small class="form-text text-muted">Выберите файл с исходными данными</small>
 32                                </div>
 33                            </div>
 34                        </div>
 35                    </div>
 36
 37                    <div class="col-md-6">
 38                        <div class="card h-100">
 39                            <div class="card-header bg-success text-white">
 40                                <h6 class="mb-0">
 41                                    <i class="fas fa-file-import me-1"></i>Целевой файл
 42                                </h6>
 43                            </div>
 44                            <div class="card-body">
 45                                <div class="form-group">
 46                                    <label class="form-label fw-bold">Целевой файл:</label>
 47                                    <input type="file" id="destinationFile" name="destinationFile" class="form-control" 
 48                                    <small class="form-text text-muted">Выберите файл для заполнения данными</small>
 49                                </div>
 50                            </div>
 51                        </div>
 52                    </div>
 53                </div>
 54
 55                <div class="text-center mt-3">
 56                    <button type="button" id="uploadBtn" class="btn btn-primary">
 57                        <i class="fas fa-upload me-1"></i>Загрузить файлы
 58                    </button>
 59                </div>
 60            </form>
 61
 62            <!-- Форма обработки -->
 63            <form id="processForm" asp-action="ProcessExcel" asp-area="OzonCards" asp-controller="Excel" method="post" e
 64                <input type="hidden" name="SourceFileName" id="hiddenSourceFile" />
 65                <input type="hidden" name="DestinationFileName" id="hiddenDestinationFile" />
 66                <input type="hidden" name="MappingName" id="hiddenMappingName" />
 67
 68                <div class="row">
 69                    <div class="col-md-6">
 70                        <div class="card h-100">
 71                            <div class="card-header bg-info text-white">
 72                                <h6 class="mb-0">
 73                                    <i class="fas fa-cog me-1"></i>Настройки исходного файла
 74                                </h6>
 75                            </div>
 76                            <div class="card-body">
 77                                <div class="form-group">
 78                                    <label class="form-label fw-bold">Исходный лист:</label>
 79                                    <select name="SourceSheet" id="sourceSheet" class="form-select" required
 80                                            asp-for="SourceSheet">
 81                                        <option value="">Выберите лист...</option>
 82                                    </select>
 83                                </div>
 84
 85                                <div class="form-group">
 86                                    <label class="form-label fw-bold">Строка заголовков:</label>
 87                                    <input type="number" name="SourceHeaderRow" id="sourceHeaderRow" class="form-control
 88                                           value="@(Model?.SourceHeaderRow ?? 1)" min="1" />
 89                                </div>
 90
 91                                <div class="form-group">
 92                                    <label class="form-label fw-bold">Строка начала данных:</label>
 93                                    <input type="number" name="SourceDataStartRow" id="sourceDataStartRow" class="form-c
 94                                           value="@(Model?.SourceDataStartRow ?? 2)" min="1" />
 95                                </div>
 96                            </div>
 97                        </div>
 98                    </div>
 99
 100                    <div class="col-md-6">
 101                        <div class="card h-100">
 102                            <div class="card-header bg-success text-white">
 103                                <h6 class="mb-0">
 104                                    <i class="fas fa-cog me-1"></i>Настройки целевого файла
 105                                </h6>
 106                            </div>
 107                            <div class="card-body">
 108                                <div class="form-group">
 109                                    <label class="form-label fw-bold">Целевой лист:</label>
 110                                    <select name="DestinationSheet" id="destinationSheet" class="form-select" required>
 111                                        <option value="">Выберите лист...</option>
 112                                    </select>
 113                                </div>
 114
 115                                <div class="form-group">
 116                                    <label class="form-label fw-bold">Строка заголовков:</label>
 117                                    <input type="number" name="DestinationHeaderRow" id="destinationHeaderRow" class="fo
 118                                           value="@(Model?.DestinationHeaderRow ?? 1)" min="1" />
 119                                </div>
 120
 121                                <div class="form-group">
 122                                    <label class="form-label fw-bold">Строка начала данных:</label>
 123                                    <input type="number" name="DestinationDataStartRow" id="destinationDataStartRow" cla
 124                                           value="@(Model?.DestinationDataStartRow ?? 2)" min="1" />
 125                                </div>
 126                            </div>
 127                        </div>
 128                    </div>
 129                </div>
 130
 131                <div class="text-center mt-3">
 132                    <button type="button" id="loadColumnsBtn" class="btn btn-info">
 133                        <i class="fas fa-sync me-1"></i> Загрузить столбцы
 134                    </button>
 135                </div>
 136
 137                <div id="columnsInfo" style="display: none;">
 138                    <hr />
 139
 140                    <div class="card">
 141                        <div class="card-header bg-warning text-dark">
 142                            <h6 class="mb-0">
 143                                <i class="fas fa-columns me-1"></i>Доступные столбцы
 144                            </h6>
 145                        </div>
 146                        <div class="card-body">
 147                            <div class="row">
 148                                <div class="col-md-6">
 149                                    <h6 class="fw-bold">Исходные столбцы:</h6>
 150                                    <div id="sourceColumnsList" class="border p-2" style="max-height: 200px; overflow-y:
 151                                </div>
 152                                <div class="col-md-6">
 153                                    <h6 class="fw-bold">Целевые столбцы:</h6>
 154                                    <div id="destinationColumnsList" class="border p-2" style="max-height: 200px; overfl
 155                                </div>
 156                            </div>
 157                        </div>
 158                    </div>
 159
 160                    <div class="card mt-3">
 161                        <div class="card-header bg-warning text-dark">
 162                            <div class="d-flex justify-content-between align-items-center">
 163                                <h6 class="mb-0">
 164                                    <i class="fas fa-exchange-alt me-1"></i>Сопоставление столбцов
 165                                </h6>
 166                                <button type="button" id="addMapping" class="btn btn-sm btn-outline-dark">
 167                                    <i class="fas fa-plus me-1"></i>Добавить
 168                                </button>
 169                            </div>
 170                        </div>
 171                        <div class="card-body">
 172                            <div id="columnMappings">
 173                                <div class="row mb-3 mapping-row align-items-center">
 174                                    <div class="col-md-5">
 175                                        <select name="ColumnMappings[0].SourceColumn" class="form-select source-column" 
 176                                            <option value="">Выберите исходный столбец</option>
 177                                        </select>
 178                                    </div>
 179
 180                                    <div class="col-md-1 text-center">
 181                                        <i class="fas fa-arrow-right fa-lg text-muted"></i>
 182                                    </div>
 183
 184                                    <div class="col-md-5">
 185                                        <select name="ColumnMappings[0].DestinationColumn" class="form-select dest-colum
 186                                            <option value="">Выберите целевой столбец</option>
 187                                        </select>
 188                                    </div>
 189
 190                                    <div class="col-md-1">
 191                                        <button type="button" class="btn btn-danger btn-sm remove-mapping">
 192                                            <i class="fas fa-times"></i>
 193                                        </button>
 194                                    </div>
 195                                </div>
 196                            </div>
 197                        </div>
 198                    </div>
 199
 200                    <div class="d-flex justify-content-between mt-3">
 201                        <button type="button" id="saveTemplateBtn" class="btn btn-outline-primary">
 202                            <i class="fas fa-save me-1"></i> Сохранить шаблон
 203                        </button>
 204                        <button type="submit" class="btn btn-success">
 205                            <i class="fas fa-cog me-1"></i> Обработать файлы
 206                        </button>
 207                    </div>
 208                </div>
 209            </form>
 210
 211            <!-- Индикатор загрузки -->
 212            <div id="loading" class="text-center" style="display: none;">
 213                <div class="spinner-border text-primary" role="status">
 214                    <span class="sr-only">Загрузка...</span>
 215                </div>
 216                <p class="mt-2">Загрузка данных...</p>
 217            </div>
 218        </div>
 219    </div>
 220</div>
 221
 222<!-- Модальное окно сохранения шаблона -->
 223<div class="modal fade" id="saveTemplateModal" tabindex="-1" aria-hidden="true">
 224    <div class="modal-dialog">
 225        <div class="modal-content">
 226            <div class="modal-header">
 227                <h5 class="modal-title">Сохранение шаблона</h5>
 228                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 229            </div>
 230            <div class="modal-body">
 231                <div class="mb-3">
 232                    <label for="templateName" class="form-label">Название шаблона:</label>
 233                    <input type="text" class="form-control" id="templateName" placeholder="Введите название шаблона" req
 234                    <div class="form-text">Дайте название для вашего шаблона сопоставления</div>
 235                </div>
 236            </div>
 237            <div class="modal-footer">
 238                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
 239                <button type="button" class="btn btn-primary" id="confirmSaveTemplate">Сохранить</button>
 240            </div>
 241        </div>
 242    </div>
 243</div>
 244
 245@section Scripts {
 246<script>
 247    let mappingCount = 1;
 248    let sourceColumns = [];
 249    let destColumns = [];
 250
 251    $('#uploadBtn').click(function () {
 252        const formData = new FormData();
 253        formData.append('sourceFile', $('#sourceFile')[0].files[0]);
 254        formData.append('destinationFile', $('#destinationFile')[0].files[0]);
 255
 256        $('#loading').show();
 257
 258        $.ajax({
 0259            url: '@Url.Action("UploadFiles", "Excel", new { area = "OzonCards" })',
 260            type: 'POST',
 261            data: formData,
 262            processData: false,
 263            contentType: false,
 264            success: function (response) {
 265                if (response.success) {
 266                    // Сохраняем имена файлов в скрытые поля
 267                    $('#hiddenSourceFile').val(response.sourceFileName);
 268                    $('#hiddenDestinationFile').val(response.destFileName);
 269
 270                    FillDropdown('#sourceSheet', response.sourceSheets);
 271                    FillDropdown('#destinationSheet', response.destinationSheets);
 272
 0273                    var selectedSourceSheet = @Html.Raw(Json.Serialize(Model?.SourceSheet ?? ""));
 0274                    var selectedDestinationSheet = @Html.Raw(Json.Serialize(Model?.DestinationSheet ?? ""));
 275
 276                    if (selectedSourceSheet) {
 277                        $('#sourceSheet option').each(function () {
 278                            if ($(this).text() === selectedSourceSheet) {
 279                                $(this).prop('selected', true);
 280                            }
 281                        });
 282                    }
 283
 284                    if (selectedDestinationSheet) {
 285                        $('#destinationSheet option').each(function () {
 286                            if ($(this).text() === selectedDestinationSheet) {
 287                                $(this).prop('selected', true);
 288                            }
 289                        });
 290                    }
 291
 292                    $('#processForm').show();
 293                } else {
 294                    alert('Ошибка: ' + response.message);
 295                }
 296            },
 297            error: function () {
 298                alert('Ошибка загрузки файлов');
 299            },
 300            complete: function () {
 301                $('#loading').hide();
 302            }
 303        });
 304    });
 305
 306    $('#loadColumnsBtn').click(function () {
 307        const sourceFile = $('#sourceFile')[0].files[0];
 308        const destFile = $('#destinationFile')[0].files[0];
 309        const sourceSheet = $('#sourceSheet').val();
 310        const destSheet = $('#destinationSheet').val();
 311        const sourceHeaderRow = $('#sourceHeaderRow').val();
 312        const destHeaderRow = $('#destinationHeaderRow').val();
 313
 314        if (!sourceSheet || !destSheet) {
 315            alert('Выберите листы в обоих файлах');
 316            return;
 317        }
 318
 319        if (window.SkeletonLoader) {
 320            SkeletonLoader.lines('#sourceColumnsList', { count: 6 });
 321            SkeletonLoader.lines('#destinationColumnsList', { count: 6 });
 322            SkeletonLoader.lines('#columnMappings', { count: 5 });
 323        }
 324
 325        const formData = new FormData();
 326        formData.append('sourceFile', sourceFile);
 327        formData.append('destinationFile', destFile);
 328        formData.append('sourceSheet', sourceSheet);
 329        formData.append('destinationSheet', destSheet);
 330        formData.append('sourceHeaderRow', sourceHeaderRow);
 331        formData.append('destinationHeaderRow', destHeaderRow);
 332
 333        $('#loading').show();
 334
 335        $.ajax({
 0336            url: '@Url.Action("GetColumnsInfo", "Excel", new { area = "OzonCards" })',
 337            type: 'POST',
 338            data: formData,
 339            processData: false,
 340            contentType: false,
 341            success: function (response) {
 342                if (response.success) {
 343                    if (window.SkeletonLoader) {
 344                        SkeletonLoader.finish('#sourceColumnsList');
 345                        SkeletonLoader.finish('#destinationColumnsList');
 346                        SkeletonLoader.finish('#columnMappings');
 347                    }
 348                    sourceColumns = response.sourceColumns;
 349                    destColumns = response.destinationColumns;
 350
 351                    ShowColumnsInfo('#sourceColumnsList', sourceColumns, 'Исходные');
 352                    ShowColumnsInfo('#destinationColumnsList', destColumns, 'Целевые');
 353
 354                    FillColumnsDropdown('.source-column', sourceColumns);
 355                    FillColumnsDropdown('.dest-column', destColumns);
 356
 0357                    var existingMappings = @Html.Raw(Json.Serialize(Model?.ColumnMappings ?? []));
 358
 359                    if (existingMappings.length > 0) {
 360                        $('#columnMappings').empty(); // очищаем текущие строки
 361
 362                        existingMappings.forEach(function (map, index) {
 363                            var html = `
 364                                <div class="row mb-3 mapping-row align-items-center">
 365                                    <div class="col-md-5">
 366                                        <select name="ColumnMappings[${index}].SourceColumn" class="form-select source-c
 367                                            <option value="">Выберите исходный столбец</option>
 368                                            ${sourceColumns.map(col => `<option value="${col.name}" title="Пример: ${col
 369                                        </select>
 370                                    </div>
 371                                    <div class="col-md-1 text-center">
 372                                        <i class="fas fa-arrow-right fa-lg text-muted"></i>
 373                                    </div>
 374                                    <div class="col-md-5">
 375                                        <select name="ColumnMappings[${index}].DestinationColumn" class="form-select des
 376                                            <option value="">Выберите целевой столбец</option>
 377                                            ${destColumns.map(col => `<option value="${col.name}" title="Пример: ${col.e
 378                                        </select>
 379                                    </div>
 380                                    <div class="col-md-1">
 381                                        <button type="button" class="btn btn-danger btn-sm remove-mapping">
 382                                            <i class="fas fa-times"></i>
 383                                        </button>
 384                                    </div>
 385                                </div>
 386                                `;
 387                            $('#columnMappings').append(html);
 388
 389                            // Устанавливаем выбранные значения по имени столбца
 390                            if (map.sourceColumn) {
 391                                $(`#columnMappings select.source-column`).last().val(map.sourceColumn);
 392                            }
 393                            if (map.destinationColumn) {
 394                                $(`#columnMappings select.dest-column`).last().val(map.destinationColumn);
 395                            }
 396                        });
 397
 398                        mappingCount = existingMappings.length;
 399                    }
 400
 401                    $('#columnsInfo').show();
 402                } else {
 403                    if (window.SkeletonLoader) {
 404                        SkeletonLoader.finish('#sourceColumnsList');
 405                        SkeletonLoader.finish('#destinationColumnsList');
 406                        SkeletonLoader.finish('#columnMappings');
 407                    }
 408                    alert('Ошибка: ' + response.message);
 409                }
 410            },
 411            error: function (xhr, status, error) {
 412                if (window.SkeletonLoader) {
 413                    SkeletonLoader.finish('#sourceColumnsList');
 414                    SkeletonLoader.finish('#destinationColumnsList');
 415                    SkeletonLoader.finish('#columnMappings');
 416                }
 417                console.error('Error:', error);
 418                alert('Ошибка загрузки столбцов: ' + error);
 419            },
 420            complete: function () {
 421                $('#loading').hide();
 422            }
 423        });
 424    });
 425
 426    // Сохранение шаблона
 427    $('#saveTemplateBtn').click(function() {
 428        $('#saveTemplateModal').modal('show');
 429    });
 430
 431    $('#confirmSaveTemplate').click(function () {
 432        const templateName = $('#templateName').val().trim();
 433        if (!templateName) {
 434            alert('Пожалуйста, введите название шаблона');
 435            return;
 436        }
 437        $('#hiddenMappingName').val(templateName);
 438        $('#saveTemplateModal').modal('hide');
 439        showToast('Имя шаблона сохранено для отправки формы', 'info');
 440    });
 441
 442
 443    function collectColumnMappings() {
 444        const mappings = [];
 445
 446        $('#columnMappings .mapping-row').each(function () {
 447            const sourceColumn = $(this).find('.source-column').val();
 448            const destColumn = $(this).find('.dest-column').val();
 449
 450            const sourceExample = $(this).find('.source-example').val() || "";
 451            const destExample = $(this).find('.dest-example').val() || "";
 452
 453            if (sourceColumn && destColumn) {
 454                mappings.push({
 455                    SourceColumn: sourceColumn,
 456                    DestinationColumn: destColumn,
 457                    SourceExample: sourceExample,
 458                    DestinationExample: destExample
 459                });
 460            }
 461        });
 462
 463        return mappings;
 464    }
 465
 466    function FillDropdown(selector, items) {
 467        $(selector).empty().append('<option value="">Выберите...</option>');
 468        items.forEach(item => {
 469            $(selector).append($('<option>', {
 470                value: item,
 471                text: item
 472            }));
 473        });
 474    }
 475
 476    function FillColumnsDropdown(selector, columns) {
 477        $(selector).empty().append('<option value="">Выберите столбец...</option>');
 478        columns.forEach(col => {
 479            $(selector).append($('<option>', {
 480                value: col.name,
 481                text: col.name,
 482                title: `Пример: ${col.example}`
 483            }));
 484        });
 485    }
 486
 487    function ShowColumnsInfo(selector, columns, title) {
 488        let html = `<div class="small"><strong>${title} столбцы:</strong><br>`;
 489        columns.forEach(col => {
 490            html += `<div class="border-bottom p-1">
 491                    <strong>${col.name}</strong>:
 492                    <span class="text-muted">${col.example}</span>
 493                </div>`;
 494        });
 495        html += '</div>';
 496        $(selector).html(html);
 497    }
 498
 499    $('#addMapping').click(function () {
 500        const html = `
 501                <div class="row mb-3 mapping-row align-items-center">
 502                    <div class="col-md-5">
 503                        <select name="ColumnMappings[${mappingCount}].SourceColumn" class="form-select source-column" re
 504                            <option value="">Выберите исходный столбец</option>
 505                            ${sourceColumns.map(col =>
 506            `<option value="${col.name}" title="Пример: ${col.example}">${col.name}</option>`
 507        ).join('')}
 508                        </select>
 509                    </div>
 510
 511                    <div class="col-md-1 text-center">
 512                        <i class="fas fa-arrow-right fa-lg text-muted"></i>
 513                    </div>
 514
 515                    <div class="col-md-5">
 516                        <select name="ColumnMappings[${mappingCount}].DestinationColumn" class="form-select dest-column"
 517                            <option value="">Выберите целевой столбец</option>
 518                            ${destColumns.map(col =>
 519            `<option value="${col.name}" title="Пример: ${col.example}">${col.name}</option>`
 520        ).join('')}
 521                        </select>
 522                    </div>
 523
 524                    <div class="col-md-1">
 525                        <button type="button" class="btn btn-danger btn-sm remove-mapping">
 526                            <i class="fas fa-times"></i>
 527                        </button>
 528                    </div>
 529                </div>
 530            `;
 531        $('#columnMappings').append(html);
 532        mappingCount++;
 533    });
 534
 535    $(document).on('click', '.remove-mapping', function () {
 536        if ($('.mapping-row').length > 1) {
 537            $(this).closest('.mapping-row').remove();
 538        } else {
 539            alert('Должно быть хотя бы одно сопоставление');
 540        }
 541    });
 542
 543    // Автоматическая подгрузка при изменении важных параметров
 544    $('#sourceSheet, #destinationSheet, #sourceHeaderRow, #destinationHeaderRow').change(function() {
 545        if ($('#sourceSheet').val() && $('#destinationSheet').val()) {
 546            $('#columnsInfo').hide();
 547        }
 548    });
 549
 550    // Функция показа уведомления
 551    function showToast(message, type = 'info') {
 552        const toast = document.createElement('div');
 553        toast.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
 554        toast.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
 555        toast.innerHTML = `
 556                ${message}
 557                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 558            `;
 559
 560        document.body.appendChild(toast);
 561
 562        setTimeout(() => {
 563            toast.remove();
 564        }, 3000);
 565    }
 566    $(document).ready(function () {
 567        // Если модель не null, показываем уведомление
 0568        var modelExists = @Html.Raw((Model != null && Model.ColumnMappings?.Count > 0) ? "true" : "false");
 569
 570        if (modelExists) {
 571            showToast('Сопоставление столбцов подгружено из шаблона', 'success');
 572        }
 573    });
 574</script>
 575
 576<style>
 577    .mapping-row {
 578        transition: all 0.3s ease;
 579        padding: 10px;
 580        border-radius: 5px;
 581        background-color: #f8f9fa;
 582    }
 583
 584    .mapping-row:hover {
 585        background-color: #e9ecef;
 586    }
 587
 588    .remove-mapping {
 589        transition: all 0.2s ease;
 590    }
 591
 592    .remove-mapping:hover {
 593        transform: scale(1.1);
 594    }
 595
 596    .card {
 597        box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
 598        margin-bottom: 1rem;
 599    }
 600
 601    .card-header {
 602        border-bottom: none;
 603    }
 604
 605    option[title]:hover::after {
 606        content: attr(title);
 607        position: absolute;
 608        background: #333;
 609        color: white;
 610        padding: 5px;
 611        border-radius: 3px;
 612        z-index: 1000;
 613    }
 614</style>
 615}

Methods/Properties

ExecuteAsync()
<ExecuteAsync()