Имя варианта "main" или с недопустимыми символами больше не отклоняет
запрос ошибкой 400 — теперь автоматически конвертируется в валидное
(sanitizeProjectVariantName), а API/UI показывают variant_warning вместо
блокировки создания/переименования варианта.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DPU-лоты (DPU_BF3_B3220-32G) не подходили под regex parsePortSpeed
(рассчитан на NIC/HCA-суффиксы NpMGbE/pFC) и падали в заглушку
категории NET с предупреждением. Добавлен parseDPUModel — структурный
разбор DPU_{FAMILY}_{MODEL} без каталога вендоров.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sale-таблица показывает LOT/Описание/Кол-во/Накидка,%/Цена вместо Estimate/
Склад/Конкуренты/Ручная цена; итоговая цена строки = база (raw estimate или
доля от общей «Ручная цена») × (Аплифт к estimate + своя Накидка%). Экспорт
CSV этой таблицы теперь выводит только LOT;Описание;Кол-во;Цена. Buy-таблица
и массовый экспорт по проекту не затронуты — новые поля запроса опциональны
и включаются только кнопкой «Экспорт CSV» у Sale-таблицы.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Генерация артикула резолвила lot_category из одного прайслиста конфигурации
(GetLocalLotCategoriesByServerPricelistID), поэтому world-only LOT (напр.
GPU_NV_RTX_PRO_6000D_SERVER_84GB_PCIE, есть только в world) молча выпадал из
артикула. Теперь категории берутся через GetLocalComponentCategoriesByLotNames
(тот же world ∪ estimate, что и весь конфигуратор). BuildOptions.ServerPricelist
убран; preview-article принимает pricelist_id, но игнорирует.
Нераспознанные токены больше не пишутся как UNK: в артикул идёт lot_category как
плейсхолдер (4xGPU, 2xCPU), сегмент помечается Recognized=false, добавляется
warning с именем LOT. Конфигуратор подсвечивает такие сегменты (amber) и выводит
список предупреждений; сохранение/обновление/откат логируют WARN. Без каталога
вендоров в репо детектируется только структурный сбой формы имени.
parseGPUModel: принимает суффикс-букву в номере модели (6000D → RTX6000D),
раньше терял её и схлопывал до RTX_84GB.
ADL bible-local/decisions/2026-09-01-article-category-from-component-universe.md,
2026-09-01-article-degraded-token-visibility.md; раздел «Article generation» в
02-architecture.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAhfF4P1ySRZ67yyUUeVw2
Новая настройка app_settings.indicator_mode (color | accessible), переключается
на /setup. В режиме accessible сигналы, которые раньше держались только на цвете,
переходят на форму/текст:
- качество цены (0-9) — 5-ступенчатый signal-meter вместо градиентной точки/числа;
единый модуль web/static/price-quality.js, ветвление по window.QF_INDICATOR_MODE;
- вкладка «Ценообразование»: ведущая колонка-meter вместо заливки строк,
пометка W вместо амбер-подсветки world-цен, ⚠ вместо красного «загрязнённого» итога.
- GET/PUT /api/settings/ui (без рестарта), регистрируется в обоих наборах роутов.
/setup переустроен: две колонки одной высоты, кнопка «Вернуться в приложение»,
исправлен <title>.
Документация: bible-local 02/03/04 + decisions/2026-08-31-accessible-indicator-mode.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbRvQgPZpM4SJTaX3iLrXk
ProjectPricingExportOptions.SaleMarkup всегда был 0 при экспорте с
проекта, поэтому применялся единый дефолт 1,3 вместо сохранённого в
Notes конфигурации значения. buildPricingExportBlock теперь берёт
аплифт из настроек каждой конфигурации — экспорт с проекта идёт по
тому же конвейеру, что и экспорт с конкретной конфигурации.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Добавлен чекбокс «Складские цены» (по аналогии с «Аренда»), управляющий
показом складских цен во всех ценовых интерфейсах конфигурации, включая
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>
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>
Order by created_at DESC без вторичного ключа мог давать нестабильный
tie-break при OFFSET/LIMIT, из-за чего строки с одинаковым created_at
выпадали между страницами при импорте server→local (6 конфигов не
попадали в local_configurations).
Co-Authored-By: Claude Sonnet 5 <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 теперь синхронизируется не только в прайслист, но и в
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>
Прайслисты: добавлено поле price_quality (0-9, выставляется внешней
утилитой ценообразования на основе количества/свежести котировок за
период — QF только отображает, не считает сам). В карточке прайслиста
рядом с ценой показывается предупреждение (жёлтый/красный «!») при
quality < 7.
price_method/price_period_days/price_coefficient/manual_price/meta_prices
убраны из контракта QF (models.PricelistItem, local_pricelist_items,
API, колонка «Настройки» на странице прайслиста) — эти поля принадлежат
внешнему движку ценообразования и нигде не использовались в клиенте.
Сами колонки в qt_pricelist_items не трогаем — оставлены для обратной
совместимости с внешней системой.
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>
Позволяет заново импортировать проект из CSV, экспортированного кнопкой
"Экспорт CSV" (Line Item;LOT;PN вендора;...), а не только из
round-trip формата (Line;Type;p/n;...). Колонки резолвятся по имени,
т.к. набор опциональных столбцов зависит от настроек экспорта.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Комментарий "Заглушка (world)" в CSV раньше писался для любой колонки
(Estimate/Stock/Конкуренты) с фолбэком на world-прайслист, даже если эта
колонка не отмечена галочкой при экспорте.
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>
OfflineDetector дёргал connMgr.IsOnline() на каждый HTTP-запрос, а тот
при офлайне синхронно лез в сеть и держал дайл/пинг с таймаутом 3с под
общей блокировкой состояния — из-за этого /health и другие запросы
блокировались на секунды прямо в обработчике.
IsOnline() теперь чистое чтение кэша. Реальный сетевой опрос вынесен в
фоновый цикл (ConnectionManager.Start/Stop), а сами попытки dial/ping
сериализуются через отдельный connMu и никогда не держат блокировку
состояния во время сетевого I/O — поэтому конкурентные читатели статуса
больше не ждут таймаут MySQL.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SetMaxOpenConns(1) сериализует запись через единственное соединение,
busy_timeout=5000 добавляет ожидание до 5с при внешних блокировках.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Открытые конфигурации фиксируются в локальном SQLite (app_settings) и
передаются на сервер через qt_client_schema_state.open_config_uuids при
каждом цикле синхронизации. Списки конфигураций обогащаются полем viewers,
в таблицах отображается иконка глаза с подсказкой при наличии других
пользователей, открывших эту конфигурацию.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ResolveLotCategoriesStrict переименован в ResolveLotCategories и лишён
строгости: лоты, отсутствующие в прайслисте или с пустой lot_category,
просто пропускаются — партномер из них не собирается. Ранее любой
«незнакомый» лот возвращал ошибку и блокировал сохранение конфига.
Удалены ErrMissingCategoryForLot, MissingCategoryForLotError и
fallback через local_components (противоречил cc72052).
resolvePricelistID: если прайслист отсутствует локально после синка —
fallback на последний активный вместо ошибки.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Таблица qt_lot_metadata не использовалась в рантайме —
ни один репозиторий/сервис/хендлер к ней не обращался.
- удалён models/metadata.go (LotMetadata, Specs, PriceMethod, PriceFreshness)
- удалена LocalToComponent() из localdb/converters.go
- убран &LotMetadata{} из AutoMigrate
- убраны мёртвые поля PriceFreshness/PopularityScore/Specs из ComponentView
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>
Удалён мёртвый серверный слой управления компонентами/категориями,
который дублировал источники категории LOT. В рантайме категория всегда
берётся из local_pricelist_items.lot_category (наполняется синком из
qt_pricelist_items.lot_category).
Удалено:
- repository: UnifiedRepo/DataSource, ComponentRepository, CategoryRepository
- services: старый ConfigurationService (заменён LocalConfigurationService),
ComponentService, ComponentToLocal, ImportFromLot, ParsePartNumber
- quote.go: недостижимый online-блок (qt_categories) + componentRepo/
pricingService/priceResolver
Сохранены живые типы: models.Category/DefaultCategories (для /api/categories),
ComponentView/ComponentListResult, CreateConfigRequest/ArticlePreviewRequest/
ConfigurationGetter/ErrConfig*.
bible-local/03-database.md: зафиксирован единственный источник категории LOT;
qt_categories/qt_lot_metadata перенесены в server-side only.
Co-Authored-By: Claude Opus 4.8 <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>
- SyncPricelists() теперь захватывает pricelistMu, предотвращая параллельный
запуск фонового тикера и ручного sync (было причиной UNIQUE constraint ошибки)
- Дедупликация lot_name в fetchServerPricelistItems на случай дублей на сервере
- PushPendingChanges пишет запись в sync_log (тип "changes") при каждом запуске
- syncPricelists вызывает reportClientSchemaState через defer — состояние
клиента отправляется на сервер независимо от исхода синхронизации
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>
Сохранение:
- 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>
Добавлен новый вариант импорта спеки: 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>
Новый формат 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>
- 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>
- Удалить все записи в qt_pricelist_sync_status (RecordSyncHeartbeat и
ensureUserSyncStatusTable); ListUserSyncStatuses переведён на
qt_client_schema_state
- Подавлять устаревший OFFLINE_UNVERIFIED_SCHEMA в UI когда соединение
уже восстановлено
- Удалить все обращения к lot_log: repository/price.go, сортировка
quote_count в component.go, UpdatePopularityScores в stats.go,
модель LotLog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Раньше NeedSync возвращал true сразу если last_sync > 1 часа назад —
до сравнения версий дело не доходило. Это приводило к бесконечным
повторным попыткам синка когда все прайслисты уже скачаны, но
last_pricelist_status застрял в "failed" из-за предыдущего сбоя.
Теперь когда онлайн — всегда сравниваем реальные версии с сервером.
Если все источники совпадают — возвращаем false независимо от времени
последнего синка. Фолбэк на 1-часовой порог только в офлайн-режиме.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
После сетевого сбоя во время синка прайслистов last_pricelist_status
мог оставаться "failed" навсегда, даже если все прайслисты реально
скачались и NeedSync() возвращает false (всё актуально).
В SyncPricelistsIfNeeded: если NeedSync() == false и статус "failed" —
сбрасываем в success и обновляем last_sync_time, чтобы UI убрал "Не докачано".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Раньше ensureClientSchemaStateTable запускался на каждом цикле синка
(каждые 5 минут) и пытался ALTER TABLE, даже если все колонки уже были.
Для пользователей без DDL-прав это давало WARN-спам в каждом цикле.
Два изменения:
- schemaOnce (sync.Once) на Service: ensureClientSchemaStateTable
вызывается не более одного раза за жизнь процесса
- columnExists() проверяет information_schema.COLUMNS перед каждым
ALTER — если колонка уже есть, ALTER пропускается без ошибки
Если таблица уже мигрирована сервером, клиент молча пропускает все DDL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>