< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_Orders_Upload
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/Orders/Upload.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 10
Coverable lines: 10
Total lines: 463
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 38
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%1482380%

File(s)

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

#LineLine coverage
 1@{
 02    ViewData["Title"] = "Загрузка заказов";
 03    string? erorrResult = ViewData["ErorrResult"] as string;
 4}
 5
 6<link rel="stylesheet" href="~/css/secondary-pages.css" asp-append-version="true"/>
 7<link rel="stylesheet" href="~/css/Studio2/Orders/orders-upload-shared.css" asp-append-version="true"/>
 8<link rel="stylesheet" href="~/css/Studio2/Orders/orders-upload.css" asp-append-version="true"/>
 9<link rel="stylesheet" href="~/css/Studio2/Orders/orders-upload-redesign.css" asp-append-version="true"/>
 10<link rel="stylesheet" href="~/css/Studio2/Orders/orders-handover-upload.css" asp-append-version="true"/>
 11
 12<div class="container py-3 ou-page">
 13    <div class="ou-hero app-section-hero app-section-hero--import">
 14        <h1><i class="fas fa-cloud-download-alt me-2 text-primary"></i>Загрузка заказов</h1>
 15        <div class="subtitle">Импорт из API маркетплейсов или Excel/CSV файла</div>
 16        <div class="subtitle-help">
 17            <strong>API</strong> — фоновая загрузка FBS-отправлений Ozon / Yandex за выбранный период (Hangfire, уведомл
 18            <strong>Файл</strong> — разбор таблицы поставщика, сохранение копии в Dropbox и создание/обновление заказов 
 19        </div>
 20        <div class="ou-quick-links app-section-hero__quick-links">
 21            <a asp-action="Index"><i class="fas fa-list"></i>Список заказов</a>
 22            <a asp-action="UploadFile"><i class="fas fa-file-excel"></i>Только загрузка файла</a>
 23            <a asp-area="Studio2" asp-controller="Percentage" asp-action="UploadFile"><i class="fas fa-percent"></i>Проц
 24        </div>
 25    </div>
 26
 27    <div class="ou-intro">
 28        <i class="fas fa-circle-info"></i>
 29        <div class="ou-intro-text">
 30            Два независимых сценария на одной странице. После API-загрузки вы вернётесь в список заказов — прогресс смот
 31            Для файла сначала проверьте предпросмотр и при CSV укажите разделитель.
 32        </div>
 33    </div>
 34
 35    <div class="ou-workflow">
 36        <div class="ou-workflow-step"><span class="num">1</span>Выберите способ</div>
 37        <div class="ou-workflow-step"><span class="num">2</span>Период или файл</div>
 38        <div class="ou-workflow-step"><span class="num">3</span>Предпросмотр (файл)</div>
 39        <div class="ou-workflow-step"><span class="num">4</span>Заказы в списке</div>
 40    </div>
 41
 042    @if (erorrResult != null)
 43{
 44    <div class="alert alert-danger alert-dismissible mb-4">
 45        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 46        <img src="~/images/status/upload-error.svg" asp-append-version="true" alt="" width="28" height="28" class="me-2 
 047        @Html.Raw(erorrResult)
 48    </div>
 49}
 50
 051@if (!string.IsNullOrEmpty(ViewBag.Message as string))
 52{
 53    <div class="alert alert-info alert-dismissible mb-4">
 54        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
 055        <img src="~/images/status/info.svg" asp-append-version="true" alt="" width="24" height="24" class="me-2 align-te
 56    </div>
 57}
 58
 59<div class="ou-upload-grid">
 60
 61    <div class="ou-card">
 62        <div class="ou-card-header">
 63            <div class="ou-upload-card-title">
 64                <span class="ou-upload-card-icon ou-upload-card-icon--api">
 65                    <i class="fas fa-calendar-alt"></i>
 66                </span>
 67                Загрузка из API
 68            </div>
 69        </div>
 70        <div class="ou-card-body">
 71            <div class="ou-api-note">
 72                <i class="fas fa-clock me-1"></i>Задание уходит в Hangfire — результат придёт уведомлением, страница сра
 73            </div>
 74            <form method="post" id="uploadForm" asp-action="Upload">
 75
 76                <div class="ou-form-row">
 77                    <div class="ou-field-group">
 78                        <label for="period" class="ou-field-label">Тип периода</label>
 79                        <select class="form-select form-select-sm custom-select-init" id="period" name="PeriodType">
 80                            <option value="fixed">Фиксированный</option>
 81                            <option value="custom">Ввести в днях</option>
 82                        </select>
 83                        <div class="ou-field-hint">Фиксированный — пресеты; в днях — 1–365</div>
 84                    </div>
 85
 86                    <div class="ou-field-group" id="fixedPeriodGroup">
 87                        <label for="fixedPeriod" class="ou-field-label">Период</label>
 88                        <select class="form-select form-select-sm custom-select-init" id="fixedPeriod" name="FixedPeriod
 89                            <option value="1">Сегодня</option>
 90                            <option value="2">Два дня</option>
 91                            <option value="7">Неделя</option>
 92                            <option value="14">Две недели</option>
 93                            <option value="30">Месяц</option>
 94                        </select>
 95                    </div>
 96
 97                    <div class="ou-field-group" id="customDaysGroup" style="display:none;">
 98                        <label for="customDays" class="ou-field-label">Дней (1–365)</label>
 99                        <input type="number" class="form-control form-control-sm" id="customDays" name="CustomDays"
 100                               min="1" max="365" placeholder="Кол-во дней">
 101                    </div>
 102
 103                    <input type="hidden" id="finalPeriod" name="period" value="1"/>
 104
 105                    <div class="ou-field-group">
 106                        <label for="clientType" class="ou-field-label">Клиент</label>
 107                        <select class="form-select form-select-sm custom-select-init" id="clientType" name="clientType">
 108                            <option value="0">Все</option>
 109                            <option value="1">Ozon</option>
 110                            <option value="2">Yandex</option>
 111                        </select>
 112                        <div class="ou-field-hint">Фильтр по типу клиента в БД</div>
 113                    </div>
 114                </div>
 115
 116                <button type="submit" class="btn btn-primary btn-sm">
 117                    <i class="fas fa-download me-1"></i>Загрузить в фоне
 118                </button>
 119
 120            </form>
 121        </div>
 122    </div>
 123
 124    <div class="ou-card">
 125        <div class="ou-card-header">
 126            <div class="ou-upload-card-title">
 127                <span class="ou-upload-card-icon ou-upload-card-icon--file">
 128                    <i class="fas fa-file-excel"></i>
 129                </span>
 130                Загрузка файла
 131            </div>
 132        </div>
 133        <div class="ou-card-body">
 0134            @using (Html.BeginForm("UploadExcelFile", "Orders", FormMethod.Post,
 0135                new { enctype = "multipart/form-data", @class = "needs-validation", novalidate = "novalidate" }))
 136            {
 137                <div class="ou-drop-zone" id="dropZone">
 138                    <input type="file" id="fileInput" name="file" accept=".xlsx,.xls,.csv" required>
 139                    <i class="fas fa-cloud-upload-alt ou-drop-zone-icon"></i>
 140                    <div class="ou-drop-zone-text" id="dropZoneText">Перетащите файл или нажмите для выбора</div>
 141                    <div class="ou-drop-zone-hint">.xlsx · .xls · .csv — копия уйдёт в Dropbox</div>
 142                </div>
 143
 144                <div id="delimiterInput" style="display:none;" class="mb-3">
 145                    <div class="ou-field-group" style="max-width: 120px;">
 146                        <label for="delimiter" class="ou-field-label">Разделитель CSV</label>
 147                        <input type="text" class="form-control form-control-sm" id="delimiter" name="delimiter"
 148                               value=";" onchange="updatePreviewIfFileSelected()">
 149                        <div class="ou-field-hint">По умолчанию «;»</div>
 150                    </div>
 151                </div>
 152
 153                <div id="startInputs" style="display:none;" class="mb-3">
 154                    <div class="ou-settings-row">
 155                        <div class="ou-field-group">
 156                            <label for="startRowInput" class="ou-field-label">С строки</label>
 157                            <input type="number" class="form-control form-control-sm" id="startRowInput" name="startRow"
 158                                   value="1" min="1" onchange="updatePreviewIfFileSelected()">
 159                            <div class="ou-field-hint">Строка заголовков</div>
 160                        </div>
 161                        <div class="ou-field-group">
 162                            <label for="startColumnInput" class="ou-field-label">С столбца</label>
 163                            <input type="number" class="form-control form-control-sm" id="startColumnInput" name="startC
 164                                   value="1" min="1" onchange="updatePreviewIfFileSelected()">
 165                        </div>
 166                    </div>
 167                </div>
 168
 169                <button type="submit" class="btn btn-primary btn-sm" id="submitBtn" disabled>
 170                    <i class="fas fa-upload me-1"></i>Загрузить файл
 171                </button>
 0172            }
 173        </div>
 174    </div>
 175
 176</div>
 177
 178<div id="filePreview" style="display:none;" class="ou-preview-card">
 179    <div class="ou-preview-card-header">
 180        <i class="fas fa-eye"></i>
 181        Предпросмотр файла
 182    </div>
 183    <div class="ou-preview-table-wrap">
 184        <table class="table table-sm table-bordered mb-0" id="previewTable">
 185            <thead id="previewTableHead"
 186                   style="position:sticky; top:0; background:#f8fafc; z-index:10;"></thead>
 187            <tbody id="previewTableBody"></tbody>
 188        </table>
 189    </div>
 190    <div class="ou-preview-card-footer" id="previewInfo"></div>
 191</div>
 192
 193<div class="ou-requirements">
 194    <h6>Требования к файлу</h6>
 195    <ul class="mb-0 ps-3">
 196        <li>Первая строка (или указанная «с строки») — заголовки столбцов</li>
 197        <li>CSV: UTF-8; при неверной кодировке проверьте разделитель</li>
 198        <li>Структура должна соответствовать шаблону поставщика / выгрузки Ozon</li>
 199    </ul>
 200</div>
 201
 202<!-- ═══ Отдельная секция: ручная загрузка JSON выдачи/возврата Ozon ═══ -->
 203<div class="ou-handover-section" id="ohuSection">
 204    <div class="ou-card">
 205        <div class="ou-card-header">
 206            <div class="ou-upload-card-title">
 207                <span class="ou-upload-card-icon ou-upload-card-icon--file">
 208                    <i class="fas fa-box-open"></i>
 209                </span>
 210                Импорт выдачи / возврата Ozon (JSON)
 211            </div>
 212        </div>
 213        <div class="ou-card-body">
 214            <p class="text-muted small mb-3">
 215                JSON-документ выдачи, приёма-передачи или отказа в выдаче Ozon (формат «Telegram Lite»).
 216                Позиции сопоставляются с заказами по номеру отправления; повторная загрузка того же
 217                документа обновляет уже сохранённые данные без дублей.
 218            </p>
 0219            <form id="ohuForm" data-no-loading data-url="@Url.Action("UploadHandoverJson", "Orders", new { area = "Studi
 220                <div class="ou-drop-zone" id="ohuDropZone">
 221                    <input type="file" id="ohuFileInput" name="file" accept=".json" required>
 222                    <i class="fas fa-file-code ou-drop-zone-icon"></i>
 223                    <div class="ou-drop-zone-text" id="ohuDropZoneText">Перетащите JSON или нажмите для выбора</div>
 224                    <div class="ou-drop-zone-hint">.json — акты выдачи / приёма-передачи / отказа Ozon</div>
 225                </div>
 226                <button type="submit" class="btn btn-primary btn-sm mt-3" id="ohuSubmitBtn" disabled>
 227                    <i class="fas fa-upload me-1"></i>Загрузить и сопоставить
 228                </button>
 229            </form>
 230            <div id="ohuPreview" class="mt-3" style="display:none;"></div>
 231            <div id="ohuResult" class="mt-3"></div>
 232        </div>
 233    </div>
 234</div>
 235
 236</div>
 237
 238<!-- XLSX reader -->
 239<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
 240<script src="~/js/status-color-contrast.js" asp-append-version="true"></script>
 241<script src="~/js/Studio2/Orders/orders-handover-upload.js" asp-append-version="true"></script>
 242
 243<script>
 244    document.addEventListener('DOMContentLoaded', function () {
 245
 246        /* ── Период загрузки ──────────────────────────────── */
 247        const periodSelect      = document.getElementById('period');
 248        const fixedPeriodGroup  = document.getElementById('fixedPeriodGroup');
 249        const fixedPeriodSelect = document.getElementById('fixedPeriod');
 250        const customDaysGroup   = document.getElementById('customDaysGroup');
 251        const customDaysInput   = document.getElementById('customDays');
 252        const finalPeriodInput  = document.getElementById('finalPeriod');
 253
 254        function syncPeriod() {
 255            if (periodSelect.value === 'custom') {
 256                fixedPeriodGroup.style.display = 'none';
 257                customDaysGroup.style.display  = 'block';
 258                customDaysInput.required = true;
 259                if (customDaysInput.value) finalPeriodInput.value = customDaysInput.value;
 260            } else {
 261                fixedPeriodGroup.style.display = 'block';
 262                customDaysGroup.style.display  = 'none';
 263                customDaysInput.required = false;
 264                customDaysInput.value = '';
 265                finalPeriodInput.value = fixedPeriodSelect.value;
 266            }
 267        }
 268
 269        periodSelect.addEventListener('change', syncPeriod);
 270        fixedPeriodSelect.addEventListener('change', function () {
 271            if (periodSelect.value === 'fixed') finalPeriodInput.value = this.value;
 272        });
 273        customDaysInput.addEventListener('input', function () {
 274            if (periodSelect.value === 'custom' && this.value) finalPeriodInput.value = this.value;
 275        });
 276        syncPeriod();
 277
 278        /* ── Файловый инпут + drag & drop ────────────────── */
 279        const fileInput     = document.getElementById('fileInput');
 280        const dropZone      = document.getElementById('dropZone');
 281        const dropZoneText  = document.getElementById('dropZoneText');
 282        const startInputs   = document.getElementById('startInputs');
 283        const delimiterInput= document.getElementById('delimiterInput');
 284        const submitBtn     = document.getElementById('submitBtn');
 285
 286        function setFileSubmitPending() {
 287            if (submitBtn.disabled) return;
 288            submitBtn.disabled = true;
 289            submitBtn.setAttribute('aria-busy', 'true');
 290            submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1" aria-hidden="true"></i>Загружаем…';
 291        }
 292
 293        // Drag & drop
 294        ['dragenter', 'dragover'].forEach(evt => {
 295            dropZone.addEventListener(evt, function (e) {
 296                e.preventDefault();
 297                dropZone.classList.add('drag-over');
 298            });
 299        });
 300        ['dragleave', 'drop'].forEach(evt => {
 301            dropZone.addEventListener(evt, function (e) {
 302                e.preventDefault();
 303                dropZone.classList.remove('drag-over');
 304            });
 305        });
 306        dropZone.addEventListener('drop', function (e) {
 307            const file = e.dataTransfer.files[0];
 308            if (file) {
 309                const dt = new DataTransfer();
 310                dt.items.add(file);
 311                fileInput.files = dt.files;
 312                handleFileSelected(file);
 313            }
 314        });
 315
 316        fileInput.addEventListener('change', function () {
 317            if (this.files[0]) {
 318                handleFileSelected(this.files[0]);
 319            } else {
 320                resetFileState();
 321            }
 322        });
 323
 324        const fileUploadForm = document.querySelector('form[enctype="multipart/form-data"]');
 325        if (fileUploadForm) {
 326            fileUploadForm.addEventListener('submit', function (event) {
 327                if (!fileUploadForm.checkValidity()) return;
 328                setFileSubmitPending();
 329            });
 330        }
 331
 332        function handleFileSelected(file) {
 333            const ext = file.name.split('.').pop().toLowerCase();
 334            dropZoneText.textContent = file.name;
 335            dropZone.classList.add('has-file');
 336
 337            startInputs.style.display    = 'block';
 338            delimiterInput.style.display = ext === 'csv' ? 'block' : 'none';
 339            submitBtn.disabled = false;
 340
 341            previewFile(file, ext);
 342        }
 343
 344        function resetFileState() {
 345            dropZoneText.textContent = 'Перетащите файл сюда или нажмите для выбора';
 346            dropZone.classList.remove('has-file');
 347            startInputs.style.display    = 'none';
 348            delimiterInput.style.display = 'none';
 349            submitBtn.disabled = true;
 350            document.getElementById('filePreview').style.display = 'none';
 351        }
 352
 353        /* ── Предпросмотр ────────────────────────────────── */
 354        function previewFile(file, extension) {
 355            const previewDiv  = document.getElementById('filePreview');
 356            const thead       = document.getElementById('previewTableHead');
 357            const tbody       = document.getElementById('previewTableBody');
 358            const info        = document.getElementById('previewInfo');
 359            const startRow    = parseInt(document.getElementById('startRowInput')?.value || 1);
 360            const startColumn = parseInt(document.getElementById('startColumnInput')?.value || 1);
 361
 362            previewDiv.style.display = 'block';
 363            thead.innerHTML = '';
 364            tbody.innerHTML = '';
 365            info.textContent = 'Загрузка...';
 366
 367            const reader = new FileReader();
 368
 369            reader.onload = function (e) {
 370                try {
 371                    let headers = [], data = [];
 372
 373                    if (extension === 'csv') {
 374                        const delimiter = document.getElementById('delimiter')?.value || ';';
 375                        const lines = e.target.result.split('\n').filter(l => l.trim());
 376                        if (lines.length > 0) {
 377                            headers = lines[0].split(delimiter).map(h => h.trim().replace(/"/g, ''));
 378                            for (let i = Math.max(1, startRow); i < Math.min(lines.length, startRow + 10); i++) {
 379                                data.push(lines[i].split(delimiter).map(v => v.trim().replace(/"/g, '')));
 380                            }
 381                        }
 382                    } else {
 383                        const wb = XLSX.read(e.target.result, { type: 'binary' });
 384                        const ws = wb.Sheets[wb.SheetNames[0]];
 385                        const json = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' });
 386                        if (json.length > 0) {
 387                            const hi = Math.max(0, startRow - 1);
 388                            if (json[hi]) headers = json[hi].slice(startColumn - 1);
 389                            for (let i = Math.max(startRow, hi + 1); i < Math.min(json.length, startRow + 10); i++) {
 390                                if (json[i]) data.push(json[i].slice(startColumn - 1));
 391                            }
 392                        }
 393                    }
 394
 395                    // Шапка
 396                    if (headers.length > 0) {
 397                        const tr = document.createElement('tr');
 398                        headers.forEach(h => {
 399                            const th = document.createElement('th');
 400                            th.textContent = h || '';
 401                            tr.appendChild(th);
 402                        });
 403                        thead.appendChild(tr);
 404                    }
 405
 406                    // Строки
 407                    data.forEach(row => {
 408                        const tr = document.createElement('tr');
 409                        headers.forEach((_, idx) => {
 410                            const td = document.createElement('td');
 411                            td.textContent = row[idx] ?? '';
 412                            tr.appendChild(td);
 413                        });
 414                        tbody.appendChild(tr);
 415                    });
 416
 417                    info.textContent = `Показано ${data.length} строк из «${file.name}». Предпросмотр ограничен 10 строк
 418                } catch (err) {
 419                    info.textContent = 'Ошибка при чтении файла: ' + err.message;
 420                }
 421            };
 422
 423            reader.onerror = () => { info.textContent = 'Ошибка при чтении файла'; };
 424
 425            extension === 'csv' ? reader.readAsText(file, 'UTF-8') : reader.readAsBinaryString(file);
 426        }
 427
 428        window.updatePreviewIfFileSelected = function () {
 429            if (fileInput.files?.[0]) {
 430                const ext = fileInput.files[0].name.split('.').pop().toLowerCase();
 431                previewFile(fileInput.files[0], ext);
 432            }
 433        };
 434
 435        /* ── Валидация форм ──────────────────────────────── */
 436        document.getElementById('uploadForm').addEventListener('submit', function (e) {
 437            if (periodSelect.value === 'custom') {
 438                const days = parseInt(customDaysInput.value);
 439                if (!days || days < 1 || days > 365) {
 440                    e.preventDefault();
 441                    alert('Введите число от 1 до 365 дней');
 442                    customDaysInput.focus();
 443                    return;
 444                }
 445                finalPeriodInput.value = days;
 446            }
 447            if (!this.checkValidity()) { e.preventDefault(); }
 448            this.classList.add('was-validated');
 449            if (e.defaultPrevented) return;
 450
 451            const button = this.querySelector('button[type="submit"]');
 452            if (!button || button.disabled) return;
 453            button.disabled = true;
 454            button.setAttribute('aria-busy', 'true');
 455            button.innerHTML = '<i class="fas fa-spinner fa-spin me-1" aria-hidden="true"></i>Задача создаётся…';
 456        });
 457
 458        document.querySelector('form[enctype="multipart/form-data"]').addEventListener('submit', function (e) {
 459            if (!this.checkValidity()) { e.preventDefault(); }
 460            this.classList.add('was-validated');
 461        });
 462    });
 463</script>

Methods/Properties

ExecuteAsync()