====== Карты сопоставления номенклатуры: настройка (для администратора) ====== Страница для администратора базы данных. Инструкция для пользователя — «[[docs:goods_maps|Карты сопоставления номенклатуры]]». Здесь: какие таблицы и скрипты нужны, где хранятся настройки модуля, все параметры с умолчаниями, подключение ИИ, требования к интерфейсам (гридам) окон. Предполагается, что вы знакомы с «[[docs:developer:grids_creating|Конфигуратор: Настройка интерфейсов SQL представлений]]» и форматом ini-описаний «НЭП». ===== 1. Установка: таблицы и скрипты ===== Скрипты применяются по порядку (Firebird, диалект базы — 1): ^Скрипт ^Что делает ^ |''GOODS_MAP.sql'' |Таблицы карт (''GOODS_MAP_SOURCES'') и строк карт (''GOODS_MAPS''), генератор, словари в ''CLASSES_ITEMS'': тип карты (''CLASS_ID'' = 433), статус (434), способ сопоставления (435).| |''GOODS_MAP_AUTO.sql'' |Поля автоподбора (''AUTO_*'') — что подобрала программа, чтобы «Отменить выбор» мог к этому вернуться.| |''GOODS_MAP_LOADS.sql'' |Сессии загрузки: ''GOODS_MAP_LOADS'', ''GOODS_MAP_LOAD_ROWS''.| |''GOODS_MAP_LOAD_ORDER.sql'' |''GOODS_MAP_LOAD_ROWS.ORDER_ID'' — номер строки в порядке загрузки (колонка «№»). **Без него загрузка и импорт по карте не работают.**| |''GOODS_MAP_STATUS_NOT_OURS.sql'' |Пункт 5 «Нет у нас» в словаре статусов — **только подпись** для колонки «Статус» (в базе «Нет у нас» — это «Подтверждено» без позиции).| |''GOODS_MAP_COMMENTS.sql'' |Описания таблиц и полей (''COMMENT ON'').| Синхронизации с удалёнными базами у таблиц карт нет. >**Права.** Отдельного права на окно пока нет: окно доступно всем, у кого есть доступ к меню «Операции». ===== 2. Где хранятся настройки ===== Настройки модуля — текст в формате ini (комментарий — строка, начинающаяся с ''#''; список — ''Ключ=^'' … ''^''). Действуют по порядку, каждый следующий уровень перекрывает предыдущий: - **Умолчания программы** (указаны у каждого параметра ниже). - **Скрипт ''GOODS_MAP_SETTINGS''** (скрипт с этим именем в таблице скриптов ''SCRIPTS'') — общие настройки для всех карт. - **Настройки карты** — поле «Настройки загрузки» в карточке карты (''GOODS_MAP_SOURCES.MAP_SOURCE_PROPS''). Те же секции и ключи; одинаковые ключи карты заменяют общие, остальные общие остаются. Окно сопоставления показывает **действующие** значения (общие + карта) и записывает в карту только то, что пользователь изменил в окне: ^Что в окне ^Куда пишется (в настройках карты) ^ |Разметка колонок, «Строка данных с» |''[Columns]'' (''Code='', ''Articul='', ''Name='', ''Ed='', ''Okei='', ''StrokeCode='', ''Production='', ''Price='' — буквы колонок; ''FirstRow='')| |«Артикул из наименования» |''[Load] ArticulFromName''| |«Кандидат от, %», «Предлагать от, %» |''[Match] MinScore'', ''[Match] ProposeScore''| |«Искать и в картах» |''[Rules] OtherMapIDs''| |Диалог «Подобрать через ИИ», галка «Запомнить для этой карты» |''[AI]'' — изменённые ключи| Запись не трогает остальной текст настроек: комментарии, порядок и списки сохраняются. ===== 3. Параметры: [Load] — загрузка ===== ^Ключ ^Умолчание ^Что задаёт ^ |''LoadNameTemplate'' |''<:SOURCE_NAME> <:DATE> <:TIME>, строк: <:ROWS>'' |Имя сессии при «Загрузить и сопоставить» (предлагается, пользователь может поправить).| |''ImportLoadNameTemplate'' |''Импорт: <:DOC_NAME>'' |Имя сессии при импорте документа по карте.| |''ArticulFromName'' |пусто |Правило извлечения артикула из наименования (см. ниже). Обычно задаётся у карты.| |''NameSimilarity'' |''0.7'' |Порог похожести наименований (0..1). Строка с тем же кодом / артикулом, что у подтверждённой, но с наименованием, отличающимся сильнее, получает «Проверить».| Теги имени сессии (регистр не важен): ''<:SOURCE_NAME>'' — карта, ''<:DATE>'' — дд.мм.гггг, ''<:TIME>'' — чч:мм, ''<:USER>'' — пользователь, ''<:ROWS>'' — число строк, ''<:DOC_NAME>'' — документ (только импорт). Правила ''ArticulFromName'': ^Значение ^Что берётся ^Пример ^ |''brackets'' |Текст в последних скобках; несколько слов — последнее |''(арт. MVA20-1-016-C)'' → ''MVA20-1-016-C''| |''first'' / ''last'' |Первое / последнее слово наименования | | |''prefix:ТЕКСТ'' |Первое слово после ТЕКСТ (регистр не важен) |''prefix:арт.'': «Кабель ВВГ арт. 12345» → ''12345''| |''regex:ВЫРАЖЕНИЕ'' |Регулярное выражение без учёта регистра; артикул — первая группа (нет групп — всё совпадение) | | |пусто |Не извлекать | | ===== 4. Параметры: [Match] — пороги подбора ===== ^Ключ ^Умолчание ^Что задаёт ^ |''MinScore'' |30 |«Кандидат от, %»: в кандидаты попадают позиции с оценкой не ниже.| |''MaxCandidates'' |10 |Сколько кандидатов хранить у строки (1..50).| |''ProposeScore'' |70 |«Предлагать от, %»: лучший кандидат с такой оценкой ставится строке автоматически. Он же — порог «предложенных» при импорте, если в настройках импорта порог не задан.| |''ProposeGap'' |10 |…и лучший выше второго не меньше чем на столько, иначе неоднозначно — строка остаётся «Новой».| ===== 5. Параметры: [Search] — поля нашей номенклатуры ===== Списки ''^ … ^'', по строке ''ТАБЛИЦА.ПОЛЕ''. Таблицы — ''GOODS'' и ''E_GOODS'' (пользовательские поля позиции, связь по ''GOOD_ID''); без таблицы — ''GOODS''. Позиция находится по любому из своих наименований / артикулов; значение, которое есть у нескольких позиций, считается неоднозначным и автоматически не предлагается. Ошибка в имени поля — ошибка при загрузке с текстом запроса. ^Ключ ^Умолчание ^Что задаёт ^ |''SearchGoodsNameFields'' |''GOODS.GOODS_NAME'' |Наименования (группа ''Name'').| |''SearchArticulFields'' |''GOODS.ARTICUL'' |Артикулы (группа ''Articul'').| |''SearchStrokeCodeFields'' |''GOODS.STROKE_CODE1'', ''GOODS.STROKE_CODE2'' |Штрихкоды (группа ''StrokeCode'').| |''SearchKeyFields'' |нет |Свои группы полей для правил: строки ''ИмяГруппы=ТАБЛИЦА.ПОЛЕ[, ТАБЛИЦА.ПОЛЕ ...]''. Имена ''Name'', ''Articul'', ''StrokeCode'', ''MapName'', ''MapArticul'' заняты.| |''GoodsWhereSQL'' |''g.CHECKED = 0'' |Какие позиции могут быть найдены: условие к ''GOODS'' (алиас ''g''; ''E_GOODS'' — ''e''). Группы (''MIX_ID'' = 0) исключаются всегда.| Что реально попало в поиск, видно во второй строке итога загрузки («Наша номенклатура для поиска: …»). Пример: [Search] SearchGoodsNameFields=^ GOODS.GOODS_NAME GOODS.GOODS_NAME1 GOODS.GOODS_NAME2 ^ SearchKeyFields=^ SupplierCode=E_GOODS.GOOD_ADV_PROP1 ^ ===== 6. Параметры: [Rules] — правила точной сверки ===== ''MatchRules'' — список правил, по строке: Имя=<:SOURCE(...)><:TARGET(...)><:SCORE(...)>[параметры] ^Часть ^Значения ^ |''<:SOURCE(...)>'' — что взять из строки поставщика |''Code'' — код; ''Articul'' — артикул; ''ArticulFromName'' — артикул из наименования по ''[Load] ArticulFromName''; ''NameArticul'' — каждое «артикулоподобное» слово наименования (есть цифры, от 4 знаков); ''Name'' — наименование целиком; ''StrokeCode'' — штрихкод| |''<:TARGET(...)>'' — с чем сравнивать |''Name'', ''Articul'', ''StrokeCode'' (группы ''[Search]''), своя группа из ''SearchKeyFields''; ''MapName'' / ''MapArticul'' — наименования / артикулы подтверждённых строк **других карт**| |''<:SCORE(...)>'' |Оценка совпадения 0..100| |''<:MINLEN(N)>'' |Значение поставщика не короче N знаков, иначе правило не применяется| |''<:STATUS(Check)>'' |Совпадение по правилу всегда ставит «Проверить», а не «Предложено»| Нет списка — встроенные правила: MatchRules=^ CodeToArticul=<:SOURCE(Code)><:TARGET(Articul)><:SCORE(90)> Articul=<:SOURCE(Articul)><:TARGET(Articul)><:SCORE(95)> ArticulFromName=<:SOURCE(ArticulFromName)><:TARGET(Articul)><:SCORE(95)> ArticulInName=<:SOURCE(NameArticul)><:TARGET(Articul)><:SCORE(90)><:MINLEN(6)> StrokeCode=<:SOURCE(StrokeCode)><:TARGET(StrokeCode)><:SCORE(95)> Name=<:SOURCE(Name)><:TARGET(Name)><:SCORE(90)> ^ Проверяются все правила. Несколько правил указали на одну позицию — к лучшей оценке прибавляется ''AgreeBonus''. Правила указали на разные позиции с близкими оценками — строка «Проверить». ^Ключ ^Умолчание ^Что задаёт ^ |''AgreeBonus'' |5 |Прибавка, если несколько правил указали на одну позицию (не выше 100).| |''ConflictGap'' |10 |Разные позиции с оценками ближе этого — «Проверить».| |''OtherMapIDs'' |нет ключа |В каких картах искать (''MAP_SOURCE_ID'' через запятую; пусто — не искать). Пишет окно (список «Искать и в картах»). Если ключ задан — главнее двух следующих.| |''UseOtherMaps'' |0 |Старый способ: 1 — искать во всех включённых картах.| |''OtherMapsTypes'' |пусто |Старый способ: только карты этих типов (0 — прочее, 1 — поставщик, 2 — покупатель, 3 — внешняя система).| Когда поиск по другим картам включён и в ''MatchRules'' нет правил с целями ''MapName'' / ''MapArticul'', добавляются встроенные: по наименованию и артикулу с оценкой 85, по артикулу в наименовании — 80. Другие карты учитываются и в нечётком подборе: кандидат, найденный по наименованию в другой карте, помечается («Найдено по»). ===== 7. Параметры: [Fuzzy] — нечёткий подбор кандидатов ===== Работает, когда точная сверка ничего не дала. Оценка кандидата — лучшая похожесть его наименований (и наименований в отмеченных картах) с наименованием поставщика ×100, плюс прибавка за артикул, не выше потолка. ^Ключ ^Умолчание ^Что задаёт ^ |''MaxScore'' |89 |Потолок оценки нечёткого подбора — ниже точного совпадения.| |''ArticulBonus'' |25 |Прибавка за артикул в наименовании поставщика.| |''ArticulBonusMode'' |''Own'' |''Own'' — только если в наименовании есть слово, равное артикулу этого кандидата; ''Any'' — любое общее слово с цифрами от 4 знаков.| |''TopCount'' |30 |Сколько позиций с наибольшим весом слов оценивать.| |''CommonWordShare'' |10 |Слово, которое есть больше чем у 1/N всех позиций (и больше чем у 30), считается частым («для», «белый») и не учитывается.| ===== 8. Параметры: [AI] — подбор через ИИ ===== Значения подставляются в диалог кнопки «Подобрать через ИИ»; галка «Запомнить для этой карты» пишет изменённые в карту. ^Ключ ^Умолчание ^Что задаёт ^ |''ReturnCount'' |3 |Сколько лучших кандидатов строки ИИ возвращает с процентом (колонка «ИИ, %» в окне кандидатов). Больше — длиннее ответ.| |''CandidatesCount'' |20 |Сколько кандидатов передавать на строку.| |''CandidatesMinScore'' |15 |Кандидат не ниже этой оценки нечёткого подбора, %.| |''BatchSize'' |15 |Строк в одном запросе. Больше — дешевле и быстрее, но длиннее ответ (риск обрезки).| |''CheckBelow'' |80 |Уверенность ИИ ниже — статус «Проверить».| |''Model'' |из ''AI_SETTINGS'' |Модель для этой задачи. DeepSeek: ''deepseek-chat'' — быстрая и дешёвая, ''deepseek-reasoner'' — дольше и дороже, точнее на трудных строках.| |''Temperature'' |из ''AI_SETTINGS'' |0 — самые предсказуемые ответы (для выбора из списка — 0).| |''Instructions'' |нет |Свои пояснения для ИИ — особенности вашей номенклатуры (многострочный список, см. пример ниже), добавляются к инструкции.| Пример: [AI] ReturnCount=3 Instructions=^ В нашей номенклатуре размеры пишутся через "x" латиницей: 4.2x25. "Оц." - оцинкованный, "Н/Ж" - нержавеющий. ^ ===== 9. Подключение к ИИ — скрипт AI_SETTINGS ===== Общий для всех задач «НЭП», которые обращаются к ИИ. Скрипт ''AI_SETTINGS'' (в таблице скриптов ''SCRIPTS''), секция ''[AI]''. Протокол — OpenAI-совместимый (DeepSeek, OpenAI, Mistral, OpenRouter, локальные Ollama / LM Studio): смена поставщика — смена адреса, модели и ключа. ^Ключ ^Умолчание ^Что задаёт ^ |''BaseURL'' |''https://api.deepseek.com'' |Адрес API; ''/chat/completions'' дописывается сам.| |''Model'' |''deepseek-chat'' |Модель.| |''ApiKey'' |— |Ключ API. **Обязателен.** Нет ключа — при нажатии «Подобрать через ИИ» программа покажет образец скрипта.| |''Timeout'' |180 |Ожидание ответа, секунд.| |''Temperature'' |0 |0 — самые предсказуемые ответы.| |''MaxTokens'' |0 |Потолок длины ответа; 0 — умолчание сервиса. Если ответ обрезается — уменьшите ''BatchSize'' или поднимите ''MaxTokens''.| |''Retries'' |2 |Повторов при перегрузке сервиса (HTTP 429 / 5xx) и обрыве связи.| [AI] BaseURL=https://api.deepseek.com Model=deepseek-chat ApiKey=<ваш ключ> >**Ключ лежит в базе:** его видят все, у кого есть доступ к скриптам. В ИИ отправляются наименования, артикулы, коды, единицы и производители строк поставщика и кандидатов из нашей номенклатуры, а для кандидата, найденного по другой карте, — имя этой карты и наименование в ней. Цены и реквизиты клиентов не отправляются. Сообщения об ошибках: неверный ключ — HTTP 401, нет средств на счёте — HTTP 402; прогон останавливается с понятным текстом. ===== 10. Интерфейсы окон (Конфигуратор) ===== Стандартные описания приходят с обновлением базы. При правке учитывайте: ==== GOODS_MAPS — таблица окна сопоставления ==== * Ключ — ''GOODS_MAP_ID'', алиас таблицы — ''m''. * **Обязательные скрытые поля** (''<:MAIN><:NOTVISIBLE>''): ''MATCH_STATUS'', ''MATCH_METHOD'', ''GOOD_ID'', ''ED_ID'' — по ним окно красит строки и работает с текущей строкой. * Колонка «Статус» — поле ''MATCH_STATUS_NAME'' со списком словаря 434: ''<:ITEMS(434@ITEM_NAME)>case when m.MATCH_STATUS = 2 and m.GOOD_ID = 0 then 5 else m.MATCH_STATUS end'' (так показывается «Нет у нас»). * Параметры окна в запросе — только через ''${Form.…}'' (не через имя формы — оно может измениться): ''${Form.MapSourceID}'' — выбранная карта; ''${Form.StatusFilter}'' — фильтр статуса (−1 — все); ''${Form.LoadsFilterSQL}'' — условие по выбранным сессиям (уже с ''and'', пусто — все); ''${Form.OrderLoadID}'' — сессия для колонки «№». * Колонка «№» — ''left join GOODS_MAP_LOAD_ROWS lo on (lo.GOODS_MAP_ID = m.GOODS_MAP_ID and lo.MAP_LOAD_ID = ${Form.OrderLoadID})'' в запросах выборки и обновления строки, поле ''lo.ORDER_ID''. В SQL самих полей шаблоны ''${…}'' не раскрываются — поэтому через join. * Колонка «Сессии» — список имён сессий строки: ''(select RESULT_BLOB from GET_SQL_LIST_BY_BLOB('select Trim(l2.MAP_LOAD_NAME) from GOODS_MAP_LOADS l2 JOIN GOODS_MAP_LOAD_ROWS lr2 ON (lr2.MAP_LOAD_ID = l2.MAP_LOAD_ID) where lr2.GOODS_MAP_ID = :ID1 order by l2.MAP_LOAD_ID', <:Alias>.GOODS_MAP_ID))''. * Окно само открывает датасет (действие ''AfterUpdateSQL''); правка в гриде — только ''BOX''. ==== GOODS_MAPS_BY_GOOD — окно «Варианты позиции в картах» ==== * Параметр запроса ''GOOD_ID'' — позиция текущей строки окна, из которого открыто (склад, карточка позиции): ''where m.GOOD_ID = :GOOD_ID''. >**Набор колонок по умолчанию.** Пользовательский набор колонок хранится в реестре под именем окна. Задайте у обоих интерфейсов набор колонок по умолчанию — иначе у нового пользователя (или после переименования окна) таблица может открыться без колонок. ===== 11. Диагностика ===== * Запуск программы с ключом ''-log'': в журнал пишутся запрос поиска по номенклатуре, раскрытие шаблонов ''${…}'' (строки ''TPL:'') и запросы гридов. * Итог загрузки во второй строке показывает, по каким полям и скольким позициям строился поиск. * «Пояснение» строки в окне называет сработавшие правила и причину статуса.