Files
PriceForge/web/templates/competitors.html
Mikhail Chusavitin c53c484bde Replace competitor discount with price_uplift; stock pricelist detail UI
- Drop `expected_discount_pct`, add `price_uplift DECIMAL(8,4) DEFAULT 1.3`
  to `qt_competitors` (migration 040); formula: effective_price = price / uplift
- Extend `LoadLotMetrics` to return per-PN qty map (`pnQtysByLot`)
- Add virtual fields `CompetitorNames`, `PriceSpreadPct`, `PartnumberQtys`
  to `PricelistItem`; populate via `enrichWarehouseItems` / `enrichCompetitorItems`
- Competitor quotes filtered to qty > 0 before lot resolution
- New "stock layout" on pricelist detail page for warehouse/competitor:
  Partnumbers column (PN + qty, only qty>0), Поставщик column, no Настройки/Доступно
- Spread badge ±N% shown next to price for competitor rows
- Bible updated: pricelist.md, history.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 12:58:41 +03:00

188 lines
13 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "title"}}Конкуренты - PriceForge{{end}}
{{define "content"}}
<div class="space-y-4">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold">Конкуренты</h1>
<div class="flex gap-2">
<button onclick="openImportModal()" class="px-3 py-2 border rounded hover:bg-gray-50 text-sm">Импорт прайслиста</button>
<button onclick="openCompetitorModal()" class="px-3 py-2 bg-orange-600 text-white rounded hover:bg-orange-700 text-sm">Добавить конкурента</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div id="comp-error" class="hidden mb-3 p-2 rounded bg-red-50 text-red-700 text-sm"></div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Код</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Название</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Базис</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Валюта</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase" title="Делитель цены: цена_без_НДС = цена / аплифт">Аплифт</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase" title="Уникальных партномеров в системе">Уник. p/n</th>
<th class="px-4 py-2 text-right text-xs font-medium text-gray-500 uppercase" title="Всего котировок исторически">Всего</th>
<th class="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase">Активен</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Действия</th>
</tr>
</thead>
<tbody id="comp-body" class="bg-white divide-y divide-gray-200">
<tr><td colspan="9" class="px-4 py-3 text-sm text-gray-500">Загрузка...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Competitor Create/Edit Modal -->
<div id="comp-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-full mx-4 max-h-[92vh] overflow-y-auto" style="max-width:min(95vw,1200px);">
<div class="flex justify-between items-center mb-4">
<h2 id="comp-modal-title" class="text-xl font-bold">Добавить конкурента</h2>
<button onclick="closeCompetitorModal()" class="text-gray-400 hover:text-gray-600 text-xl">&times;</button>
</div>
<div id="comp-modal-error" class="hidden mb-3 p-2 rounded bg-red-50 text-red-700 text-sm"></div>
<form id="comp-form" class="space-y-4" onsubmit="saveCompetitor(event)">
<input type="hidden" id="comp-id" value="">
<!-- Basic info -->
<div class="grid grid-cols-2 gap-3">
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Название <span class="text-red-500">*</span></label>
<input id="comp-name" type="text" required class="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div id="comp-code-row">
<label class="block text-sm font-medium text-gray-700 mb-1">Код <span class="text-red-500">*</span></label>
<input id="comp-code" type="text" required class="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500" placeholder="например: acme">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Базис поставки</label>
<input id="comp-delivery" type="text" class="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500" placeholder="DDP">
</div>
<div class="col-span-2 grid grid-cols-2 gap-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1" title="Делитель: цена_без_НДС = ценаонкурента / аплифт">Аплифт (делитель цены)</label>
<input id="comp-uplift" type="number" step="0.0001" min="0.0001" class="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500" value="1.3" placeholder="1.3">
</div>
</div>
</div>
<!-- Column mapping -->
<div class="border rounded p-4 bg-gray-50 space-y-3">
<p class="text-sm font-medium text-gray-700">Маппинг колонок Excel</p>
<div class="flex items-end gap-3 flex-wrap">
<div>
<label class="block text-xs text-gray-500 mb-1">Лист (номер)</label>
<input id="comp-sheet" type="number" min="1" value="1" class="w-20 px-2 py-1.5 border rounded text-sm focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div class="flex items-center gap-2">
<input id="comp-sample-file" type="file" accept=".xlsx,.xls" class="hidden" onchange="onSampleFileUpload(this)">
<button type="button" onclick="document.getElementById('comp-sample-file').click()"
class="text-xs px-3 py-1.5 border rounded hover:bg-white text-gray-700 flex items-center gap-1.5 bg-white">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/></svg>
Загрузить пример файла
</button>
<span id="comp-sample-filename" class="text-xs text-gray-400 italic"></span>
<span id="comp-sample-loading" class="hidden text-xs text-orange-600">Разбор...</span>
</div>
</div>
<!-- Current mapping summary (shown in edit mode before file upload) -->
<div id="comp-mapping-summary" class="hidden text-xs bg-white border rounded p-2 text-gray-600 leading-relaxed"></div>
<!-- Preview: slider + table (shown after file upload) -->
<div id="comp-preview" class="hidden">
<div class="flex gap-3">
<!-- Vertical header-row slider -->
<div class="flex flex-col items-center gap-1 select-none" style="min-width:52px;">
<span class="text-xs text-gray-500 text-center leading-tight">Строка<br>заголовка</span>
<span id="comp-hrow-val" class="text-sm font-bold text-orange-600">1</span>
<input type="range" id="comp-hrow-slider" min="1" max="30" value="1"
orient="vertical"
style="-webkit-appearance:slider-vertical;appearance:slider-vertical;writing-mode:vertical-lr;height:160px;width:20px;cursor:pointer;accent-color:#ea580c;"
oninput="onHeaderRowSlider(this.value)">
<span class="text-xs text-gray-400" id="comp-hrow-max">/ 30</span>
</div>
<!-- Scrollable table -->
<div class="flex-1 min-w-0">
<div class="overflow-x-auto border rounded bg-white">
<table id="comp-preview-table" class="text-xs border-collapse" style="min-width:100%;">
<thead id="comp-preview-thead"></thead>
<tbody id="comp-preview-body"></tbody>
</table>
</div>
<div class="flex items-center justify-between mt-1">
<span id="comp-preview-count" class="text-xs text-gray-400"></span>
<button type="button" id="comp-load-more-btn" onclick="loadMorePreviewRows()"
class="hidden text-xs text-blue-600 hover:underline">
Загрузить ещё 10 строк
</button>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" onclick="closeCompetitorModal()" class="px-4 py-2 border rounded hover:bg-gray-50">Отмена</button>
<button type="submit" id="comp-save-btn" class="px-4 py-2 bg-orange-600 text-white rounded hover:bg-orange-700">Сохранить</button>
</div>
</form>
</div>
</div>
<!-- Import Modal -->
<div id="import-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 max-w-lg w-full mx-4 space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-xl font-bold">Импорт прайслиста конкурента</h2>
<button onclick="closeImportModal()" class="text-gray-400 hover:text-gray-600 text-xl">&times;</button>
</div>
<div id="import-error" class="hidden p-2 rounded bg-red-50 text-red-700 text-sm"></div>
<div id="import-progress-container" class="hidden p-3 bg-orange-50 rounded border border-orange-200">
<div class="flex justify-between text-sm text-gray-700 mb-1">
<span id="import-progress-text" class="font-medium">Импорт...</span>
<span id="import-progress-percent" class="font-bold">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div id="import-progress-bar" class="bg-orange-600 h-3 rounded-full transition-all duration-300" style="width:0%"></div>
</div>
</div>
<form id="import-form" class="space-y-3" onsubmit="submitImport(event)">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Конкурент <span class="text-red-500">*</span></label>
<select id="import-competitor" required class="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500">
<option value="">Выберите конкурента...</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Дата котировки</label>
<input id="import-date" type="date" class="w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500">
</div>
<div id="import-rate-row" class="hidden">
<label class="block text-sm font-medium text-gray-700 mb-1">Курс к USD (для локальной валюты)</label>
<div class="flex items-center gap-2">
<input id="import-rate" type="number" step="0.0001" min="0.0001" placeholder="89.50" class="flex-1 px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-orange-500">
<span class="text-sm text-gray-400 whitespace-nowrap">ед. = 1 USD</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Файл Excel (.xlsx) <span class="text-red-500">*</span></label>
<input id="import-file" type="file" accept=".xlsx,.xls" required class="w-full px-3 py-2 border rounded">
</div>
<div class="flex justify-end gap-2 pt-2">
<button type="button" onclick="closeImportModal()" class="px-4 py-2 border rounded hover:bg-gray-50">Отмена</button>
<button type="submit" id="import-submit-btn" class="px-4 py-2 bg-orange-600 text-white rounded hover:bg-orange-700">Импортировать</button>
</div>
</form>
</div>
</div>
<script src="/static/js/competitors.js"></script>
{{end}}
{{template "base" .}}