| | | 1 | | @using OzonOrdersWeb.Areas.Studio2.ViewModels.Grid |
| | | 2 | | @using OzonOrdersWeb.WebServices.Directories |
| | | 3 | | @model List<SupplierGridRow> |
| | | 4 | | @{ |
| | 0 | 5 | | ViewData["Title"] = $"Поставщики ({Model.Count})"; |
| | 0 | 6 | | Layout = "~/Views/Shared/_Layout.cshtml"; |
| | 0 | 7 | | var jsonOptions = new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web); |
| | | 8 | | } |
| | | 9 | | |
| | 0 | 10 | | @await Html.PartialAsync("_BsxGridShell") |
| | 0 | 11 | | <script type="application/json" id="spApiSuppliersData">@Html.Raw(System.Text.Json.JsonSerializer.Serialize((List<ApiSup |
| | | 12 | | <link rel="stylesheet" href="~/css/Studio2/Supplier/supplier-index.css" asp-append-version="true"/> |
| | | 13 | | |
| | | 14 | | <div class="container-fluid py-3 sp-page bsx-surface"> |
| | | 15 | | <div class="app-section-hero app-section-hero--import d-flex flex-wrap justify-content-between align-items-start gap |
| | | 16 | | <div> |
| | | 17 | | <h1><i class="fas fa-truck-loading me-2 text-primary"></i>Поставщики</h1> |
| | | 18 | | <div class="subtitle">Справочник поставщиков, коэффициенты цен и связь с контрагентами 1С</div> |
| | | 19 | | <div class="subtitle-help mt-1"> |
| | | 20 | | Поставщик привязывается к заказу и влияет на расчёт закупки (коэффициент и валюта), себестоимости |
| | | 21 | | (доставка за кг × вес товара), сроков поставки и остатков (CSV URL). ИНН нужен для проводок в 1С — |
| | | 22 | | в колонке «1С» показано, найден ли контрагент в базе. |
| | | 23 | | </div> |
| | | 24 | | <div class="app-section-hero__quick-links"> |
| | | 25 | | <a asp-area="Studio2" asp-controller="OzonClients" asp-action="Index"><i class="fas fa-store"></i>Клиент |
| | | 26 | | <a asp-area="Studio2" asp-controller="Warehouse" asp-action="Index"><i class="fas fa-warehouse"></i>Скла |
| | | 27 | | <a asp-area="Studio2" asp-controller="Manufacturers" asp-action="Index"><i class="fas fa-tag"></i>Произв |
| | | 28 | | <a asp-area="Studio2" asp-controller="ApiSupplierRequestAudit" asp-action="Index"><i class="fas fa-clipb |
| | | 29 | | <a asp-area="Studio2" asp-controller="FidSupplierLink" asp-action="Index"><i class="fas fa-link"></i>Свя |
| | | 30 | | </div> |
| | | 31 | | </div> |
| | | 32 | | <div class="d-flex flex-wrap gap-2 align-items-center"> |
| | | 33 | | <div class="bsx-dir-stats"> |
| | 0 | 34 | | <span class="bsx-dir-stat">Всего <b>@Model.Count</b></span> |
| | 0 | 35 | | <span class="bsx-dir-stat">С API <b>@Model.Count(r => r.ApiProviders.Count > 0)</b></span> |
| | 0 | 36 | | @if (Model.Any(r => r.Counterparty.State == "missing")) |
| | | 37 | | { |
| | 0 | 38 | | <span class="bsx-dir-stat bsx-dir-stat--warn" title="ИНН задан, но контрагент в 1С не найден">Нет в |
| | | 39 | | } |
| | | 40 | | </div> |
| | | 41 | | <a asp-action="Create" class="btn btn-primary btn-sm"><i class="fas fa-plus me-1"></i>Создать</a> |
| | | 42 | | </div> |
| | | 43 | | </div> |
| | | 44 | | |
| | | 45 | | <div class="sp-api-suppliers mb-3"> |
| | 0 | 46 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("apiSup", "Поставщики по API", "fa-p |
| | 0 | 47 | | "Прямые интеграции через API. Статус проверяется при открытии страницы; двойной клик — консоль поставщика.") |
| | | 48 | | <a asp-area="Studio2" asp-controller="FidSupplierLink" asp-action="Index" class="sp-api-fid-link"><i class="fas |
| | | 49 | | </div> |
| | | 50 | | |
| | 0 | 51 | | @if (TempData["ErrorMessage"] is string errorMessage) |
| | | 52 | | { |
| | | 53 | | <div class="alert alert-danger alert-dismissible fade show" role="alert"> |
| | 0 | 54 | | <i class="fas fa-exclamation-triangle me-2"></i>@errorMessage |
| | | 55 | | <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> |
| | | 56 | | </div> |
| | | 57 | | } |
| | | 58 | | |
| | 0 | 59 | | @if (TempData["SuccessMessage"] is string successMessage) |
| | | 60 | | { |
| | | 61 | | <div class="alert alert-success alert-dismissible fade show" role="alert"> |
| | 0 | 62 | | <i class="fas fa-check-circle me-2"></i>@successMessage |
| | | 63 | | <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> |
| | | 64 | | </div> |
| | | 65 | | } |
| | | 66 | | |
| | 0 | 67 | | @await Html.PartialAsync("_BsxDirectoryGrid", new BsxDirectoryGridViewModel("supplier", "Список поставщиков", "fa-li |
| | 0 | 68 | | "Клик по ячейке — правка, «Сохранить» в строке сохраняет всю строку. Двойной клик по названию поставщика — полна |
| | | 69 | | </div> |
| | | 70 | | |
| | | 71 | | @section Scripts { |
| | | 72 | | <script src="~/js/Studio2/Supplier/index.js" asp-append-version="true"></script> |
| | | 73 | | <script src="~/js/Studio2/Grid/bsx-directory.js" asp-append-version="true"></script> |
| | | 74 | | <script> |
| | | 75 | | (function () { |
| | | 76 | | const D = window.BsxDirectory; |
| | | 77 | | const esc = BsxGrid.esc; |
| | 0 | 78 | | const editUrl = id => '@Url.Action("Edit", "Supplier", new { area = "Studio2" })/' + id; |
| | | 79 | | const CURRENCIES = ['RUB', 'USD', 'EUR', 'BYN']; |
| | | 80 | | const CURRENCY_LABELS = { NON: '—' }; |
| | | 81 | | const money = p => p.value == null || p.value === '' ? '—' : Number(p.value).toLocaleString('ru-RU', { minimumFr |
| | | 82 | | const text = p => p.value || '—'; |
| | | 83 | | const edit = (def) => Object.assign({ editable: true }, def); |
| | | 84 | | // Десятичные поля биндятся в культуре сервера — отправляем с её разделителем. |
| | 0 | 85 | | const decimalSeparator = '@System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator'; |
| | | 86 | | const dec = v => v == null || v === '' ? '' : String(v).replace('.', decimalSeparator); |
| | | 87 | | |
| | | 88 | | function logos(list) { |
| | | 89 | | if (!list || !list.length) return ''; |
| | | 90 | | return '<span class="bsx-logo-row">' + list.map(x => |
| | | 91 | | `<span class="bsx-logo" title="Подключён API-поставщик: ${esc(x.name)}">${x.logoUrl ? `<img src="${esc(x |
| | | 92 | | } |
| | | 93 | | |
| | | 94 | | D.grid({ |
| | | 95 | | key: 'dirSupplier', |
| | | 96 | | prefix: 'supplier', |
| | 0 | 97 | | rows: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model, jsonOptions)), |
| | | 98 | | rowUrl: r => editUrl(r.id), |
| | | 99 | | onSave: r => D.submit(editUrl(r.id), { |
| | | 100 | | Id: r.id, Name: r.name, CostFactor: dec(r.costFactor), CurrencyCode: r.currencyCode, |
| | | 101 | | WeightFactor: dec(r.weightFactor), WeightFactorCurrencyCode: r.weightFactorCurrencyCode, |
| | | 102 | | CsvUrl: r.csvUrl ?? '', Site: r.site ?? '', AdditionalTerm: r.additionalTerm ?? '', |
| | | 103 | | ExpectedDeliveryHours: r.expectedDeliveryHours ?? '', INNCode: r.innCode ?? '', |
| | | 104 | | IsVatApplicable: r.isVatApplicable, IsReSeller: r.isReSeller |
| | | 105 | | }), |
| | | 106 | | columnDefs: [ |
| | | 107 | | edit({ field: 'name', headerName: 'Поставщик', pinned: 'left', width: 260, sort: 'asc', |
| | | 108 | | cellRenderer: p => `<span class="text-truncate">${esc(p.value || '—')}</span>${logos(p.data.apiProvide |
| | | 109 | | D.setCol({ colId: 'api', headerName: 'API', headerTooltip: 'Подключён ли API-поставщик', width: 100, hid |
| | | 110 | | valueGetter: p => p.data.apiProviders.length > 0, filterParams: { labels: { 'true': 'Есть', 'false': ' |
| | | 111 | | cellRenderer: p => p.value ? D.tone('API', 'accent', 'fa-plug') : '' }), |
| | | 112 | | D.numCol(edit({ field: 'costFactor', headerName: 'Коэф.', headerTooltip: 'Коэффициент на закупку', width |
| | | 113 | | cellEditor: 'agNumberCellEditor', valueFormatter: money })), |
| | | 114 | | D.setCol(edit({ field: 'currencyCode', headerName: 'Валюта', width: 105, filterParams: { labels: CURRENC |
| | | 115 | | cellEditor: 'agSelectCellEditor', cellEditorParams: { values: CURRENCIES }, valueFormatter: p => CURRE |
| | | 116 | | D.numCol(edit({ field: 'weightFactor', headerName: '₽/кг', headerTooltip: 'Стоимость доставки за кг — в |
| | | 117 | | cellEditor: 'agNumberCellEditor', valueFormatter: money })), |
| | | 118 | | D.setCol(edit({ field: 'weightFactorCurrencyCode', headerName: 'Вал. кг', width: 105, filterParams: { la |
| | | 119 | | cellEditor: 'agSelectCellEditor', cellEditorParams: { values: CURRENCIES }, valueFormatter: p => CURRE |
| | | 120 | | edit({ field: 'csvUrl', headerName: 'CSV', headerTooltip: 'CSV URL остатков', width: 200, valueFormatter |
| | | 121 | | edit({ field: 'site', headerName: 'Сайт', width: 170, valueFormatter: text, tooltipField: 'site' }), |
| | | 122 | | D.numCol(edit({ field: 'additionalTerm', headerName: 'Срок', headerTooltip: 'Дополнительный срок поставк |
| | | 123 | | cellEditor: 'agNumberCellEditor', valueFormatter: text })), |
| | | 124 | | D.numCol(edit({ field: 'expectedDeliveryHours', headerName: 'SLA, ч', headerTooltip: 'Норматив прихода д |
| | | 125 | | cellEditor: 'agNumberCellEditor', cellEditorParams: { min: 1 }, valueFormatter: text })), |
| | | 126 | | edit({ field: 'innCode', headerName: 'ИНН', headerTooltip: 'Контрагент 1С', width: 140, valueFormatter: |
| | | 127 | | D.boolCol(edit({ field: 'isVatApplicable', headerName: 'НДС', headerTooltip: 'Облагается НДС', width: 95 |
| | | 128 | | D.boolCol(edit({ field: 'isReSeller', headerName: 'Реал.', headerTooltip: 'Реализатор', width: 95, cellE |
| | | 129 | | D.setCol({ colId: 'counterparty', headerName: '1С', headerTooltip: 'Контрагент 1С по ИНН', flex: 1, minW |
| | | 130 | | valueGetter: p => p.data.counterparty.state === 'ok' ? p.data.counterparty.name : p.data.counterparty. |
| | | 131 | | filterParams: { labels: { '': '(ИНН не задан)' } }, |
| | | 132 | | tooltipValueGetter: p => p.data.counterparty.fullName || '', |
| | | 133 | | cellRenderer: p => { |
| | | 134 | | const c = p.data.counterparty; |
| | | 135 | | if (c.state === 'ok') return `<span class="bsx-tone-text-accent text-truncate"><i class="fas fa-ch |
| | | 136 | | if (c.state === 'missing') return `<span class="bsx-tone-text-danger" title="ИНН ${esc(p.data.innC |
| | | 137 | | return '<span class="bsx-muted">—</span>'; |
| | | 138 | | } }), |
| | | 139 | | D.actionsCol(r => [{ icon: 'fa-pencil-alt', title: 'Изменить', href: editUrl(r.id) }], { width: 160 }) |
| | | 140 | | ] |
| | | 141 | | }); |
| | | 142 | | })(); |
| | | 143 | | </script> |
| | | 144 | | } |