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>
- Роут GET /:code/:variant → редирект на /projects/:uuid (case-insensitive)
- Валидация имени варианта: только URL-безопасные символы [A-Za-z0-9._-]
(бэкенд validateProjectVariantName + клиентская проверка в обеих формах)
- Подсказки в UI: «Используется в URL: /КОД/Вариант»
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>
buildPricingExportBlock теперь создаёт одну строку на каждый LOT mapping,
а не одну строку на BOM-строку. BOM-цена ставится только в первую подстроку
(как vendorOrig в фронтенде). Добавлен computeSingleLotTotal, удалён
неиспользуемый formatLotDisplay.
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>
Добавлен новый вариант импорта спеки: quantity-first формат, где каждая
строка начинается с `<qty>x <description>` (например, «2x Intel Xeon 8570»).
Порядок детекции: Inspur → Nx → Text BOM. Заголовок «, в составе:» работает
так же, как в Text BOM — последний токен перед запятой становится server_model.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
modernc.org/sqlite (glebarez/sqlite) не приводит BLOB к TEXT при LIKE,
в отличие от нативного sqlite3. lots_json хранится как BLOB (json.Marshal
возвращает []byte), поэтому `lots_json LIKE ?` всегда возвращал 0.
Исправлено на CAST(lots_json AS TEXT) LIKE — теперь поиск по LOT имени
работает корректно.
Заодно обновлён плейсхолдер поля поиска: «PN, LOT или описание».
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ensureConfigurationProject: если project не найден ни на сервере, ни локально
(stale UUID после удаления), падаем в fallback «Без проекта» вместо вечной ошибки
- PushPendingChanges: автоматически вызывает RepairPendingChanges() перед циклом,
чтобы локально-исправимые проблемы чинились до попытки отправки
- maxPendingChangeAttempts=20: после 20 неудачных попыток change считается
unrecoverable и удаляется из очереди (логируется ERROR)
- pushSingleChange/pushConfigurationChange: unknown entity type / operation
теперь дропается с warn вместо вечного error в цикле
- latestSyncErrorState: last_sync_error_text в qt_client_schema_state теперь
содержит JSON-массив с type/uuid/op/attempts/error по всем застрявшим changes
(до 20 штук) вместо текста только последней ошибки
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PullPartnumberBooks вызывается автоматически после каждой синхронизации
прайслистов — в фоновом воркере, при ручном триггере /api/sync/pricelists
и при полной синхронизации /api/sync/all. Отдельная кнопка «Синхронизировать»
на странице Партномера удалена.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
При сохранении vendor-spec строки с заполненным lot_mappings автоматически
отправляются на сервер и пишутся в новый столбец lot_suggestion. Столбец
хранит JSON-массив [{lot_name, qty}] — тот же формат, что qt_partnumber_book_items.lots_json.
Если миграция ещё не прошла (столбец отсутствует), приложение логирует WARN
и записывает строку без столбца; сбоя нет.
Контракт для инструмента создания partnumber-books описан в bible-local/11-lot-suggestions.md.
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>
Включает контракты build-version-display, local-first-recovery и
автоматизацию резервных копий миграций.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Новый формат vendor-import: опциональный заголовок "Сервер <модель>,
в составе:" и строки вида "<описание> - <кол-во> шт." (дефис/тире,
пробел перед "шт" и точка опциональны). Количество якорится в конце
строки, поэтому дефисы и цифры внутри описания (8-GPU-2304GB) сохраняются.
Описание пишется и в vendor_partnumber, и в description: строки
резолвятся через активную книгу партномеров, иначе остаются
нерезолвленными и редактируемыми. Весь файл — одна конфигурация.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Удалены модели, репозитории и авто-миграции для трёх таблиц, которые
никогда не использовались в продакшн-коде. Убраны StatsRepository и
RecordUsage из сервисов, сигнатуры конструкторов упрощены.
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>
compressArticle assumed a fixed segment layout [model,cpu,mem,gpu,disk,net,psu,support].
Build() skips empty segments, so without GPU the PSU slot shifted to index 5. Step 2 of
compression called compressNetSegment on the PSU value ("2x1kW") which returned "2xNIC".
Replace positional indexing with namedSeg{group,value} tagged segments; compressArticle
now looks up each segment by group name via findSegGroup(), regardless of array length.
Regression test TestBuild_CompressArticle_NoGPU_PSUNotNIC reproduces the exact config
from OPS-2445 (NF5280M6 + 2xPSU, no GPU) that triggered the bug.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Poll /health every 5s; show full-screen overlay after 2 consecutive
failures telling the user the console was closed
- Auto-hide overlay when backend comes back online
- Added to base.html (all main pages) and setup.html (first-run/settings)
- setup.html: suppress false-positive overlay during intentional restart
via awaitingRestart flag
- setup.html: add amber warning banner that the console must stay open
- .gitignore: block *_import.sql and *_export.csv to prevent future
accidental commits of real supplier/pricing data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ampere, Hopper, Blackwell now produce AMP/HOP/BWL suffixes (like ADA)
so RTX cards across generations are distinguishable: RTX6000ADA vs
RTX6000BWL. LOVELACE remains a skip token as it duplicates ADA info.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RTX 6000 ADA and A6000 are distinct cards — RTX_4000_ADA_SFF now
produces RTX4000ADA instead of RTX, avoiding visual ambiguity with
the segment separator (10xRTX4000ADA vs 10xRTX-1x…).
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>
SyncPricelistsIfNeeded was called synchronously in Create(), blocking
the HTTP response for several seconds while pricelist data was fetched.
Users clicking multiple times caused 6+ duplicate configurations.
- Run SyncPricelistsIfNeeded in a goroutine so Create() returns immediately
- Add TryLock mutex to SyncPricelistsIfNeeded to skip concurrent calls
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>
Expand 03-database.md with complete table structure reference for all
23 tables in the final schema: active QuoteForge tables, competitor
subsystem, legacy RFQ tables, and server-side-only tables.
Also clarifies access patterns per group and notes removal of
qt_client_local_migrations from the schema.
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>
MB_ lots (e.g. MB_INTEL_..._GPU8) are incorrectly categorized as GPU
in the pricelist. Two fixes:
- Skip MB_ lots in buildGPUSegment regardless of pricelist category
- Add INTEL to vendor token skip list in parseGPUModel (was missing,
unlike AMD/NV/NVIDIA which were already skipped)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MB_ lots (e.g. MB_INTEL_..._GPU8) are incorrectly categorized as GPU
in the pricelist. Two fixes:
- Skip MB_ lots in buildGPUSegment regardless of pricelist category
- Add INTEL to vendor token skip list in parseGPUModel (was missing,
unlike AMD/NV/NVIDIA which were already skipped)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add bible/ with 7 hierarchical English-only files covering overview,
architecture, database schemas, API endpoints, config/env, backup, and dev guides
- Consolidate all docs from README.md, CLAUDE.md, man/backup.md into bible/
- Simplify CLAUDE.md to a single rule: read and respect the bible
- Simplify README.md to a brief intro with links to bible/
- Remove man/backup.md and pricelists_window.md (content migrated or obsolete)
- Fix API docs: add missing endpoints (preview-article, sync/repair),
correct DELETE /api/projects/:uuid semantics (variant soft-delete only)
- Add Soft Deletes section to architecture doc (is_active pattern)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Ignore all files in releases/ directory (binaries, archives, checksums)
- Preserve releases/memory/ for changelog tracking
- Changed from 'releases/' to 'releases/*' for clearer intent
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Unified export filename format across both ExportCSV and ExportConfigCSV:
- Format: YYYY-MM-DD (project_name) config_name BOM.csv
- Use PriceUpdatedAt if available, otherwise CreatedAt
- Extract project name from ProjectUUID for ExportCSV via projectService
- Pass project_uuid from frontend to backend in export request
- Add projectUUID and projectName state variables to track project context
This ensures consistent naming whether exporting from form or project view,
and uses most recent price update timestamp in filename.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>