| | | 1 | | @model OrderHistoryViewModel |
| | | 2 | | @using OzonDomains |
| | | 3 | | @using OzonDomains.Models |
| | | 4 | | @using OzonOrdersWeb.Areas.Studio2.ViewModels.Grid |
| | | 5 | | @using OzonOrdersWeb.Areas.Studio2.ViewModels.OrderHistory |
| | | 6 | | @using OzonOrdersWeb.WebServices.OrderHistory |
| | | 7 | | @using Services.BitrixStockServices |
| | | 8 | | @using Services.TransactionUtilsServices |
| | | 9 | | |
| | | 10 | | @{ |
| | 0 | 11 | | var transactions = Model.Transactions ?? new List<Transaction>(); |
| | 0 | 12 | | var shipmentTotalsHistory = Model.ShipmentTotalsHistory ?? new List<OrderShipmentTotalsHistoryRowViewModel>(); |
| | 0 | 13 | | var calculationSnapshots = Model.CalculationSnapshots ?? new List<OrderCalculationSnapshotHistoryViewModel>(); |
| | 0 | 14 | | ViewData["Title"] = $"История заказа - {Model.ShipmentNumber ?? $"Заказ #{Model.OrderId}"}"; |
| | 0 | 15 | | var isAdmin = User.IsInRole("Admin"); |
| | 0 | 16 | | var jsonOptions = new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web); |
| | | 17 | | |
| | | 18 | | // Строки гридов: вычисления — в OrderHistoryGridRows, вьюха только сериализует. |
| | 0 | 19 | | var changeGrid = OrderHistoryGridRows.BuildChanges(Model); |
| | 0 | 20 | | var orderedTransactions = transactions.OrderByDescending(t => t.CreatedDateTime).ToList(); |
| | 0 | 21 | | var gridData = new |
| | 0 | 22 | | { |
| | 0 | 23 | | orderId = Model.OrderId, |
| | 0 | 24 | | isAdmin, |
| | 0 | 25 | | changes = changeGrid.Rows, |
| | 0 | 26 | | calculations = calculationSnapshots.Select(s => new |
| | 0 | 27 | | { |
| | 0 | 28 | | s.Id, s.CapturedAt, source = s.Source == "Transaction" ? "Проведение транзакции" : "Ручное редактирование", |
| | 0 | 29 | | s.ShipmentAmount, s.CostPrice, s.MinProfit, s.MaxProfit, s.MinMarkup, s.MaxMarkup |
| | 0 | 30 | | }), |
| | 0 | 31 | | transactions = orderedTransactions.Select(t => new |
| | 0 | 32 | | { |
| | 0 | 33 | | t.Id, |
| | 0 | 34 | | type = t.Type.HasValue ? t.Type.Value.GetDisplayName() : "Транзакция", |
| | 0 | 35 | | icon = t.Type.HasValue ? t.Type.Value.GetTransactionTypeIcon() : "fas fa-circle", |
| | 0 | 36 | | createdBy = t.CreateBy, |
| | 0 | 37 | | createdAt = t.CreatedDateTime, |
| | 0 | 38 | | createdAtMsk = t.CreatedDateTime.HasValue ? BitrixStockMskDateTime.Format(t.CreatedDateTime.Value) : null, |
| | 0 | 39 | | // Проведено в 1С: в комментарии есть GUID документа 1С. |
| | 0 | 40 | | conductedIn1C = !string.IsNullOrWhiteSpace(t.Comment) |
| | 0 | 41 | | && System.Text.RegularExpressions.Regex.IsMatch(t.Comment, "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4} |
| | 0 | 42 | | commentText = string.IsNullOrWhiteSpace(t.Comment) |
| | 0 | 43 | | ? null |
| | 0 | 44 | | : System.Net.WebUtility.HtmlDecode(System.Text.RegularExpressions.Regex.Replace(t.Comment, "<[^>]+>", " |
| | 0 | 45 | | }), |
| | 0 | 46 | | totals = shipmentTotalsHistory.Select(h => new |
| | 0 | 47 | | { |
| | 0 | 48 | | h.UpdatedAt, h.ListAmount, h.AccrualsForSale, h.SaleCommission, h.ProcessingAndDelivery, h.RefundsAndCancell |
| | 0 | 49 | | }), |
| | 0 | 50 | | handover = Model.HandoverItems.Select((h, i) => |
| | 0 | 51 | | { |
| | 0 | 52 | | var (tone, icon, text) = OrderHistoryGridRows.DescribeHandoverStatus(h.Status); |
| | 0 | 53 | | return new |
| | 0 | 54 | | { |
| | 0 | 55 | | id = i, |
| | 0 | 56 | | act = h.ActType ?? h.GroupName ?? "Акт", |
| | 0 | 57 | | actNo = string.IsNullOrWhiteSpace(h.ActNo) ? "—" : h.ActNo, |
| | 0 | 58 | | actDate = h.ActDate != default ? h.ActDate.ToString("dd.MM.yyyy") : null, |
| | 0 | 59 | | pvz = h.Pvz, |
| | 0 | 60 | | returnType = h.ReturnType?.ToUpperInvariant(), |
| | 0 | 61 | | status = text, statusTone = tone, statusIcon = icon, |
| | 0 | 62 | | product = h.ProductName, barcode = h.Barcode, offerId = h.OfferId, sku = h.Sku, |
| | 0 | 63 | | value = h.Value, valueApi = h.ValueApi != h.Value ? h.ValueApi : null, |
| | 0 | 64 | | defect = h.Defect, missort = h.Missort, markedAt = h.MarkedAt, |
| | 0 | 65 | | details = OrderHistoryGridRows.HandoverDetails(h).Select(d => new { label = d.Key, value = d.Value }), |
| | 0 | 66 | | photos = h.Photos.Select(p => Url.Action("GetHandoverPhoto", "OrderHistory", new { area = "Studio2", ord |
| | 0 | 67 | | }; |
| | 0 | 68 | | }) |
| | 0 | 69 | | }; |
| | | 70 | | } |
| | | 71 | | |
| | | 72 | | <link rel="stylesheet" href="~/css/Studio2/Orders/order-history.css" asp-append-version="true"/> |
| | | 73 | | <link rel="stylesheet" href="~/css/Studio2/Orders/order-history-redesign.css" asp-append-version="true"/> |
| | | 74 | | <link rel="stylesheet" href="~/css/Studio2/Orders/order-history-movement-map.css" asp-append-version="true"/> |
| | | 75 | | <link rel="stylesheet" href="~/css/Studio2/BitrixStock/bitrix-stock.css" asp-append-version="true"/> |
| | | 76 | | <link rel="stylesheet" href="~/css/Studio2/Orders/TransactionsTable.css" asp-append-version="true"/> |
| | 0 | 77 | | @await Html.PartialAsync("_BsxGridShell") |
| | | 78 | | <link rel="stylesheet" href="~/css/Studio2/Orders/order-history-grids.css" asp-append-version="true"/> |
| | | 79 | | <link rel="stylesheet" href="~/css/Studio2/OrdersGrid/feed-explain.css" asp-append-version="true"/> |
| | | 80 | | |
| | | 81 | | <div id="top" class="oh-page container-fluid bsx-surface"> |
| | | 82 | | <a href="/Orders/Index" class="oh-page-back"> |
| | | 83 | | <i class="fas fa-arrow-left"></i> К списку заказов |
| | | 84 | | </a> |
| | | 85 | | |
| | | 86 | | @{ |
| | 0 | 87 | | var hasTransactions = Model.Transactions != null && Model.Transactions.Any(); |
| | | 88 | | // Начисления Ozon грузятся лениво через AJAX (GetShipmentTotals) — на SSR-этапе |
| | | 89 | | // известно только, есть ли у заказа номер отправления, по которому их можно запросить. |
| | 0 | 90 | | var hasFinanceTab = !string.IsNullOrWhiteSpace(Model.ShipmentNumber); |
| | 0 | 91 | | var hasHistories = Model.Histories.Any(); |
| | 0 | 92 | | var hasTotalsHistory = shipmentTotalsHistory.Count > 0; |
| | 0 | 93 | | var hasHandoverItems = Model.HandoverItems.Any(); |
| | 0 | 94 | | var createdRecord = Model.Histories |
| | 0 | 95 | | .SelectMany(g => g.Value) |
| | 0 | 96 | | .FirstOrDefault(c => c.ColumnDisplayName == "Создание записи"); |
| | 0 | 97 | | var totalChanges = Model.Histories.Values.Sum(h => h.Count); |
| | | 98 | | } |
| | | 99 | | <div class="card oh-section oh-hero-card"> |
| | | 100 | | <div class="oh-hero-body"> |
| | 0 | 101 | | @if (!string.IsNullOrWhiteSpace(Model.ProductImageUrl)) |
| | | 102 | | { |
| | | 103 | | <div class="oh-hero-image"> |
| | | 104 | | <img src="@Model.ProductImageUrl" |
| | | 105 | | alt="@(Model.ProductName ?? Model.Article ?? "Товар")" |
| | | 106 | | class="oh-hero-thumb" |
| | | 107 | | onclick="openProductImage(this.src, this.alt)" |
| | | 108 | | onerror="this.style.display='none'" |
| | | 109 | | loading="lazy" /> |
| | | 110 | | </div> |
| | | 111 | | } |
| | | 112 | | |
| | | 113 | | <div class="oh-hero-main"> |
| | | 114 | | <div class="oh-hero-top"> |
| | | 115 | | <div class="oh-hero-heading"> |
| | | 116 | | <span class="oh-hero-title-text"> |
| | | 117 | | <i class="fas fa-history"></i> |
| | 0 | 118 | | @(Model.ShipmentNumber ?? $"Заказ #{Model.OrderId}") |
| | | 119 | | </span> |
| | 0 | 120 | | @if (hasTransactions) |
| | | 121 | | { |
| | | 122 | | <span class="oh-hero-badge-blue"> |
| | 0 | 123 | | <i class="fas fa-exchange-alt me-1"></i>@transactions.Count тр. |
| | | 124 | | </span> |
| | | 125 | | } |
| | 0 | 126 | | @if (hasHistories) |
| | | 127 | | { |
| | | 128 | | <span class="oh-hero-badge-purple"> |
| | 0 | 129 | | <i class="fas fa-pencil-alt me-1"></i>@totalChanges изм. |
| | | 130 | | </span> |
| | | 131 | | } |
| | | 132 | | </div> |
| | | 133 | | |
| | | 134 | | <div class="oh-hero-meta-row"> |
| | 0 | 135 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 136 | | { |
| | 0 | 137 | | <span class="oh-hero-meta-chip"><i class="fas fa-barcode"></i>@Model.Article</span> |
| | | 138 | | } |
| | 0 | 139 | | @if (!string.IsNullOrWhiteSpace(Model.ClientName)) |
| | | 140 | | { |
| | 0 | 141 | | <span class="oh-hero-meta-chip"><i class="fas fa-building"></i>@Model.ClientName</span> |
| | | 142 | | } |
| | 0 | 143 | | @if (!string.IsNullOrWhiteSpace(Model.ProductName)) |
| | | 144 | | { |
| | | 145 | | <span class="oh-hero-meta-chip" title="@Model.ProductName"> |
| | 0 | 146 | | <i class="fas fa-box"></i>@Model.ProductName |
| | | 147 | | </span> |
| | | 148 | | } |
| | 0 | 149 | | @if (createdRecord != null) |
| | | 150 | | { |
| | | 151 | | <span class="oh-hero-meta-chip"> |
| | 0 | 152 | | <i class="fas fa-calendar-alt"></i>@createdRecord.ChangedAt.ToString("dd.MM.yyyy HH:mm") |
| | | 153 | | </span> |
| | | 154 | | } |
| | | 155 | | </div> |
| | | 156 | | </div> |
| | | 157 | | |
| | | 158 | | <div class="oh-hero-stats"> |
| | | 159 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-history"> |
| | 0 | 160 | | <span class="oh-hero-stat-value oh-hero-stat-value--accent">@totalChanges</span> |
| | | 161 | | <span class="oh-hero-stat-label">Изменений</span> |
| | | 162 | | </button> |
| | 0 | 163 | | @if (hasTransactions) |
| | | 164 | | { |
| | | 165 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-transactions"> |
| | 0 | 166 | | <span class="oh-hero-stat-value">@transactions.Count</span> |
| | | 167 | | <span class="oh-hero-stat-label">Транзакций</span> |
| | | 168 | | </button> |
| | | 169 | | } |
| | 0 | 170 | | @if (hasFinanceTab) |
| | | 171 | | { |
| | | 172 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-finance"> |
| | | 173 | | <i class="fas fa-ruble-sign oh-hero-stat-icon"></i> |
| | | 174 | | <span class="oh-hero-stat-label">Начисления Ozon</span> |
| | | 175 | | </button> |
| | | 176 | | } |
| | 0 | 177 | | @if (hasTotalsHistory) |
| | | 178 | | { |
| | | 179 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-totals"> |
| | 0 | 180 | | <span class="oh-hero-stat-value">@shipmentTotalsHistory.Count</span> |
| | | 181 | | <span class="oh-hero-stat-label">История итогов</span> |
| | | 182 | | </button> |
| | | 183 | | } |
| | 0 | 184 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 185 | | { |
| | | 186 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-feed"> |
| | | 187 | | <i class="fas fa-tags oh-hero-stat-icon"></i> |
| | | 188 | | <span class="oh-hero-stat-label">Фид</span> |
| | | 189 | | </button> |
| | | 190 | | } |
| | 0 | 191 | | @if (Model.ProductId.HasValue) |
| | | 192 | | { |
| | | 193 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-quotations"> |
| | | 194 | | <i class="fas fa-clock-rotate-left oh-hero-stat-icon"></i> |
| | | 195 | | <span class="oh-hero-stat-label">Проценки</span> |
| | | 196 | | </button> |
| | | 197 | | } |
| | 0 | 198 | | @if (hasHandoverItems) |
| | | 199 | | { |
| | | 200 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-handover"> |
| | 0 | 201 | | <span class="oh-hero-stat-value">@Model.HandoverItems.Count</span> |
| | | 202 | | <span class="oh-hero-stat-label">Данные по возврату</span> |
| | | 203 | | </button> |
| | | 204 | | } |
| | 0 | 205 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 206 | | { |
| | | 207 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-bitrix"> |
| | | 208 | | <i class="fas fa-sync oh-hero-stat-icon"></i> |
| | | 209 | | <span class="oh-hero-stat-label">BitrixStock</span> |
| | | 210 | | </button> |
| | | 211 | | <button type="button" class="oh-hero-stat-item" data-tab="tab-movement"> |
| | | 212 | | <i class="fas fa-chart-line oh-hero-stat-icon"></i> |
| | | 213 | | <span class="oh-hero-stat-label">Карта движений</span> |
| | | 214 | | </button> |
| | | 215 | | } |
| | | 216 | | </div> |
| | | 217 | | </div> |
| | | 218 | | </div> |
| | | 219 | | </div> |
| | | 220 | | |
| | | 221 | | <partial name="_OrderProblems" model="Model.Problems" /> |
| | | 222 | | |
| | | 223 | | <!-- ── Панель вкладок ───────────────────────────────────────── --> |
| | | 224 | | <div class="oh-tabs-bar" id="oh-tabs-bar"> |
| | | 225 | | <button class="oh-tab-btn active" data-tab="tab-history" id="btn-tab-history"> |
| | | 226 | | <i class="fas fa-pencil-alt"></i> |
| | | 227 | | История изменений |
| | 0 | 228 | | @if (hasHistories) |
| | | 229 | | { |
| | 0 | 230 | | <span class="oh-tab-badge">@totalChanges</span> |
| | | 231 | | } |
| | | 232 | | </button> |
| | 0 | 233 | | @if (calculationSnapshots.Any()) |
| | | 234 | | { |
| | | 235 | | <button class="oh-tab-btn" data-tab="tab-calculations" id="btn-tab-calculations"> |
| | | 236 | | <i class="fas fa-calculator"></i> |
| | | 237 | | Расчёты |
| | 0 | 238 | | <span class="oh-tab-badge">@calculationSnapshots.Count</span> |
| | | 239 | | </button> |
| | | 240 | | } |
| | 0 | 241 | | @if (hasTransactions) |
| | | 242 | | { |
| | | 243 | | <button class="oh-tab-btn" data-tab="tab-transactions" id="btn-tab-transactions"> |
| | | 244 | | <i class="fas fa-exchange-alt"></i> |
| | | 245 | | Транзакции |
| | 0 | 246 | | <span class="oh-tab-badge">@transactions.Count</span> |
| | | 247 | | </button> |
| | | 248 | | } |
| | 0 | 249 | | @if (hasFinanceTab) |
| | | 250 | | { |
| | | 251 | | <button class="oh-tab-btn" data-tab="tab-finance" id="btn-tab-finance"> |
| | | 252 | | <i class="fas fa-ruble-sign"></i> |
| | | 253 | | Начисления Ozon |
| | | 254 | | </button> |
| | | 255 | | } |
| | 0 | 256 | | @if (hasTotalsHistory) |
| | | 257 | | { |
| | | 258 | | <button class="oh-tab-btn" data-tab="tab-totals" id="btn-tab-totals"> |
| | | 259 | | <i class="fas fa-history"></i> |
| | | 260 | | История итогов |
| | 0 | 261 | | <span class="oh-tab-badge">@shipmentTotalsHistory.Count</span> |
| | | 262 | | </button> |
| | | 263 | | } |
| | 0 | 264 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 265 | | { |
| | | 266 | | <button class="oh-tab-btn" data-tab="tab-feed" id="btn-tab-feed"> |
| | | 267 | | <i class="fas fa-tags"></i> |
| | | 268 | | Фид |
| | | 269 | | </button> |
| | | 270 | | } |
| | 0 | 271 | | @if (Model.ProductId.HasValue) |
| | | 272 | | { |
| | | 273 | | <button class="oh-tab-btn" data-tab="tab-quotations" id="btn-tab-quotations"> |
| | | 274 | | <i class="fas fa-clock-rotate-left"></i> |
| | | 275 | | Проценки поставщиков |
| | 0 | 276 | | <span class="oh-tab-badge" id="oh-quotation-tab-count">@Model.QuotationOffersCount</span> |
| | | 277 | | </button> |
| | | 278 | | } |
| | 0 | 279 | | @if (hasHandoverItems) |
| | | 280 | | { |
| | | 281 | | <button class="oh-tab-btn" data-tab="tab-handover" id="btn-tab-handover"> |
| | | 282 | | <i class="fas fa-box-open"></i> |
| | | 283 | | Данные по возврату |
| | 0 | 284 | | <span class="oh-tab-badge">@Model.HandoverItems.Count</span> |
| | | 285 | | </button> |
| | | 286 | | } |
| | 0 | 287 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 288 | | { |
| | | 289 | | <button class="oh-tab-btn" data-tab="tab-bitrix" id="btn-tab-bitrix"> |
| | | 290 | | <i class="fas fa-sync"></i> |
| | | 291 | | BitrixStock |
| | | 292 | | </button> |
| | | 293 | | <button class="oh-tab-btn" data-tab="tab-movement" id="btn-tab-movement"> |
| | | 294 | | <i class="fas fa-chart-line"></i> |
| | | 295 | | Карта движений 1С |
| | | 296 | | </button> |
| | | 297 | | } |
| | | 298 | | </div> |
| | | 299 | | |
| | | 300 | | <!-- ── Содержимое вкладок: все таблицы — гриды AG Grid (order-history-grids.js) ── --> |
| | | 301 | | <div class="oh-tabs-content"> |
| | | 302 | | |
| | | 303 | | <div class="oh-tab-pane active" id="tab-history"> |
| | 0 | 304 | | @if (!Model.Histories.Any()) |
| | | 305 | | { |
| | | 306 | | <div class="oh-empty-state"> |
| | | 307 | | <img src="~/images/status/empty-state.svg" asp-append-version="true" alt=""> |
| | | 308 | | <div> |
| | | 309 | | <strong>История изменений отсутствует</strong> |
| | | 310 | | <div class="small mt-1">Записи появляются при создании и изменении заказа. Для старых заказов ис |
| | | 311 | | </div> |
| | | 312 | | </div> |
| | | 313 | | } |
| | | 314 | | else |
| | | 315 | | { |
| | 0 | 316 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohChanges", "История измене |
| | 0 | 317 | | "Поиск по полю, значению, пользователю…", |
| | 0 | 318 | | "Строки одной операции объединены по времени и автору. Фильтры колонок: дата, пользователь, поле. Кн |
| | | 319 | | @* Проведения 1С операций — разметка партиала, показывается модалкой по кнопке в строке. *@ |
| | 0 | 320 | | @foreach (var operation in changeGrid.Operations.Where(o => o.OneCPostings.Count > 0)) |
| | | 321 | | { |
| | | 322 | | <template id="oh-onec-@operation.Index"> |
| | 0 | 323 | | @await Html.PartialAsync("~/Areas/Studio2/Views/OrderHistory/_OrderHistoryOneCPostingHeader.csht |
| | | 324 | | </template> |
| | | 325 | | } |
| | | 326 | | } |
| | | 327 | | </div> |
| | | 328 | | |
| | 0 | 329 | | @if (calculationSnapshots.Any()) |
| | | 330 | | { |
| | | 331 | | <div class="oh-tab-pane" id="tab-calculations"> |
| | 0 | 332 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohCalc", "Сохранённые расчё |
| | 0 | 333 | | "Значения зафиксированы на момент проведения или редактирования. «Подробный расчёт» — полный расчёт |
| | | 334 | | </div> |
| | | 335 | | } |
| | | 336 | | |
| | 0 | 337 | | @if (transactions.Any()) |
| | | 338 | | { |
| | | 339 | | <div class="oh-tab-pane" id="tab-transactions"> |
| | 0 | 340 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohTx", "Транзакции заказа", |
| | 0 | 341 | | "Комментарий транзакции целиком — кнопкой в строке или двойным кликом.")) |
| | 0 | 342 | | @foreach (var tr in orderedTransactions) |
| | | 343 | | { |
| | | 344 | | <template id="oh-txc-@tr.Id"> |
| | | 345 | | <partial name="_OrderHistoryTransactionComment" model="TransactionCommentDisplayFormatter.ParseF |
| | | 346 | | </template> |
| | | 347 | | } |
| | | 348 | | </div> |
| | | 349 | | } |
| | | 350 | | |
| | 0 | 351 | | @if (!string.IsNullOrWhiteSpace(Model.ShipmentNumber)) |
| | | 352 | | { |
| | | 353 | | <div class="oh-tab-pane" id="tab-finance"> |
| | 0 | 354 | | <div id="oh-finance-section" data-order-id="@Model.OrderId"> |
| | | 355 | | <div class="bsx-dir-stats mb-2"> |
| | | 356 | | <span class="bsx-dir-stat">Операций <b id="oh-finance-count-badge">—</b></span> |
| | | 357 | | <span class="bsx-dir-stat">Период <b id="oh-finance-period">—</b></span> |
| | | 358 | | <span class="bsx-dir-stat">Итого <b id="oh-finance-total">—</b></span> |
| | | 359 | | </div> |
| | | 360 | | <div id="oh-finance-error" class="alert alert-danger small d-none"></div> |
| | 0 | 361 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohFinance", "Начисления |
| | 0 | 362 | | "Данные Ozon API по номеру отправления (кэш 20 мин). Фильтры колонок: дата, тип операции, катего |
| | | 363 | | <div class="oh-totals-grid d-none" id="oh-finance-totals-grid"> |
| | | 364 | | <div class="oh-totals-grid-label"><i class="fas fa-calculator me-1"></i>Итоги по отправлению</di |
| | | 365 | | <div class="oh-totals-cards" id="oh-finance-totals-cards"></div> |
| | | 366 | | </div> |
| | | 367 | | </div> |
| | | 368 | | </div> |
| | | 369 | | } |
| | | 370 | | |
| | 0 | 371 | | @if (shipmentTotalsHistory.Any()) |
| | | 372 | | { |
| | | 373 | | <div class="oh-tab-pane" id="tab-totals"> |
| | 0 | 374 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohTotals", "История итогов |
| | | 375 | | </div> |
| | | 376 | | } |
| | | 377 | | |
| | 0 | 378 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 379 | | { |
| | | 380 | | <div class="oh-tab-pane" id="tab-feed"> |
| | | 381 | | <div id="oh-feed-section" |
| | 0 | 382 | | data-url="@Url.Action("GetFeedPriceStock", "OrderHistory", new { area = "Studio2", orderId = Model. |
| | 0 | 383 | | data-explain-url="@Url.Action("GetFeedPriceExplanation", "OrderHistory", new { area = "Studio2", or |
| | | 384 | | <div class="oh-feed-bar"> |
| | | 385 | | <div class="oh-feed-bar-main"> |
| | | 386 | | <span class="oh-feed-bar-label">Артикул в фиде</span> |
| | | 387 | | <span class="oh-feed-key" id="oh-feed-key">Загрузка из фида…</span> |
| | | 388 | | </div> |
| | | 389 | | <div class="oh-feed-cabinets" id="oh-feed-cabinets" role="tablist" aria-label="Кабинет фида"></d |
| | | 390 | | <button type="button" class="btn btn-sm btn-outline-secondary" id="oh-feed-refresh"> |
| | | 391 | | <i class="fas fa-rotate me-1"></i>Обновить |
| | | 392 | | </button> |
| | | 393 | | </div> |
| | | 394 | | <div class="oh-feed-summary" id="oh-feed-summary"></div> |
| | | 395 | | <div class="oh-feed-layout"> |
| | | 396 | | <section class="oh-feed-block oh-feed-block--explain"> |
| | | 397 | | <h3 class="oh-feed-title">Как получилась цена</h3> |
| | | 398 | | <div id="oh-feed-explain" class="oh-feed-explain"></div> |
| | | 399 | | </section> |
| | | 400 | | <div class="oh-feed-side"> |
| | | 401 | | <section class="oh-feed-block"> |
| | | 402 | | <h3 class="oh-feed-title">Остатки по складам <span class="oh-feed-count" id="oh-feed-cur |
| | | 403 | | <div id="ohFeedCurrent" class="oh-feed-grid"></div> |
| | | 404 | | </section> |
| | | 405 | | <section class="oh-feed-block"> |
| | | 406 | | <h3 class="oh-feed-title">История цены по сборкам <span class="oh-feed-count" id="oh-fee |
| | | 407 | | <div id="ohFeedHistory" class="oh-feed-grid oh-feed-grid--history"></div> |
| | | 408 | | </section> |
| | | 409 | | </div> |
| | | 410 | | </div> |
| | | 411 | | </div> |
| | | 412 | | </div> |
| | | 413 | | } |
| | | 414 | | |
| | 0 | 415 | | @if (Model.ProductId.HasValue) |
| | | 416 | | { |
| | | 417 | | <div class="oh-tab-pane" id="tab-quotations"> |
| | | 418 | | <div class="bsx-grid-toolbar mb-2"> |
| | | 419 | | <div class="bsx-quick" id="oh-quote-kind"> |
| | 0 | 420 | | <button type="button" class="is-active" data-kind="offers">Предложения <b>@Model.QuotationOffers |
| | | 421 | | <button type="button" data-kind="failed">Ошибки запросов</button> |
| | | 422 | | </div> |
| | 0 | 423 | | <span class="bsx-grid-count">@Model.QuotationRequestsCount запросов по товару</span> |
| | | 424 | | </div> |
| | 0 | 425 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohQuote", "История проценок |
| | 0 | 426 | | "Поиск по наименованию, направлению, производителю…", |
| | 0 | 427 | | "Фильтры и сортировка — по всей истории проценок товара на сервере. Срок с «+Nч» включает надбавку п |
| | | 428 | | </div> |
| | | 429 | | } |
| | | 430 | | |
| | 0 | 431 | | @if (Model.HandoverItems.Any()) |
| | | 432 | | { |
| | | 433 | | <div class="oh-tab-pane" id="tab-handover"> |
| | 0 | 434 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohHandover", "Данные по воз |
| | 0 | 435 | | "Акты выдачи/приёма-передачи/отказа Ozon (импорт «Telegram Lite»). Подробности и фото — в последней |
| | | 436 | | </div> |
| | | 437 | | } |
| | | 438 | | |
| | 0 | 439 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 440 | | { |
| | | 441 | | <div class="oh-tab-pane" id="tab-bitrix"> |
| | | 442 | | <div id="oh-bitrix-sync-section" |
| | 0 | 443 | | data-order-id="@Model.OrderId" |
| | 0 | 444 | | data-article="@Model.Article" |
| | 0 | 445 | | data-export-url="@Url.Action("ExportBitrixSyncHistoryExcel", "OrderHistory", new { area = "Studio2" |
| | | 446 | | <div class="bsx-grid-toolbar mb-2"> |
| | 0 | 447 | | <span class="bsx-dir-stat">Артикул <b>@Model.Article</b></span> |
| | | 448 | | <span class="bsx-grid-count" id="oh-bitrix-sync-meta"></span> |
| | | 449 | | <div class="bsx-grid-toolbar-end"> |
| | | 450 | | <button type="button" id="oh-bitrix-sync-more" class="btn btn-sm btn-outline-secondary d-non |
| | | 451 | | <button type="button" class="btn btn-sm btn-outline-success d-none" id="oh-bitrix-sync-expor |
| | | 452 | | </div> |
| | | 453 | | </div> |
| | | 454 | | <div id="oh-bitrix-sync-error" class="alert alert-danger small d-none"></div> |
| | 0 | 455 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohBitrix", "История син |
| | 0 | 456 | | "Поиск по складу, действию, примечанию…", "Строка = склад в отчёте синхронизации; отчёт — колонк |
| | | 457 | | </div> |
| | | 458 | | </div> |
| | | 459 | | |
| | | 460 | | <div class="oh-tab-pane" id="tab-movement"> |
| | 0 | 461 | | <div id="ohm-section" data-article="@Model.Article" data-can-edit-reserve="@(isAdmin.ToString().ToLowerI |
| | | 462 | | <div id="ohm-balance-wrap" class="mb-3 d-none"> |
| | | 463 | | <div class="small fw-semibold mb-1 bsx-muted">Текущий остаток:</div> |
| | | 464 | | <div id="ohm-balance-line"></div> |
| | 0 | 465 | | @if (isAdmin) |
| | | 466 | | { |
| | | 467 | | <div id="ohm-stock-actions" class="mt-2 small"></div> |
| | | 468 | | } |
| | | 469 | | <div class="small fw-semibold mb-1 mt-2 bsx-muted">В резерве:</div> |
| | | 470 | | <div id="ohm-reserve-line"></div> |
| | 0 | 471 | | @if (isAdmin) |
| | | 472 | | { |
| | | 473 | | <div id="ohm-reserve-actions" class="mt-2 small"></div> |
| | | 474 | | } |
| | | 475 | | </div> |
| | | 476 | | <div class="bsx-dir-stats mb-3" id="ohm-stats-row"> |
| | | 477 | | <span class="bsx-muted small"><i class="fas fa-spinner fa-spin me-1"></i>Кликните на вкладку для |
| | | 478 | | </div> |
| | | 479 | | <div id="ohm-error" class="alert alert-danger d-none"></div> |
| | 0 | 480 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("ohMove", "Карта движени |
| | 0 | 481 | | "Поиск по документу, складу…", "Баланс — нарастающий итог по непометкам на удаление в хронологич |
| | | 482 | | </div> |
| | | 483 | | </div> |
| | | 484 | | } |
| | | 485 | | |
| | | 486 | | </div>@* /oh-tabs-content *@ |
| | | 487 | | </div> |
| | | 488 | | |
| | 0 | 489 | | @await Html.PartialAsync("~/Areas/Studio2/Views/Partials/Shared/_ProductImageModal.cshtml") |
| | | 490 | | |
| | | 491 | | <div class="modal fade" id="ohDetailModal" tabindex="-1" aria-hidden="true"> |
| | | 492 | | <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable"> |
| | | 493 | | <div class="modal-content"> |
| | | 494 | | <div class="modal-header"> |
| | | 495 | | <h5 class="modal-title" id="ohDetailTitle"></h5> |
| | | 496 | | <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button> |
| | | 497 | | </div> |
| | | 498 | | <div class="modal-body" id="ohDetailBody"></div> |
| | | 499 | | </div> |
| | | 500 | | </div> |
| | | 501 | | </div> |
| | | 502 | | |
| | 0 | 503 | | @if (User.IsInRole("Admin")) |
| | | 504 | | { |
| | 0 | 505 | | @Html.AntiForgeryToken() |
| | | 506 | | |
| | | 507 | | <div class="modal fade" id="ohmAdjustmentModal" tabindex="-1" aria-labelledby="ohmAdjustmentModalTitle" aria-hidden= |
| | | 508 | | <div class="modal-dialog modal-dialog-centered"> |
| | | 509 | | <div class="modal-content ohm-adjustment-modal"> |
| | | 510 | | <div class="modal-header"> |
| | | 511 | | <div> |
| | | 512 | | <div class="ohm-modal-kicker"><i class="fas fa-shield-halved"></i> Контролируемое изменение</div |
| | | 513 | | <h5 class="modal-title" id="ohmAdjustmentModalTitle">Изменить резерв</h5> |
| | | 514 | | </div> |
| | | 515 | | <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button> |
| | | 516 | | </div> |
| | | 517 | | <form id="ohm-adjustment-form"> |
| | | 518 | | <div class="modal-body"> |
| | | 519 | | <div class="ohm-adjustment-summary" id="ohm-adjustment-summary"></div> |
| | | 520 | | <div class="mb-3"> |
| | | 521 | | <label class="form-label" for="ohm-adjustment-quantity">Новое количество</label> |
| | | 522 | | <input class="form-control" id="ohm-adjustment-quantity" type="number" min="0" step="0.001" |
| | | 523 | | <div class="form-text" id="ohm-adjustment-help"></div> |
| | | 524 | | </div> |
| | | 525 | | <div class="mb-0"> |
| | | 526 | | <label class="form-label" for="ohm-adjustment-reason">Причина изменения</label> |
| | | 527 | | <textarea class="form-control" id="ohm-adjustment-reason" rows="3" minlength="3" maxlength=" |
| | | 528 | | <div class="form-text">Будет записана в комментарий документа 1С и журнал аудита.</div> |
| | | 529 | | </div> |
| | | 530 | | <div class="alert alert-danger py-2 px-3 mt-3 mb-0 d-none" id="ohm-adjustment-error"></div> |
| | | 531 | | </div> |
| | | 532 | | <div class="modal-footer"> |
| | | 533 | | <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button> |
| | | 534 | | <button type="submit" class="btn btn-primary" id="ohm-adjustment-submit"><i class="fas fa-check |
| | | 535 | | </div> |
| | | 536 | | </form> |
| | | 537 | | </div> |
| | | 538 | | </div> |
| | | 539 | | </div> |
| | | 540 | | |
| | | 541 | | <div class="modal fade" id="ohRollbackModal" tabindex="-1" aria-hidden="true"> |
| | | 542 | | <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable"> |
| | | 543 | | <div class="modal-content oh-rollback-modal"> |
| | | 544 | | <div class="modal-header"> |
| | | 545 | | <h5 class="modal-title"> |
| | | 546 | | <i class="fas fa-clock-rotate-left me-2 text-primary"></i> |
| | | 547 | | Восстановление заказа |
| | | 548 | | </h5> |
| | | 549 | | <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button> |
| | | 550 | | </div> |
| | | 551 | | <div class="modal-body"> |
| | | 552 | | <p class="oh-rollback-lead mb-3"> |
| | | 553 | | Заказ будет приведён к состоянию на момент |
| | | 554 | | <strong id="ohRollbackPointLabel">—</strong>. |
| | | 555 | | Восстановление будет записано в историю и помечено как «Откат». |
| | | 556 | | </p> |
| | | 557 | | |
| | | 558 | | <div id="ohRollbackLoading" class="text-center text-muted py-4 d-none"> |
| | | 559 | | <i class="fas fa-spinner fa-spin me-2"></i>Расчёт изменений… |
| | | 560 | | </div> |
| | | 561 | | |
| | | 562 | | <div id="ohRollbackError" class="alert alert-danger d-none" role="alert"></div> |
| | | 563 | | |
| | | 564 | | <div id="ohRollbackEmpty" class="alert alert-info d-none" role="alert"> |
| | | 565 | | <i class="fas fa-info-circle me-1"></i>Заказ уже находится в этом состоянии — изменять нечего. |
| | | 566 | | </div> |
| | | 567 | | |
| | | 568 | | <div id="ohRollbackChangesWrap" class="d-none"> |
| | | 569 | | <div id="ohRollbackGrid" class="bsx-grid bsx-grid--auto"></div> |
| | | 570 | | <div id="ohRollbackWarnings" class="mt-3 d-none"></div> |
| | | 571 | | </div> |
| | | 572 | | </div> |
| | | 573 | | <div class="modal-footer"> |
| | | 574 | | <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button> |
| | | 575 | | <button type="button" class="btn btn-primary" id="ohRollbackConfirmBtn" disabled> |
| | | 576 | | <i class="fas fa-check me-1"></i>Подтвердить восстановление |
| | | 577 | | </button> |
| | | 578 | | </div> |
| | | 579 | | </div> |
| | | 580 | | </div> |
| | | 581 | | </div> |
| | | 582 | | } |
| | | 583 | | |
| | | 584 | | @section Scripts { |
| | | 585 | | <script> |
| | 0 | 586 | | window.ohGridData = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(gridData, jsonOptions)); |
| | 0 | 587 | | window.ohLabelPreviewUrl = '@Url.Action("Preview", "Labels", new { area = "Studio2" })'; |
| | 0 | 588 | | window.ohQuotationProviders = @Html.Raw(System.Text.Json.JsonSerializer.Serialize( |
| | 0 | 589 | | new[] { "Armtek", "ArmtekBy", "ShateM", "Avd", "Berg", "Motex", "MlAuto", "MlAutoRu", "ForumAuto", "ForumAut |
| | 0 | 590 | | "ForumAutoRostov", "ForumAutoIstra", "FavoritParts", "FavoritPartsIstra", "FavoritPartsRostov", "Mos |
| | 0 | 591 | | "Nikei", "MikadoMskHod20", "Japarts", "ZZapMoscow" } |
| | 0 | 592 | | .ToDictionary(k => k, SupplierQuotationProviders.Info), jsonOptions)); |
| | | 593 | | </script> |
| | | 594 | | <script src="~/js/Studio2/Grid/bsx-directory.js" asp-append-version="true"></script> |
| | 0 | 595 | | @if (Model.LabelPrints.Any()) |
| | | 596 | | { |
| | | 597 | | <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js" crossorigin="anonymous"></script |
| | | 598 | | } |
| | | 599 | | <script src="~/js/Studio2/OrdersGrid/orders-grid-feed-explain.js" asp-append-version="true"></script> |
| | | 600 | | <script src="~/js/Studio2/Orders/order-history-grids.js" asp-append-version="true"></script> |
| | | 601 | | <script src="~/js/Studio2/Orders/order-history-tx-comment.js" asp-append-version="true"></script> |
| | | 602 | | <partial name="~/Areas/Studio2/Views/Partials/Transaction/_TransactionCalculationModal.cshtml" /> |
| | | 603 | | <script src="~/js/Studio2/Orders/Transactions/transaction-calculation-explainer.js" asp-append-version="true"></scri |
| | | 604 | | <script src="~/js/Studio2/Orders/order-history-bitrix-sync.js" asp-append-version="true"></script> |
| | 0 | 605 | | @if (!string.IsNullOrWhiteSpace(Model.ShipmentNumber)) |
| | | 606 | | { |
| | | 607 | | <script src="~/js/Studio2/Orders/order-history-finance.js" asp-append-version="true"></script> |
| | | 608 | | } |
| | 0 | 609 | | @if (!string.IsNullOrWhiteSpace(Model.Article)) |
| | | 610 | | { |
| | | 611 | | <script src="~/js/Studio2/Orders/order-history-movement-map.js" asp-append-version="true"></script> |
| | | 612 | | } |
| | 0 | 613 | | @if (isAdmin) |
| | | 614 | | { |
| | | 615 | | <script src="~/js/Studio2/Orders/order-history-rollback.js" asp-append-version="true"></script> |
| | | 616 | | } |
| | | 617 | | } |