Добавлен чекбокс «Складские цены» (по аналогии с «Аренда»), управляющий
показом складских цен во всех ценовых интерфейсах конфигурации, включая
CSV-экспорт. «Только наличие» перенесён из настроек цен конфигурации в
настройки проекта и активен только при включённых складских ценах.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Фронтенд не отправлял значение поля "Аплифт к estimate" в запрос
экспорта, из-за чего бэкенд всегда падал на дефолт 1.3. Кроме того,
applyDDPMarkup применял один и тот же коэффициент к Estimate, Склад
и Конкуренты — теперь Склад/Конкуренты всегда x1.3, а Estimate — по
пользовательскому аплифту.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
applyConfigTypeToTabs() тоже пересобирал ASSIGNED_CATEGORIES из всех
вкладок, включая "other", и вызывался прямо внутри applyServerSettings —
из-за этого предыдущий фикс (два других места) перезатирался сразу же,
и SYS всё ещё не появлялся во вкладке «Прочее».
Свёл все 4 места пересчёта ASSIGNED_CATEGORIES в одну функцию
computeAssignedCategories(), которая по определению исключает "other" —
это устраняет класс бага целиком, а не по одному вхождению.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ASSIGNED_CATEGORIES пересчитывался из всех вкладок TAB_CONFIG уже после
того, как неизвестные категории попадали в TAB_CONFIG.other — из-за
этого они сами себя исключали из фильтра "not assigned" на вкладке
«Прочее». Баг был в двух местах (loadCategoriesFromAPI и
applyServerSettings), второе не проявлялось только потому, что синк
qt_settings с сервера никогда не был подключён к воркеру.
Заодно подключил Service.SyncQtSettings к фоновому синку (был мёртвым
кодом), чтобы tab_config/config_types и т.п. реально доезжали с сервера
до локального кеша.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Красный цвет суммы означает, что она частично посчитана по ценам-заглушкам
из прайслиста world. Если подстановок не было, сумма показывается обычным
цветом; попап с пояснением остаётся в обоих случаях.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Колонка «PN code» в таблице импорта BOM: HPE-спецификации хранят партномер
в двух столбцах, канонический вид — P52534-B21#B19. Склейка попадает
в vendor_partnumber и сравнивается с книгой партномеров как есть; при
загрузке разбирается обратно на две колонки. Схему БД менять не пришлось.
Список допустимых LOT собирается из world ∪ estimate (componentUniverse),
а не только из estimate. Прежняя область видимости молча теряла
lot_mappings: резолвер сопоставлял PN по книге, которая про прайслисты
не знает, а фронт отбрасывал LOT, которого нет в estimate — при этом
в корзину он всё равно попадал. World-only позиции отдают price_quality 0.
Кнопка «Пересопоставить» — перерешать BOM по актуальной книге, так как
при открытии конфигурации сопоставления остаются замороженными.
Итоги на вкладке «Ценообразование»: звёздочки убраны (переносились
на новую строку), все три суммы красные, при наведении — попап с долей
цен из прайслиста WORLD. Колонка «PN вендора» больше не переносится.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
price_quality теперь показывается не только на вкладке Base (renderTab),
но и на остальных вкладках конфигуратора (renderMultiSelectTab,
renderMultiSelectTabWithSections) — раньше индикатор был виден только
в выпадающем списке поиска. Колонка-индикатор везде без текстового
заголовка (только тултип), включая колонку «Качество» в прайслисте.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
price_quality теперь синхронизируется не только в прайслист, но и в
LocalComponent/services.ComponentView (/api/components) — виден в
выпадающем списке поиска (цветная точка), в таблице конфигуратора
(левая колонка-пиктограмма) и на вкладке «Ценообразование» (подсветка
строки). Цветовая шкала вынесена в один модуль web/static/price-quality.js
(подключён в base.html), чтобы не дублировать расчёт цвета по шаблонам.
Шкала — градиент 0 (красный) → 5 (жёлтый) → 9 (зелёный).
Экспорт цен (internal/services/export.go): resolveLotDescriptions был
заглушкой, всегда возвращавшей пустую карту — строки BOM/не-BOM в
экспорте всегда оставались без описания LOT. Заменён на реальный
запрос к local_pricelist_items текущего выбранного прайслиста
(LocalDB.GetLocalDescriptionsForLots), по аналогии с уже существующим
GetLocalLotCategoriesByServerPricelistID.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Вкладка «Аренда»: добавлены колонки Estimate и Цена продажи (+ итоги),
чекбокс «БУ» перенесён первым слева, таблица переведена на тот же
разметку/классы, что и таблицы вкладки «Ценообразование».
Прайслисты: local_pricelist_items получает lot_description (синхронизируется
из qt_pricelist_items.lot_description) вместо устаревших available_qty/
partnumbers, которые нигде не заполнялись.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Новая вкладка «Аренда» в конфигураторе (доступна для проектов с флагом
rental_enabled): расчёт по методичке "Регламент расчёта стоимости
платного тестирования и аренды GPU-серверов" — Разовый + Еженедельный
платёж по каждому компоненту (New/БУ чекбоксом), с аплифтом к Estimate.
Заодно: поддержка (support_code) в Base-вкладке теперь добавляется как
обычный LOT в спеку (SVC_{срок}y{уровень}_{платформа}) через
autocomplete-пикер вместо выпадающего списка — партномер собирается
тем же lot_name-based механизмом, что и для GPU/CPU/памяти, справочная
цена по регламенту техподдержки хранится в qt_settings.support_pricing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Там, где для LOT нет цены в estimate/warehouse/competitor, подставляется
цена из world-прайслиста. Такие ячейки в таблицах «Цена покупки»/«Цена продажи»
подсвечиваются (amber), участвуют в «Итого» и убирают красную «*».
В CSV-экспорте добавлена колонка «Заглушка (world)» с перечнем столбцов,
где сработал фолбэк.
Добавлены Tx-версии GetLatestLocalPricelistBySource/GetLocalPricesForLots,
чтобы резолв прайслистов внутри транзакции не дедлочил single-connection пул.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Открытые конфигурации фиксируются в локальном SQLite (app_settings) и
передаются на сервер через qt_client_schema_state.open_config_uuids при
каждом цикле синхронизации. Списки конфигураций обогащаются полем viewers,
в таблицах отображается иконка глаза с подсказкой при наличии других
пользователей, открывших эту конфигурацию.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LOT из BOM-маппинга мог быть в смешанном регистре, а корзина — в каноничном
UPPERCASE, из-за чего позиции дублировались (в таблице «Цена покупки» и в
экспорте CSV).
- localdb.NormalizeLotMappings: единая каноничная нормализация LOT-маппингов
(UPPERCASE + схлопывание дублей с суммированием qty). Убраны две разошедшиеся
копии normalizeLotMappings (handlers и services — последняя только тримила,
что и было причиной бага в CSV).
- export.go: BOM-ветка использует общую функцию + канонизирует LOT корзины
для coverage/lookup. Удалена мёртвая computeMappingTotal.
- index.html (renderPricingTab): сопоставление/дедуп LOT через каноничный ключ
UPPERCASE; аксессоры _getRowBaseLot/_getRowAllocations возвращают канон.
- Добавлен регресс-тест TestNormalizeLotMappings_*.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ASSIGNED_CATEGORIES пересобирался из отфильтрованного tab.categories,
поэтому категории скрытые для текущего типа конфигурации переставали
считаться «назначенными» и уходили в Other. Теперь используется
tab._allCategories (полный статический список) — категория принадлежит
вкладке независимо от видимости в текущем режиме.
Также убрал лишний .toUpperCase() в updateTabVisibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. JS-конфигуратор: при загрузке сохранённой конфигурации item.category
всегда undefined (в config.items хранится только lot_name/quantity/unit_price).
Добавлено обогащение cart из allComponents после загрузки, все сравнения
категорий переведены на ciStr() вместо .toUpperCase(), исправлены все 4
точки построения ASSIGNED_CATEGORIES — устраняет TypeError и таб «Other»
показывал компоненты с известными категориями.
2. RepairPendingChanges: repair-функции теперь возвращают (bool, error);
attempts/last_error сбрасываются только при modified=true — устраняет
бесконечный retry когда ошибка на стороне сервера, а не локальных данных.
3. UpsertByUUID: сброс project.ID=0 перед INSERT … ON DUPLICATE KEY UPDATE,
чтобы конфликт шёл по уникальному uuid, а не по PK чужой строки —
устраняет «record not found» при разрешении изменений проекта.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SQLite-запросы по lot_name теперь используют UPPER(lot_name) IN/= для
совместимости с легаси-данными, синхронизированными до нормализации регистра
- Удалена таблица local_components и весь связанный код синхронизации;
источник данных для компонентов — local_pricelist_items
- Удалена функция getCategoryFromLotName из JS: категория берётся только
из прайслиста, без инференса из имени лота
- Регистронезависимые сравнения lot_name в JS (warehouse stock set,
addedLots, cartLots, allComponents.find, _bomLotValid)
- В support bundle добавлены: latest_pricelist_items.json, local.db,
autocomplete_lots.json для диагностики
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- При нажатии «обновить цены» создаётся ревизия текущего состояния
(«до обновления цен») через новый эндпоинт POST /api/configs/:uuid/snapshot,
затем saveConfig создаёт ревизию с новыми ценами
- Роут GET /:code → редирект на /projects/:uuid по коду опти (регистронезависимо)
- Валидация кода опти: только URL-безопасные символы [A-Za-z0-9._-]
(бэкенд + клиентская проверка + подсказка в форме)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces hardcoded JS category filters and config-type buttons with
server-pushed settings synced from qt_settings (MariaDB) → local_qt_settings (SQLite).
- new table local_qt_settings (AutoMigrate) — synced after component sync
- GET /api/configurator-settings returns config_types, tab_config,
always_visible_tabs, required_categories with hardcoded fallbacks
- new-config modal: type buttons rendered from server data (Сервер/СХД static fallback)
- configurator: TAB_CONFIG and category filter driven by server; required-category badge on tabs
- SyncQtSettings wired into SyncComponents and SyncAll handlers (non-fatal on old server)
- bible-local/server-contract-qt-settings.md — contract for server-side agent
- page titles: OFS → QFS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ценообразование показывало неверное количество для LOT-ов с bundle
(lot_qty_per_pn > 1) или устаревшим quantity_per_pn в vendor_spec.
Итог Estimate расходился с Estimate-табом.
Теперь qty берётся из корзины если LOT там присутствует; BOM-расчёт
(row.quantity × lot_qty_per_pn) остаётся fallback для ещё не применённых строк.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Сохранение:
- restoreAutosaveDraftIfAny теперь восстанавливает pricing_ui из notes драфта
- saveConfigOnExit привязан к pagehide и visibilitychange — цены сохраняются
на сервер при уходе со страницы без явного нажатия «Сохранить»
Экспорт CSV:
- exportPricingCSV передаёт manual_price (buy для FOB, sale для DDP)
- ProjectPricingExportOptions.ManualPrice *float64 — новое поле
- distributeManualPrice распределяет ручную цену пропорционально estimate
с коррекцией остатка на последней строке
- Колонка «Ручная цена» в CSV (заголовок, строки, итог конфига)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- filterAutocompleteBOM: LOT из текущего конфигуратора выводятся первыми
с разделителем «── прочие ──», остальные — по popularity_score
- qtyMismatch теперь сравнивает cartQty с pn_qty × lot_qty_per_pn во всех
трёх местах рендера BOM-таблицы; «8 LOT = 1 PN» больше не даёт ложного
жёлтого предупреждения
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Паста BOM на странице конфигурирования теперь распознаёт текстовый и
Inspur-форматы: вместо дублирования парсера на JS добавлен stateless
эндпоинт POST /api/vendor-spec/parse-text, который использует те же
детекторы и парсеры, что и импорт файла (KISS — один парсер на оба
входа). JS-копии _parseInspurBOMText/_isInspurBOMText удалены.
Заголовок конфигурации определяется по маркеру ", в составе:" с любым
префиксом ("Сервер X3" и "Вычислительный GPU сервер X3" → модель X3);
строки тримятся, пробел в начале не попадает в P/N; запятые и дефисы
внутри описания сохраняются (RAID0,1,10; 8-GPU-2304GB).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ExportConfigCSV и ExportConfigPricingCSV: GetByUUID → GetByUUIDNoAuth,
чтобы не падать с ErrConfigForbidden на конфигах с чужим OriginalUsername
- ConfigurationGetter: добавлен GetByUUIDNoAuth в интерфейс
- Шаблоны: toLocaleString('en-US') → 'ru-RU' во всех местах отображения цен;
процентные значения: .toFixed(1) → .toFixed(1).replace('.', ',')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Добавлен парсер для текстового формата Inspur (опциональный '|' в начале
строки, разделитель '*' перед количеством). На BOM-вкладке вставка такого
текста автоматически определяется и разбивается на колонки P/N + Qty без
ручного выбора типов. На бэкенде тот же формат поддерживается через
POST /api/projects/:uuid/vendor-import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Вынести sortConfigsByLine() — устранить дублирование sort.Slice
в ProjectToExportData и ProjectToPricingExportData
- Добавить ConfigToPricingExportData() и ExportConfigPricingCSV handler
- Зарегистрировать POST /api/configs/:uuid/export/pricing
- Заменить клиентский DOM-скрапинг exportPricingCSV() на fetch к новому
endpoint; артикул теперь включается через pricingConfigSummaryRow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Все вкладки (storage, pci, power, accessories, sw, other) теперь
используют редактируемый autocomplete-input для существующих позиций,
как на вкладке base; выбор заменяет позицию с сохранением количества
- LOT-поле в BOM-таблицах переведено на общий autocomplete dropdown
вместо datalist
- Кнопка ✕ в BOM снимает сопоставление вместо удаления строки
- Кнопка «Пересчитать эстимейт» переименована в «Перенести в эстимейт»
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add config_type field ("server"|"storage") to Configuration and LocalConfiguration
- Create modal: Сервер/СХД segmented control in configs.html and project_detail.html
- Configurator: ENC/DKC/CTL categories in Base tab, HIC section in PCI tab hidden for server configs
- Add SW tab (categories: SW) to configurator, visible only when components present
- TAB_CONFIG.pci: add HIC section for storage HIC adapters (separate from server HBA/NIC)
- Migration 029: ALTER TABLE qt_configurations ADD COLUMN config_type
- Fix: skip Error 1833 (Cannot change column used in FK) in GORM AutoMigrate
- Operator guide: docs/storage-components-guide.md with LOT naming rules and DE4000H catalog template
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ErrCannotRenameMainVariant; ProjectService.Update now returns
this error if the caller tries to change the Variant of a main
project (empty Variant) — ensures there is always exactly one main
- Handle ErrCannotRenameMainVariant in PUT /api/projects/:uuid with 400
- Set document.title dynamically from breadcrumb data:
- Configurator: "CODE / variant / Config name — QuoteForge"
- Project detail: "CODE / variant — QuoteForge"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove 'auto (latest active)' option from pricelist dropdowns; new
configs pre-select the first active pricelist instead
- Stop resetting stored pricelist_id to null when it is not in the
active list (deactivated pricelists are shown as inactive options)
- RefreshPricesNoAuth now accepts an optional pricelist_id; uses the
UI-selected pricelist, then the config's stored pricelist, then
latest as a last-resort fallback — no longer silently overwrites
the stored pricelist on every price refresh
- Same fix applied to RefreshPrices (with auth)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Split into two sections: Цена покупки and Цена продажи
- All price cells show unit price (per 1 pcs); totals only in footer
- Added note "Цены указаны за 1 шт." next to each table heading
- Buy table: Своя цена redistributes proportionally with green/red coloring vs estimate; footer shows % diff
- Sale table: configurable uplift (default 1.3) applied to estimate; Склад/Конкуренты fixed at ×1.3
- Footer Склад/Конкуренты marked red with asterisk tooltip when coverage is partial
- CSV export updated: all 8 columns, SPEC-BUY/SPEC-SALE suffix, no % annotation in totals
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Render competitor prices in Pricing tab (all three row branches)
- Add footer total accumulation for competitor column
- Deduplicate local_pricelist_items via migration + unique index
- Use ON CONFLICT DO NOTHING in SaveLocalPricelistItems to prevent duplicates on concurrent sync
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BOM paste: auto-detect columns by content (price, qty, PN, description);
handles $5,114.00 and European comma-decimal formats
- LOT input: HTML5 datalist rebuilt on each renderBOMTable from allComponents;
oninput updates data only (no re-render), onchange validates+resolves
- BOM persistence: PUT handler explicitly marshals VendorSpec to JSON string
(GORM Update does not reliably call driver.Valuer for custom types)
- BOM autosave after every resolveBOM() call
- Pricing tab: async renderPricingTab() calls /api/quote/price-levels for all
resolved LOTs directly — Estimate prices shown even before cart apply
- Unresolved PNs pushed to qt_vendor_partnumber_seen via POST
/api/sync/partnumber-seen (fire-and-forget from JS)
- sync.PushPartnumberSeen(): upsert with ON DUPLICATE KEY UPDATE last_seen_at
- partnumber_books: pull ALL books (not only is_active=1); re-pull items when
header exists but item count is 0; fallback for missing description column
- partnumber_books UI: collapsible snapshot section (collapsed by default),
pagination (10/page), sync button always visible in header
- vendorSpec handlers: use GetConfigurationByUUID + IsActive check (removed
original_username from WHERE — GetUsername returns "" without JWT)
- bible/09-vendor-spec.md: updated with all architectural decisions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>