Files
PriceForge/web/templates/vendor_mappings.html
Mikhail Chusavitin f48615e8a9 Modularize Go files, extract JS to static, implement competitor pricelists
Go refactoring:
- Split handlers/pricing.go (2446→291 lines) into 5 focused files
- Split services/stock_import.go (1334→~400 lines) into stock_mappings.go + stock_parse.go
- Split services/sync/service.go (1290→~250 lines) into 3 files

JS extraction:
- Move all inline <script> blocks to web/static/js/ (6 files)
- Templates reduced: admin_pricing 2873→521, lot 1531→304, vendor_mappings 1063→169, etc.

Competitor pricelists (migrations 033-039):
- qt_competitors + partnumber_log_competitors tables
- Excel import with column mapping, dedup, bulk insert
- p/n→lot resolution via weighted_median, discount applied
- Unmapped p/ns written to qt_vendor_partnumber_seen
- Quote counts (unique/total) shown on /admin/competitors
- price_method="weighted_median", price_period_days=0 stored explicitly

Fix price_method/price_period_days for warehouse items:
- warehouse: weighted_avg, period=0
- competitor: weighted_median, period=0
- Removes misleading DB defaults (was: median/90)

Update bible: architecture.md, pricelist.md, history.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:44:10 +03:00

170 lines
10 KiB
HTML

{{define "title"}}Vendor mappings - PriceForge{{end}}
{{define "content"}}
<div class="space-y-4">
<h1 class="text-2xl font-bold">Глобальные сопоставления Vendor Partnumbers</h1>
<!-- Partnumber Book Snapshots -->
<div class="bg-white rounded-lg shadow p-4 space-y-3">
<div class="flex items-center justify-between">
<h2 class="text-base font-semibold text-gray-800">Снимки сопоставлений (Partnumber Books)</h2>
<button onclick="createPartnumberBook()" id="btn-create-book" class="px-3 py-2 bg-orange-600 text-white rounded hover:bg-orange-700 text-sm">
Создать снапшот сопоставлений
</button>
</div>
<!-- Progress bar for book creation -->
<div id="book-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="book-progress-text" class="font-medium">Создание снимка...</span>
<span id="book-progress-percent" class="font-bold">0%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div id="book-progress-bar" class="bg-orange-600 h-3 rounded-full transition-all duration-300" style="width:0%"></div>
</div>
</div>
<div id="book-error" class="hidden 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-right text-xs font-medium text-gray-500 uppercase">Строк</th>
<th class="px-4 py-2 text-center text-xs font-medium text-gray-500 uppercase">Статус</th>
</tr>
</thead>
<tbody id="book-list" class="bg-white divide-y divide-gray-200">
<tr><td colspan="5" class="px-4 py-3 text-sm text-gray-500">Загрузка...</td></tr>
</tbody>
</table>
</div>
<p class="text-xs text-gray-400">Политика хранения: 7 ежедневных · 5 еженедельных · 12 ежемесячных · 10 ежегодных. Старые снимки удаляются автоматически при создании нового.</p>
</div>
<div class="bg-white rounded-lg shadow p-4 space-y-4">
<div class="flex gap-3 items-center">
<input id="vm-search" type="text" placeholder="Поиск по vendor / partnumber / LOT / описанию" class="flex-1 px-3 py-2 border rounded">
<label class="inline-flex items-center gap-2 text-sm text-gray-700">
<input id="vm-unmapped" type="checkbox" class="rounded border-gray-300">
<span>Партномера без сопоставления</span>
</label>
<label class="inline-flex items-center gap-2 text-sm text-gray-700">
<input id="vm-ignored" type="checkbox" class="rounded border-gray-300">
<span>Игнорируемые</span>
</label>
<button onclick="openVendorMappingModal()" class="px-3 py-2 bg-orange-600 text-white rounded hover:bg-orange-700">Создать</button>
<button onclick="openVendorMappingsImportModal()" class="px-3 py-2 border rounded hover:bg-gray-50">CSV operations</button>
<button onclick="loadVendorMappings(1)" class="px-3 py-2 border rounded hover:bg-gray-50">Обновить</button>
<input id="vm-import-file" type="file" accept=".csv,text/csv" class="hidden">
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Vendor</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">LOT</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">pn</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase"></th>
</tr>
</thead>
<tbody id="vm-body" class="bg-white divide-y divide-gray-200">
<tr><td colspan="5" class="px-4 py-3 text-sm text-gray-500">Загрузка...</td></tr>
</tbody>
</table>
</div>
<div id="vm-pagination" class="flex justify-center gap-2"></div>
</div>
</div>
<div id="vm-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-xl w-full mx-4 space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-xl font-bold">CSV operations</h2>
<button onclick="closeVendorMappingsImportModal()" class="text-gray-400 hover:text-gray-600 text-xl">&times;</button>
</div>
<div class="text-sm text-gray-600 space-y-1">
<p>Формат CSV: `;` (Excel RU), UTF-8 (BOM поддерживается).</p>
<p>Колонки: `vendor;partnumber;lot_name;description;ignore`.</p>
<p>Если `ignore` заполнен и `lot_name` пустой, строка будет помечена как игнорируемая.</p>
</div>
<div class="border rounded p-3 bg-gray-50 space-y-2">
<div class="flex items-center gap-2">
<button onclick="triggerVendorMappingsCsvImport()" class="px-3 py-2 border rounded hover:bg-white">Загрузить</button>
<button onclick="downloadVendorMappingsCsvExample()" class="px-3 py-2 border rounded hover:bg-white">Пример</button>
<button onclick="exportUnmappedVendorMappingsCsv()" class="px-3 py-2 border rounded hover:bg-white">Скачать новые</button>
</div>
<div id="vm-import-file-name" class="text-sm text-gray-500">Файл не выбран</div>
</div>
<div id="vm-import-modal-error" class="hidden p-2 rounded bg-red-50 text-red-700 text-sm"></div>
<div class="flex justify-end gap-2">
<button onclick="closeVendorMappingsImportModal()" class="px-3 py-2 border rounded hover:bg-gray-50">Отмена</button>
<button id="vm-import-submit-btn" onclick="importVendorMappingsCsv()" class="px-3 py-2 bg-orange-600 text-white rounded hover:bg-orange-700 disabled:opacity-50" disabled>Импортировать</button>
</div>
</div>
</div>
<div id="vm-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-3xl w-full mx-4 space-y-4">
<div class="flex justify-between items-center">
<h2 class="text-xl font-bold">Сопоставление</h2>
<button onclick="closeVendorMappingModal()" class="text-gray-400 hover:text-gray-600 text-xl">&times;</button>
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">Vendor</label>
<input id="vm-modal-vendor" type="text" class="w-full px-3 py-2 border rounded">
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">Описание</label>
<textarea id="vm-modal-description" rows="7" class="w-full px-3 py-2 border rounded resize-y"></textarea>
</div>
<div class="border rounded bg-gray-50">
<div class="flex items-center justify-between px-3 py-2 border-b bg-white rounded-t">
<h3 class="text-sm font-semibold">Состав PN</h3>
<button type="button" onclick="addVendorMappingItemRow()" class="px-3 py-1.5 border rounded hover:bg-gray-50 text-sm">Добавить строку</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-gray-500 bg-gray-50">
<th class="px-3 py-2 font-medium uppercase text-xs">p/n</th>
<th class="px-3 py-2 font-medium uppercase text-xs w-28">Кол-во</th>
<th class="px-3 py-2 font-medium uppercase text-xs">LOT</th>
<th class="px-3 py-2 font-medium uppercase text-xs w-28"></th>
</tr>
</thead>
<tbody id="vm-items-body"></tbody>
</table>
</div>
</div>
<div id="vm-lot-options" class="hidden fixed z-[70] max-h-72 overflow-auto rounded-lg border border-gray-200 bg-white shadow-xl"></div>
<div id="vm-modal-error" class="hidden p-2 rounded bg-red-50 text-red-700 text-sm"></div>
<div class="flex justify-between">
<div class="space-x-2">
<button id="vm-btn-ignore-toggle" onclick="toggleIgnoreVendorMapping()" class="px-3 py-2 border rounded hover:bg-gray-50">Игнорировать</button>
<button id="vm-btn-delete" onclick="deleteVendorMapping()" class="px-3 py-2 border border-red-300 text-red-700 rounded hover:bg-red-50">Удалить</button>
</div>
<div class="space-x-2">
<button onclick="closeVendorMappingModal()" class="px-3 py-2 border rounded hover:bg-gray-50">Отмена</button>
<button onclick="saveVendorMapping()" class="px-3 py-2 bg-orange-600 text-white rounded hover:bg-orange-700">Сохранить</button>
</div>
</div>
</div>
</div>
<script src="/static/js/vendor_mappings.js"></script>
{{end}}
{{template "base" .}}