| | | 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 | | @{ |
| | 0 | 213 | | var currencyCodeMap = new Dictionary<int, string> |
| | 0 | 214 | | { |
| | 0 | 215 | | { 1, "USD" }, |
| | 0 | 216 | | { 2, "EUR" }, |
| | 0 | 217 | | { 3, "RUB" }, |
| | 0 | 218 | | { 4, "BYN" } |
| | 0 | 219 | | }; |
| | | 220 | | |
| | 0 | 221 | | var currencyCodeMapJson = JsonConvert.SerializeObject(currencyCodeMap); |
| | 0 | 222 | | var previewDataJson = JsonConvert.SerializeObject(Model.TableData.Take(5).ToList()); |
| | | 223 | | |
| | 0 | 224 | | decimal rateUSD = ViewBag.RateUSD ?? 0m; |
| | 0 | 225 | | decimal rateEUR = ViewBag.RateEUR ?? 0m; |
| | 0 | 226 | | decimal rateBYN = ViewBag.RateBYN ?? 0m; |
| | 0 | 227 | | 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()"> |
| | 0 | 269 | | @foreach (var c in Model.CurrencyCodes) |
| | | 270 | | { |
| | 0 | 271 | | <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 | | |
| | 0 | 338 | | @if (TempData["MappingWarning"] != null) |
| | | 339 | | { |
| | 0 | 340 | | 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"> |
| | 0 | 350 | | @foreach (var warning in warnings) |
| | | 351 | | { |
| | 0 | 352 | | <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> |
| | 0 | 381 | | @foreach (var mapping in Model.SavedMappings) |
| | | 382 | | { |
| | 0 | 383 | | var mappingPairs = mapping.ColumnMappings != null |
| | 0 | 384 | | ? string.Join(", ", mapping.ColumnMappings |
| | 0 | 385 | | .Where(kv => !string.IsNullOrWhiteSpace(kv.Value)) |
| | 0 | 386 | | .Select(kv => $"{kv.Key} → {kv.Value}")) |
| | 0 | 387 | | : ""; |
| | 0 | 388 | | <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> |
| | 0 | 412 | | @for (int i = 0; i < Model.MainTableHeaders.Count; i++) |
| | | 413 | | { |
| | 0 | 414 | | var mainHeader = Model.MainTableHeaders[i]; |
| | 0 | 415 | | var inputId = "input-" + mainHeader.Replace(" ", "-"); |
| | | 416 | | |
| | | 417 | | // Строку "Код валюты отправления" скрываем — она выбирается в модалке |
| | 0 | 418 | | if (mainHeader == "Код валюты отправления") |
| | | 419 | | { |
| | | 420 | | continue; |
| | | 421 | | } |
| | | 422 | | |
| | | 423 | | <tr id="row-@i"> |
| | 0 | 424 | | <td class="main-header">@mainHeader</td> |
| | | 425 | | <td> |
| | 0 | 426 | | @if (mainHeader == "Клиент") |
| | | 427 | | { |
| | | 428 | | <div class="d-flex align-items-center gap-2"> |
| | | 429 | | <select name="ColumnMappings[@mainHeader]" class="form-select" |
| | | 430 | | id="input-Клиент-Таблица" |
| | 0 | 431 | | data-row-id="@i" |
| | 0 | 432 | | data-main-header="@mainHeader" |
| | | 433 | | onchange="updateTableHeader(this)"> |
| | | 434 | | <option value="">Выберите столбец</option> |
| | 0 | 435 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 436 | | { |
| | 0 | 437 | | <option value="@tempHeader">@tempHeader</option> |
| | | 438 | | } |
| | | 439 | | </select> |
| | | 440 | | <select name="SelectedClient.Id" class="form-select" |
| | | 441 | | id="input-Клиент" |
| | 0 | 442 | | data-row-id="@i" |
| | 0 | 443 | | data-main-header="@mainHeader" |
| | | 444 | | onchange="updateClient(this)"> |
| | | 445 | | <option value="">Выберите клиента</option> |
| | 0 | 446 | | @foreach (var client in Model.OzonClients) |
| | | 447 | | { |
| | 0 | 448 | | <option value="@client.Id">@client.Name</option> |
| | | 449 | | } |
| | | 450 | | </select> |
| | | 451 | | </div> |
| | | 452 | | } |
| | 0 | 453 | | 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" |
| | 0 | 458 | | data-row-id="@i" |
| | 0 | 459 | | data-main-header="@mainHeader" |
| | | 460 | | onchange="updateTableHeader(this)"> |
| | | 461 | | <option value="">Выберите столбец</option> |
| | 0 | 462 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 463 | | { |
| | 0 | 464 | | <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 | | } |
| | 0 | 472 | | 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" |
| | 0 | 477 | | data-row-id="@i" |
| | 0 | 478 | | data-main-header="@mainHeader" |
| | | 479 | | onchange="updateTableHeader(this)"> |
| | | 480 | | <option value="">Выберите столбец</option> |
| | 0 | 481 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 482 | | { |
| | 0 | 483 | | <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 | | } |
| | 0 | 491 | | 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-Статус-Клиента-Таблица" |
| | 0 | 496 | | data-row-id="@i" |
| | 0 | 497 | | data-main-header="@mainHeader" |
| | | 498 | | onchange="updateTableHeader(this)"> |
| | | 499 | | <option value="">Выберите столбец</option> |
| | 0 | 500 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 501 | | { |
| | 0 | 502 | | <option value="@tempHeader">@tempHeader</option> |
| | | 503 | | } |
| | | 504 | | </select> |
| | | 505 | | <select name="SelectedStatus" class="form-select" |
| | | 506 | | id="input-Статус-Клиента" |
| | 0 | 507 | | data-row-id="@i" |
| | 0 | 508 | | data-main-header="@mainHeader" |
| | | 509 | | onchange="updateClient(this)"> |
| | | 510 | | <option value="">Выберите статус</option> |
| | 0 | 511 | | @foreach (var status in Model.Statuses) |
| | | 512 | | { |
| | 0 | 513 | | <option value="@status.Value">@status.Text</option> |
| | | 514 | | } |
| | | 515 | | </select> |
| | | 516 | | </div> |
| | | 517 | | } |
| | 0 | 518 | | 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-Производитель-Таблица" |
| | 0 | 523 | | data-row-id="@i" |
| | 0 | 524 | | data-main-header="@mainHeader" |
| | | 525 | | onchange="updateTableHeader(this)"> |
| | | 526 | | <option value="">Выберите столбец</option> |
| | 0 | 527 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 528 | | { |
| | 0 | 529 | | <option value="@tempHeader">@tempHeader</option> |
| | | 530 | | } |
| | | 531 | | </select> |
| | | 532 | | <select name="SelectedManufacturer.Id" class="form-select" |
| | | 533 | | id="input-Производитель" |
| | 0 | 534 | | data-row-id="@i" |
| | 0 | 535 | | data-main-header="@mainHeader" |
| | | 536 | | onchange="updateClient(this)"> |
| | | 537 | | <option value="">Выберите производителя</option> |
| | | 538 | | <option value="-1">На основе трёх последних символов артикула</option> |
| | | 539 | | <option value="-2">Из названия</option> |
| | 0 | 540 | | @foreach (var manufacturer in Model.Manufacturers) |
| | | 541 | | { |
| | 0 | 542 | | <option value="@manufacturer.Id">@manufacturer.Name</option> |
| | | 543 | | } |
| | | 544 | | </select> |
| | | 545 | | </div> |
| | | 546 | | } |
| | 0 | 547 | | 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тгрузки-Таблица" |
| | 0 | 552 | | data-row-id="@i" |
| | 0 | 553 | | data-main-header="@mainHeader" |
| | | 554 | | onchange="updateTableHeader(this)"> |
| | | 555 | | <option value="">Выберите столбец</option> |
| | 0 | 556 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 557 | | { |
| | 0 | 558 | | <option value="@tempHeader">@tempHeader</option> |
| | | 559 | | } |
| | | 560 | | </select> |
| | | 561 | | <select name="SelectedWarehouse.Id" class="form-select" |
| | | 562 | | id="input-Склад-Oтгрузки" |
| | 0 | 563 | | data-row-id="@i" |
| | 0 | 564 | | data-main-header="@mainHeader" |
| | | 565 | | onchange="updateClient(this)"> |
| | | 566 | | <option value="">Выберите склад отгрузки</option> |
| | 0 | 567 | | @foreach (var warehouse in Model.Warehouses) |
| | | 568 | | { |
| | 0 | 569 | | <option value="@warehouse.Id">@warehouse.Name</option> |
| | | 570 | | } |
| | | 571 | | </select> |
| | | 572 | | </div> |
| | | 573 | | } |
| | 0 | 574 | | 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-Поставщик" |
| | 0 | 580 | | data-row-id="@i" |
| | 0 | 581 | | data-main-header="@mainHeader" |
| | | 582 | | onchange="updateClient(this)"> |
| | | 583 | | <option value="">Выберите поставщика</option> |
| | 0 | 584 | | @foreach (var supplier in Model.Suppliers) |
| | | 585 | | { |
| | 0 | 586 | | <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" |
| | 0 | 596 | | data-row-id="@i" |
| | 0 | 597 | | data-main-header="@mainHeader" |
| | | 598 | | onchange="updateTableHeader(this)"> |
| | | 599 | | <option value="">Выберите столбец</option> |
| | 0 | 600 | | @foreach (var tempHeader in Model.TableHeaders) |
| | | 601 | | { |
| | 0 | 602 | | <option value="@tempHeader">@tempHeader</option> |
| | | 603 | | } |
| | | 604 | | </select> |
| | | 605 | | </div> |
| | | 606 | | } |
| | | 607 | | </td> |
| | | 608 | | </tr> |
| | 0 | 609 | | } |
| | | 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"> |
| | 0 | 621 | | <span class="badge bg-secondary">@Model.TableData.Count строк</span> |
| | 0 | 622 | | <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> |
| | 0 | 630 | | @foreach (var header in Model.TableHeaders) |
| | | 631 | | { |
| | | 632 | | <th id="header-@header.Replace(" ", "-")"> |
| | 0 | 633 | | @header |
| | | 634 | | <span id="mapping-@header.Replace(" ", "-")" class="badge bg-success ms-1" style="fo |
| | | 635 | | </th> |
| | | 636 | | } |
| | | 637 | | </tr> |
| | | 638 | | </thead> |
| | | 639 | | <tbody> |
| | 0 | 640 | | @for (int i = 0; i < Math.Min(5, Model.TableData.Count); i++) |
| | | 641 | | { |
| | | 642 | | <tr> |
| | 0 | 643 | | @foreach (var header in Model.TableHeaders) |
| | | 644 | | { |
| | 0 | 645 | | <td class="table-cell" data-header="@header"> |
| | 0 | 646 | | @(Model.TableData[i].ContainsKey(header) ? Model.TableData[i][header] : "") |
| | | 647 | | </td> |
| | | 648 | | } |
| | | 649 | | </tr> |
| | | 650 | | } |
| | 0 | 651 | | @if (Model.TableData.Count > 5) |
| | | 652 | | { |
| | | 653 | | <tr> |
| | | 654 | | <td colspan="@Model.TableHeaders.Count" class="text-center text-muted py-2"> |
| | 0 | 655 | | <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;"> |
| | 0 | 741 | | @for (int i = 0; i < Model.MainTableHeaders.Count; i++) |
| | | 742 | | { |
| | 0 | 743 | | 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"> |
| | 0 | 747 | | @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;"> |
| | 0 | 756 | | @for (int i = 0; i < Model.MainTableHeaders.Count; i++) |
| | | 757 | | { |
| | 0 | 758 | | 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"> |
| | 0 | 762 | | @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 | | // ===== Данные из сервера ===== |
| | 0 | 779 | | var currencyCodeMap = @Html.Raw(currencyCodeMapJson); |
| | 0 | 780 | | var previewData = @Html.Raw(previewDataJson); |
| | 0 | 781 | | 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 | | |
| | 0 | 981 | | 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'); |
| | 0 | 1103 | | 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 | | } |
| | 0 | 1140 | | 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) { |
| | 0 | 1176 | | 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> |