< Summary

Information
Class: AspNetCoreGeneratedDocument.Areas_Studio2_Views_WarehouseStock_MovementMap
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/WarehouseStock/MovementMap.cshtml
Line coverage
0%
Covered lines: 0
Uncovered lines: 10
Coverable lines: 10
Total lines: 271
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 18
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ExecuteAsync()0%210140%
<ExecuteAsync()0%2040%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Views/WarehouseStock/MovementMap.cshtml

#LineLine coverage
 1@model string
 2@{
 03    var article = Model;
 04    ViewData["Title"] = string.IsNullOrEmpty(article) ? "Карта движений" : $"Карта движений: {article}";
 5}
 6
 7<style>
 8    .mm-skeleton { background: linear-gradient(90deg, #e9ecef 25%, #dee2e6 50%, #e9ecef 75%); background-size: 200% 100%
 9    @@keyframes mm-shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
 10    .mm-badge-receipt { background: #198754; color: #fff; }
 11    .mm-badge-expense { background: #dc3545; color: #fff; }
 12    .mm-badge-transfer { background: #0d6efd; color: #fff; }
 13    .mm-row-deleted td { opacity: .45; text-decoration: line-through; }
 14    .mm-row-unposted td { opacity: .7; font-style: italic; }
 15    .mm-arrow { color: #adb5bd; }
 16    .mm-wh-pill { font-size: .85rem; }
 17    #mm-table tbody tr:hover { background: #f8f9fa; }
 18    .mm-balance-chip { display: inline-block; background: #198754; color: #fff; border-radius: 6px; padding: 1px 8px; fo
 19    .mm-filter-btn.active { font-weight: 600; }
 20</style>
 21
 22<div class="container-fluid mt-3">
 23
 24    <div class="card mb-3">
 25        <div class="card-header d-flex align-items-center gap-2">
 26            <a href="javascript:history.back()" class="btn btn-sm btn-outline-secondary me-1"><i class="fas fa-arrow-lef
 27            <i class="fas fa-chart-line me-1"></i>
 28            <span>Карта движений по 1С</span>
 029            @if (!string.IsNullOrEmpty(article))
 30            {
 31                <span class="text-muted">·</span>
 032                <strong>@article</strong>
 33                <span class="text-muted fw-normal small" id="mm-nom-name"></span>
 34            }
 35        </div>
 36        <div class="card-body pb-2">
 37
 038            @if (string.IsNullOrEmpty(article))
 39            {
 40                <form method="get" asp-area="Studio2" asp-controller="WarehouseStock" asp-action="MovementMap" class="ro
 41                    <div class="col-auto">
 42                        <input type="text" name="article" class="form-control" placeholder="Артикул..." autofocus style=
 43                    </div>
 44                    <div class="col-auto">
 45                        <button class="btn btn-primary"><i class="fas fa-search me-1"></i>Найти</button>
 46                    </div>
 47                </form>
 48            }
 49            else
 50            {
 51                <div class="mb-2">
 52                    <span class="text-muted small me-2">Текущий остаток:</span>
 53                    <span id="mm-balance-line"><span class="mm-skeleton d-inline-block" style="height:22px;width:100px;v
 54                </div>
 55
 56                <!-- Сводка -->
 57                <div class="row g-2 mb-3" id="mm-stats-row">
 058                    @for (int i = 0; i < 4; i++) {
 59                        <div class="col-6 col-md-3"><div class="mm-skeleton" style="height:66px;"></div></div>
 60                    }
 61                </div>
 62
 63                <!-- Фильтры -->
 64                <div class="d-flex align-items-center gap-2 mb-2 flex-wrap">
 65                    <div class="btn-group btn-group-sm">
 66                        <button class="btn btn-outline-secondary mm-filter-btn active" data-kind="all">Все</button>
 67                        <button class="btn btn-outline-success mm-filter-btn" data-kind="receipt">Приход</button>
 68                        <button class="btn btn-outline-danger mm-filter-btn" data-kind="expense">Расход</button>
 69                        <button class="btn btn-outline-primary mm-filter-btn" data-kind="transfer">Перемещения</button>
 70                    </div>
 71                    <div class="form-check form-switch mb-0 ms-2">
 72                        <input class="form-check-input" type="checkbox" id="mm-hide-deleted">
 73                        <label class="form-check-label small" for="mm-hide-deleted">Скрыть удалённые</label>
 74                    </div>
 75                    <span class="ms-auto small text-muted" id="mm-count-label"></span>
 76                </div>
 77            }
 78        </div>
 79    </div>
 80
 081    @if (!string.IsNullOrEmpty(article))
 82    {
 83        <div class="card">
 84            <div class="table-responsive">
 85                <table class="table table-sm table-hover mb-0" id="mm-table">
 86                    <thead class="table-light">
 87                        <tr>
 88                            <th style="width:95px">Дата</th>
 89                            <th style="width:115px">Тип</th>
 90                            <th>Документ</th>
 91                            <th style="width:75px" class="text-end">Кол-во</th>
 92                            <th style="width:85px" class="text-end">Цена</th>
 93                            <th>Склад</th>
 94                            <th style="width:80px" class="text-end">Остаток</th>
 95                        </tr>
 96                    </thead>
 97                    <tbody id="mm-tbody">
 98                        <tr><td colspan="7" class="p-3">
 99                            <div class="d-flex flex-column gap-2">
 0100                                @for (int i = 0; i < 6; i++) { <div class="mm-skeleton" style="height:30px;"></div> }
 101                            </div>
 102                        </td></tr>
 103                    </tbody>
 104                </table>
 105            </div>
 106        </div>
 107
 108        <div id="mm-error" class="alert alert-danger mt-3 d-none"></div>
 109    }
 110</div>
 111
 112@section Scripts {
 113<script>
 114(function () {
 0115    const article = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(article));
 0116    const accessKey = @Html.Raw(System.Text.Json.JsonSerializer.Serialize(ViewBag.AccessKey as string ?? ""));
 117    if (!article) return;
 118
 119    function accessKeyQs() {
 120        return accessKey ? '&accessKey=' + encodeURIComponent(accessKey) : '';
 121    }
 122
 123    let allMovements = [];
 124    let activeKind = 'all';
 125    let hideDeleted = false;
 126
 127    function fmtDate(s) { const [y,m,d]=s.split('-'); return `${d}.${m}.${y}`; }
 128    function fmtQty(n) { return (+n).toLocaleString('ru-RU',{maximumFractionDigits:3}); }
 129    function fmtPrice(n) { return n>0 ? (+n).toLocaleString('ru-RU',{minimumFractionDigits:2,maximumFractionDigits:2}) :
 130
 131    function kindBadge(kind) {
 132        const m = { receipt:['mm-badge-receipt','<i class="fas fa-arrow-down me-1"></i>Приход'], expense:['mm-badge-expe
 133        const [cls,html] = m[kind]||['bg-secondary text-white',kind];
 134        return `<span class="badge ${cls}">${html}</span>`;
 135    }
 136
 137    function warehouseCell(from, to, kind) {
 138        if (kind==='receipt') return `<span class="mm-wh-pill">${to||'—'}</span>`;
 139        if (kind==='expense') return `<span class="mm-wh-pill">${from||'—'}</span>`;
 140        return `<span class="mm-wh-pill">${from||'?'}</span><span class="mm-arrow mx-1">→</span><span class="mm-wh-pill"
 141    }
 142
 143    function renderStats(s, balance) {
 144        const chips = balance.map(b=>`<span class="mm-balance-chip">${b.warehouse}: ${fmtQty(b.quantity)}</span>`).join(
 145        document.getElementById('mm-balance-line').innerHTML = chips || '<span class="text-muted">Нет остатков</span>';
 146
 147        document.getElementById('mm-stats-row').innerHTML = `
 148            <div class="col-6 col-md-3">
 149                <div class="card border-success h-100">
 150                    <div class="card-body py-2 px-3">
 151                        <div class="text-success fw-bold fs-5">+${fmtQty(s.receiptQty)}</div>
 152                        <div class="text-muted small">${s.receiptDocs} приход. накл.</div>
 153                    </div>
 154                </div>
 155            </div>
 156            <div class="col-6 col-md-3">
 157                <div class="card border-danger h-100">
 158                    <div class="card-body py-2 px-3">
 159                        <div class="text-danger fw-bold fs-5">−${fmtQty(s.expenseQty)}</div>
 160                        <div class="text-muted small">${s.expenseDocs} расход. накл.</div>
 161                    </div>
 162                </div>
 163            </div>
 164            <div class="col-6 col-md-3">
 165                <div class="card border-primary h-100">
 166                    <div class="card-body py-2 px-3">
 167                        <div class="text-primary fw-bold fs-5">↔${fmtQty(s.transferQty)}</div>
 168                        <div class="text-muted small">${s.transferDocs} перемещ.</div>
 169                    </div>
 170                </div>
 171            </div>
 172            <div class="col-6 col-md-3">
 173                <div class="card h-100">
 174                    <div class="card-body py-2 px-3">
 175                        <div class="fw-bold fs-5">${fmtQty(s.calculatedBalance)}</div>
 176                        <div class="text-muted small">расчётный остаток</div>
 177                    </div>
 178                </div>
 179            </div>`;
 180    }
 181
 182    function renderTable() {
 183        const filtered = allMovements.filter(m => {
 184            if (activeKind !== 'all' && m.kind !== activeKind) return false;
 185            if (hideDeleted && m.isDeleted) return false;
 186            return true;
 187        });
 188
 189        document.getElementById('mm-count-label').textContent = `${filtered.length} записей`;
 190
 191        let bal = 0;
 192        const balMap = new Map();
 193        for (const m of allMovements) {
 194            if (!m.isDeleted) {
 195                if (m.kind==='receipt') bal += m.quantity;
 196                else if (m.kind==='expense') bal -= m.quantity;
 197            }
 198            balMap.set(m, bal);
 199        }
 200
 201        const rows = filtered.map(m => {
 202            const cls = m.isDeleted ? 'mm-row-deleted' : (!m.isPosted ? 'mm-row-unposted' : '');
 203            const b = balMap.get(m);
 204            const balCell = (m.kind!=='transfer' && !m.isDeleted)
 205                ? `<span class="badge ${b>=0?'bg-success':'bg-danger'} bg-opacity-75">${fmtQty(b)}</span>`
 206                : '<span class="text-muted">—</span>';
 207            const marks = (m.isDeleted ? '<span class="badge bg-secondary ms-1 opacity-50" style="font-size:.7em">удалён
 208                        + (!m.isPosted&&!m.isDeleted ? '<span class="badge bg-warning text-dark ms-1" style="font-size:.
 209            const docLink = m.refKey
 210                ? `<a href="/Studio2/OneCDocument/Details?refKey=${m.refKey}${accessKeyQs()}" target="_blank" class="fw-
 211                : `<span class="fw-medium">#${m.docNumber}</span>`;
 212            return `<tr class="${cls}">
 213                <td class="text-muted small text-nowrap">${fmtDate(m.date)}</td>
 214                <td>${kindBadge(m.kind)}</td>
 215                <td>${docLink}${marks}</td>
 216                <td class="text-end fw-semibold">${m.kind==='receipt'?'+':m.kind==='expense'?'−':'↔'}${fmtQty(m.quantity
 217                <td class="text-end text-muted small">${fmtPrice(m.price)}</td>
 218                <td>${warehouseCell(m.warehouseFrom,m.warehouseTo,m.kind)}</td>
 219                <td class="text-end">${balCell}</td>
 220            </tr>`;
 221        }).join('');
 222
 223        document.getElementById('mm-tbody').innerHTML = rows || St2EmptyState.tableRow(7, { title: 'Движений пока нет', 
 224    }
 225
 226    async function load() {
 227        const feedback = OperationFeedback.begin({
 228            title: 'Загружаем движения склада',
 229            steps: ['Подготавливаем запрос', 'Получаем данные из 1С', 'Строим карту движений']
 230        });
 231        try {
 232            feedback.step(1);
 233            const resp = await PageLoadingBar.run(() => fetch(`/Studio2/WarehouseStock/movement-api?article=${encodeURIC
 234            const data = await resp.json();
 235            if (!data.found) {
 236                document.getElementById('mm-error').classList.remove('d-none');
 237                document.getElementById('mm-error').textContent = data.error || 'Артикул не найден в 1С';
 238                document.getElementById('mm-tbody').innerHTML = '';
 239                document.getElementById('mm-stats-row').innerHTML = '<div class="col-12 text-muted small">—</div>';
 240                document.getElementById('mm-balance-line').innerHTML = '<span class="text-muted">—</span>';
 241                return;
 242            }
 243            document.getElementById('mm-nom-name').textContent = data.name !== article ? `· ${data.name}` : '';
 244            feedback.step(2);
 245            renderStats(data.summary, data.currentBalance);
 246            allMovements = data.movements;
 247            renderTable();
 248        } catch (e) {
 249            document.getElementById('mm-error').classList.remove('d-none');
 250            document.getElementById('mm-error').textContent = 'Ошибка загрузки: ' + e.message;
 251        } finally {
 252            feedback.finish();
 253        }
 254    }
 255
 256    document.querySelectorAll('.mm-filter-btn').forEach(btn => {
 257        btn.addEventListener('click', () => {
 258            document.querySelectorAll('.mm-filter-btn').forEach(b => b.classList.remove('active'));
 259            btn.classList.add('active');
 260            activeKind = btn.dataset.kind;
 261            renderTable();
 262        });
 263    });
 264    document.getElementById('mm-hide-deleted').addEventListener('change', e => {
 265        hideDeleted = e.target.checked; renderTable();
 266    });
 267
 268    load();
 269})();
 270</script>
 271}

Methods/Properties

ExecuteAsync()
<ExecuteAsync()