answer42 0.2.20__tar.gz → 0.2.22__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {answer42-0.2.20 → answer42-0.2.22}/PKG-INFO +1 -1
  2. {answer42-0.2.20 → answer42-0.2.22}/pyproject.toml +1 -1
  3. {answer42-0.2.20 → answer42-0.2.22}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +301 -37
  4. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  5. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/server.py +84 -3
  6. {answer42-0.2.20 → answer42-0.2.22}/.gitignore +0 -0
  7. {answer42-0.2.20 → answer42-0.2.22}/LICENSE +0 -0
  8. {answer42-0.2.20 → answer42-0.2.22}/README.md +0 -0
  9. {answer42-0.2.20 → answer42-0.2.22}/credentials.example.json +0 -0
  10. {answer42-0.2.20 → answer42-0.2.22}/docs/agent-installation.md +0 -0
  11. {answer42-0.2.20 → answer42-0.2.22}/docs/architecture.md +0 -0
  12. {answer42-0.2.20 → answer42-0.2.22}/docs/assets/answer42-logo.png +0 -0
  13. {answer42-0.2.20 → answer42-0.2.22}/docs/assets/platform42-logo.svg +0 -0
  14. {answer42-0.2.20 → answer42-0.2.22}/docs/installation.md +0 -0
  15. {answer42-0.2.20 → answer42-0.2.22}/scripts/build_cf.py +0 -0
  16. {answer42-0.2.20 → answer42-0.2.22}/scripts/build_pages.py +0 -0
  17. {answer42-0.2.20 → answer42-0.2.22}/scripts/e2e_stable.py +0 -0
  18. {answer42-0.2.20 → answer42-0.2.22}/scripts/openclaw_mcp_autoreload.py +0 -0
  19. {answer42-0.2.20 → answer42-0.2.22}/scripts/rag_cli.py +0 -0
  20. {answer42-0.2.20 → answer42-0.2.22}/src/cf/ConfigDumpInfo.xml +0 -0
  21. {answer42-0.2.20 → answer42-0.2.22}/src/cf/Configuration.xml +0 -0
  22. {answer42-0.2.20 → answer42-0.2.22}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  23. {answer42-0.2.20 → answer42-0.2.22}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  24. {answer42-0.2.20 → answer42-0.2.22}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  25. {answer42-0.2.20 → answer42-0.2.22}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  26. {answer42-0.2.20 → answer42-0.2.22}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  27. {answer42-0.2.20 → answer42-0.2.22}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  28. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Catalogs//320/237/320/241_/320/222/320/273/320/260/320/264/320/265/320/273/320/265/321/206.xml" +0 -0
  29. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Catalogs//320/237/320/241_/320/241/320/277/321/200/320/260/320/262/320/276/321/207/320/275/320/270/320/2721.xml" +0 -0
  30. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/ConfigDumpInfo.xml +0 -0
  31. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Configuration.xml +0 -0
  32. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  33. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ManagerModule.bsl" +0 -0
  34. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ObjectModule.bsl" +0 -0
  35. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205/Ext/Template.xml" +0 -0
  36. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205.xml" +0 -0
  37. {answer42-0.2.20 → answer42-0.2.22}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262.xml" +0 -0
  38. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/__init__.py +0 -0
  39. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  40. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/assets/__init__.py +0 -0
  41. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  42. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  43. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/bridge.py +0 -0
  44. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/credentials.py +0 -0
  45. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/os_support.py +0 -0
  46. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/platform.py +0 -0
  47. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/protocol.py +0 -0
  48. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/rag/__init__.py +0 -0
  49. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/rag/detect.py +0 -0
  50. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/rag/model.py +0 -0
  51. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/rag/parsers.py +0 -0
  52. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/rag/service.py +0 -0
  53. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/rag/store.py +0 -0
  54. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/recorder.py +0 -0
  55. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/release_helper.py +0 -0
  56. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/runtime.py +0 -0
  57. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/skill_installer.py +0 -0
  58. {answer42-0.2.20 → answer42-0.2.22}/src/mcp_1c/window_control.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: answer42
3
- Version: 0.2.20
3
+ Version: 0.2.22
4
4
  Summary: Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API
5
5
  Author: Marvin (AI Assistant), 42Clouds, and contributors
6
6
  Author-email: "Kosolapov Stanislav (proDOOMman)" <prodoomman@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "answer42"
3
- version = "0.2.20"
3
+ version = "0.2.22"
4
4
  description = "Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -412,6 +412,10 @@
412
412
  Возврат ДобавитьСтрокуТаблицыMCP(Параметры);
413
413
  ИначеЕсли Метод = "table_set_field" Тогда
414
414
  Возврат УстановитьПолеТаблицыMCP(Параметры);
415
+ ИначеЕсли Метод = "table_set_row_values" Тогда
416
+ Возврат УстановитьЗначенияСтрокиТаблицыMCP(Параметры);
417
+ ИначеЕсли Метод = "set_form_fields" Тогда
418
+ Возврат УстановитьЗначенияПолейФормыMCP(Параметры);
415
419
  ИначеЕсли Метод = "table_choose_field_from_list" Тогда
416
420
  Возврат ВыбратьПолеТаблицыИзСпискаMCP(Параметры);
417
421
  ИначеЕсли Метод = "table_edit_info" Тогда
@@ -3021,7 +3025,7 @@
3021
3025
  Имя = СвойствоСтруктуры(Параметры, "name", "");
3022
3026
  Заголовок = СвойствоСтруктуры(Параметры, "title", "");
3023
3027
  Путь = СвойствоСтруктуры(Параметры, "path", "");
3024
- ФорматФайла = СвойствоСтруктуры(Параметры, "format", "pdf");
3028
+ ФорматФайла = НРег(СвойствоСтруктуры(Параметры, "format", "pdf"));
3025
3029
  Глубина = СвойствоСтруктуры(Параметры, "max_depth", 8);
3026
3030
  Поле = НайтиТабличныйДокументПоПараметрам(АктивноеОкно, Имя, Заголовок, 0, Глубина);
3027
3031
  Если Поле = Неопределено Тогда
@@ -3030,12 +3034,119 @@
3030
3034
  Если ПустаяСтрока(Путь) Тогда
3031
3035
  Путь = КаталогВременныхФайлов() + "tabular_document_" + Формат(ТекущаяДата(), "ДФ=yyyyMMdd_HHmmss") + "." + ФорматФайла;
3032
3036
  КонецЕсли;
3037
+
3038
+ // Для tested form field extension for spreadsheet document direct Записать()
3039
+ // недоступен/ненадежен; используем штатный метод tested field
3040
+ // ЗаписатьСодержимоеВФайл(<СохранитьКак>) и очередь результата файлового
3041
+ // диалога. Для PDF пробуем несколько индексов фильтра и оба значения
3042
+ // <СохранитьКак> (Истина/Ложь), чтобы не угадывать поведение конкретной формы.
3043
+ Если ФорматФайла = "pdf" Тогда
3044
+ Файлы = Новый Массив;
3045
+ Ошибки = Новый Массив;
3046
+ ВариантыСохранитьКак = Новый Массив;
3047
+ ВариантыСохранитьКак.Добавить(Истина);
3048
+ ВариантыСохранитьКак.Добавить(Ложь);
3049
+ Для Каждого СохранитьКак Из ВариантыСохранитьКак Цикл
3050
+ Для ИндексФильтра = 1 По 10 Цикл
3051
+ ПутьПопытки = ДобавитьСуффиксКПутиMCP(Путь, "_saveas_" + ?(СохранитьКак, "true", "false") + "_filter" + Строка(ИндексФильтра));
3052
+ Попытка
3053
+ ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, ПутьПопытки, ИндексФильтра);
3054
+ Поле.ЗаписатьСодержимоеВФайл(СохранитьКак);
3055
+ ОписаниеФайла = Новый Структура("path,filter_index,save_as", ПутьПопытки, ИндексФильтра, СохранитьКак);
3056
+ Файлы.Добавить(ОписаниеФайла);
3057
+ Исключение
3058
+ Ошибки.Добавить(Новый Структура("filter_index,save_as,error", ИндексФильтра, СохранитьКак, ОписаниеОшибки()));
3059
+ КонецПопытки;
3060
+ КонецЦикла;
3061
+ КонецЦикла;
3062
+ Если Файлы.Количество() > 0 Тогда
3063
+ Возврат Новый Структура("saved,path,format,method,files,errors", Истина, Путь, ФорматФайла, "write_content_to_file_dialog_filters", Файлы, Ошибки);
3064
+ КонецЕсли;
3065
+ Возврат Новый Структура("saved,path,format,method,files,errors", Ложь, Путь, ФорматФайла, "write_content_to_file_dialog_filters", Файлы, Ошибки);
3066
+ КонецЕсли;
3067
+
3068
+ ОшибкаЗаписатьСодержимое = "";
3033
3069
  Попытка
3034
- Поле.Записать(Путь, ФорматФайла);
3035
- Возврат Новый Структура("saved,path,format", Истина, Путь, ФорматФайла);
3070
+ ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, Путь, ИндексФильтраДиалогаТабличногоДокументаMCP(ФорматФайла));
3071
+ Поле.ЗаписатьСодержимоеВФайл(Истина);
3072
+ Возврат Новый Структура("saved,path,format,method,save_as", Истина, Путь, ФорматФайла, "write_content_to_file_dialog", Истина);
3036
3073
  Исключение
3037
- Возврат Новый Структура("saved,path,format,error", Ложь, Путь, ФорматФайла, ОписаниеОшибки());
3074
+ ОшибкаЗаписатьСодержимое = ОписаниеОшибки();
3038
3075
  КонецПопытки;
3076
+ Попытка
3077
+ ТестируемоеПриложение.УстановитьРезультатДиалогаВыбораФайла(Истина, Путь, ИндексФильтраДиалогаТабличногоДокументаMCP(ФорматФайла));
3078
+ Поле.ЗаписатьСодержимоеВФайл(Ложь);
3079
+ Возврат Новый Структура("saved,path,format,method,save_as", Истина, Путь, ФорматФайла, "write_content_to_file_dialog", Ложь);
3080
+ Исключение
3081
+ Если Не ПустаяСтрока(ОшибкаЗаписатьСодержимое) Тогда
3082
+ ОшибкаЗаписатьСодержимое = ОшибкаЗаписатьСодержимое + Символы.ПС + ОписаниеОшибки();
3083
+ Иначе
3084
+ ОшибкаЗаписатьСодержимое = ОписаниеОшибки();
3085
+ КонецЕсли;
3086
+ КонецПопытки;
3087
+
3088
+ Попытка
3089
+ ТипФайла = ТипФайлаТабличногоДокументаMCP(ФорматФайла);
3090
+ Поле.Записать(Путь, ТипФайла);
3091
+ Возврат Новый Структура("saved,path,format,method", Истина, Путь, ФорматФайла, "write_direct");
3092
+ Исключение
3093
+ Возврат Новый Структура("saved,path,format,error,content_error", Ложь, Путь, ФорматФайла, ОписаниеОшибки(), ОшибкаЗаписатьСодержимое);
3094
+ КонецПопытки;
3095
+ КонецФункции
3096
+
3097
+ &НаКлиенте
3098
+ Функция ДобавитьСуффиксКПутиMCP(Путь, Суффикс)
3099
+ ПозицияТочки = 0;
3100
+ Для Индекс = СтрДлина(Путь) По 1 Цикл
3101
+ Если Сред(Путь, Индекс, 1) = "." Тогда
3102
+ ПозицияТочки = Индекс;
3103
+ Прервать;
3104
+ КонецЕсли;
3105
+ КонецЦикла;
3106
+ Если ПозицияТочки = 0 Тогда
3107
+ Возврат Путь + Суффикс;
3108
+ КонецЕсли;
3109
+ Возврат Лев(Путь, ПозицияТочки - 1) + Суффикс + Сред(Путь, ПозицияТочки);
3110
+ КонецФункции
3111
+
3112
+ &НаКлиенте
3113
+ Функция ТипФайлаТабличногоДокументаMCP(ФорматФайла)
3114
+ Формат = НРег(Строка(ФорматФайла));
3115
+ Если Формат = "pdf" Тогда
3116
+ Возврат ТипФайлаТабличногоДокумента.PDF;
3117
+ ИначеЕсли Формат = "xlsx" Тогда
3118
+ Возврат ТипФайлаТабличногоДокумента.XLSX;
3119
+ ИначеЕсли Формат = "xls" Тогда
3120
+ Возврат ТипФайлаТабличногоДокумента.XLS;
3121
+ ИначеЕсли Формат = "mxl" Тогда
3122
+ Возврат ТипФайлаТабличногоДокумента.MXL;
3123
+ ИначеЕсли Формат = "html" Или Формат = "htm" Тогда
3124
+ Возврат ТипФайлаТабличногоДокумента.HTML;
3125
+ ИначеЕсли Формат = "txt" Тогда
3126
+ Возврат ТипФайлаТабличногоДокумента.TXT;
3127
+ Иначе
3128
+ Возврат ТипФайлаТабличногоДокумента.PDF;
3129
+ КонецЕсли;
3130
+ КонецФункции
3131
+
3132
+ &НаКлиенте
3133
+ Функция ИндексФильтраДиалогаТабличногоДокументаMCP(ФорматФайла)
3134
+ Формат = НРег(Строка(ФорматФайла));
3135
+ Если Формат = "pdf" Тогда
3136
+ Возврат 1;
3137
+ ИначеЕсли Формат = "xlsx" Тогда
3138
+ Возврат 2;
3139
+ ИначеЕсли Формат = "xls" Тогда
3140
+ Возврат 3;
3141
+ ИначеЕсли Формат = "mxl" Тогда
3142
+ Возврат 4;
3143
+ ИначеЕсли Формат = "html" Или Формат = "htm" Тогда
3144
+ Возврат 5;
3145
+ ИначеЕсли Формат = "txt" Тогда
3146
+ Возврат 6;
3147
+ Иначе
3148
+ Возврат 1;
3149
+ КонецЕсли;
3039
3150
  КонецФункции
3040
3151
 
3041
3152
  &НаКлиенте
@@ -3046,13 +3157,8 @@
3046
3157
  Попытка
3047
3158
  Подчиненные = БезопасныеПодчиненные(РодительскийОбъект);
3048
3159
  Для Каждого Подчиненный Из Подчиненные Цикл
3049
- ТипТек = НормализованноеИмяТипа(Строка(ТипЗнч(Подчиненный)));
3050
- Если ТипТек = "тестируемоеполетабличногодокументаформы" Тогда
3051
- ИмяТек = БезопасноеСвойство(Подчиненный, "Имя", "");
3052
- ЗаголовокТек = БезопасныйЗаголовок(Подчиненный);
3053
- Если (ПустаяСтрока(Имя) Или ИмяТек = Имя) И (ПустаяСтрока(Заголовок) Или ЗаголовокТек = Заголовок) Тогда
3054
- Возврат Подчиненный;
3055
- КонецЕсли;
3160
+ Если ЭтоИскомоеПолеТабличногоДокумента(Подчиненный, Имя, Заголовок) Тогда
3161
+ Возврат Подчиненный;
3056
3162
  КонецЕсли;
3057
3163
  КонецЦикла;
3058
3164
  Для Каждого Подчиненный Из Подчиненные Цикл
@@ -3068,6 +3174,31 @@
3068
3174
  Возврат Неопределено;
3069
3175
  КонецФункции
3070
3176
 
3177
+ &НаКлиенте
3178
+ Функция ЭтоИскомоеПолеТабличногоДокумента(Объект, Имя, Заголовок)
3179
+ ТипТек = НормализованноеИмяТипа(Строка(ТипЗнч(Объект)));
3180
+ ЭтоПолеТабличногоДокумента = ТипТек = "тестируемоеполетабличногодокументаформы";
3181
+ Если Не ЭтоПолеТабличногоДокумента И ТипТек = "тестируемоеполеформы" Тогда
3182
+ ЭтоПолеТабличногоДокумента = ПредставлениеДанныхПоляТабличныйДокумент(Объект);
3183
+ КонецЕсли;
3184
+ Если Не ЭтоПолеТабличногоДокумента Тогда
3185
+ Возврат Ложь;
3186
+ КонецЕсли;
3187
+ ИмяТек = БезопасноеСвойство(Объект, "Имя", "");
3188
+ ЗаголовокТек = БезопасныйЗаголовок(Объект);
3189
+ Возврат (ПустаяСтрока(Имя) Или ИмяТек = Имя) И (ПустаяСтрока(Заголовок) Или ЗаголовокТек = Заголовок);
3190
+ КонецФункции
3191
+
3192
+ &НаКлиенте
3193
+ Функция ПредставлениеДанныхПоляТабличныйДокумент(Поле)
3194
+ Попытка
3195
+ Представление = Поле.ПолучитьПредставлениеДанных();
3196
+ Возврат НРег(Строка(Представление)) = "табличныйдокумент";
3197
+ Исключение
3198
+ Возврат Ложь;
3199
+ КонецПопытки;
3200
+ КонецФункции
3201
+
3071
3202
 
3072
3203
 
3073
3204
  &НаКлиенте
@@ -3981,11 +4112,98 @@
3981
4112
  Возврат ИмяТек = ИмяПоля Или ЗаголовокТек = ИмяПоля Или СтрНайти(НРег(ИмяТек), НРег(ИмяПоля)) > 0 Или СтрНайти(НРег(ЗаголовокТек), НРег(ИмяПоля)) > 0;
3982
4113
  КонецФункции
3983
4114
 
4115
+ &НаКлиенте
4116
+ Функция КлючСтрокиСоответствуетПолюMCP(Ключ, ИмяПоля, ИскатьПоЗаголовку)
4117
+ Если ИскатьПоЗаголовку Тогда
4118
+ Возврат Строка(Ключ) = ИмяПоля Или СтрНайти(НРег(Строка(Ключ)), НРег(ИмяПоля)) > 0;
4119
+ КонецЕсли;
4120
+ Возврат Строка(Ключ) = ИмяПоля Или СтрНайти(НРег(Строка(Ключ)), НРег(ИмяПоля)) > 0;
4121
+ КонецФункции
4122
+
4123
+ &НаКлиенте
4124
+ Функция НормализоватьЗначениеДляПроверкиMCP(Значение)
4125
+ Результат = НРег(СокрЛП(Строка(Значение)));
4126
+ Результат = СтрЗаменить(Результат, Символ(160), "");
4127
+ Результат = СтрЗаменить(Результат, " ", "");
4128
+ Результат = СтрЗаменить(Результат, ",", ".");
4129
+ Возврат Результат;
4130
+ КонецФункции
4131
+
4132
+ &НаКлиенте
4133
+ Функция ЗначенияСовпадаютДляПроверкиMCP(ФактическоеЗначение, ОжидаемоеЗначение)
4134
+ Факт = НормализоватьЗначениеДляПроверкиMCP(ФактическоеЗначение);
4135
+ Ожидание = НормализоватьЗначениеДляПроверкиMCP(ОжидаемоеЗначение);
4136
+ Если Факт = Ожидание Тогда
4137
+ Возврат Истина;
4138
+ КонецЕсли;
4139
+ Если Не ПустаяСтрока(Ожидание) И СтрНайти(Факт, Ожидание) > 0 Тогда
4140
+ Возврат Истина;
4141
+ КонецЕсли;
4142
+ Если Не ПустаяСтрока(Факт) И СтрНайти(Ожидание, Факт) > 0 Тогда
4143
+ Возврат Истина;
4144
+ КонецЕсли;
4145
+ Возврат Ложь;
4146
+ КонецФункции
4147
+
4148
+ &НаКлиенте
4149
+ Функция ЗначениеКолонкиТекущейСтрокиMCP(Таблица, ИмяПоля, ИскатьПоЗаголовку)
4150
+ ТекущаяСтрока = Таблица.ПолучитьТекущуюСтроку();
4151
+ Если ТекущаяСтрока = Неопределено Тогда
4152
+ Возврат Новый Структура("found,key,value", Ложь, "", "");
4153
+ КонецЕсли;
4154
+ Для Каждого КЗ Из ТекущаяСтрока Цикл
4155
+ Если КлючСтрокиСоответствуетПолюMCP(КЗ.Ключ, ИмяПоля, ИскатьПоЗаголовку) Тогда
4156
+ Возврат Новый Структура("found,key,value", Истина, Строка(КЗ.Ключ), Строка(КЗ.Значение));
4157
+ КонецЕсли;
4158
+ КонецЦикла;
4159
+ Возврат Новый Структура("found,key,value", Ложь, "", "");
4160
+ КонецФункции
4161
+
4162
+ &НаКлиенте
4163
+ Процедура ОбеспечитьРедактированиеТекущейСтрокиТаблицыMCP(Таблица)
4164
+ Таблица.Активизировать();
4165
+ Попытка
4166
+ Если Не Таблица.ТекущийРежимРедактирование() Тогда
4167
+ Таблица.ИзменитьСтроку();
4168
+ КонецЕсли;
4169
+ Исключение
4170
+ Таблица.ИзменитьСтроку();
4171
+ КонецПопытки;
4172
+ КонецПроцедуры
4173
+
3984
4174
  &НаКлиенте
3985
4175
  Функция НайтиПолеТаблицыMCP(Таблица, ИмяПоля, ИскатьПоЗаголовку)
3986
4176
  Возврат НайтиПолеТаблицыРекурсивноMCP(Таблица, ИмяПоля, ИскатьПоЗаголовку, 0, 8);
3987
4177
  КонецФункции
3988
4178
 
4179
+ &НаКлиенте
4180
+ Функция НайтиПолеРедактораТаблицыMCP(АктивноеОкно, Таблица, ИмяТаблицы, ИмяПоля, ИскатьПоЗаголовку, МаксимальнаяГлубина)
4181
+ Кандидаты = Новый Массив;
4182
+ Если Не ПустаяСтрока(ИмяТаблицы) И Не ПустаяСтрока(ИмяПоля) Тогда
4183
+ Кандидаты.Добавить(ИмяТаблицы + ИмяПоля);
4184
+ КонецЕсли;
4185
+ Кандидаты.Добавить(ИмяПоля);
4186
+
4187
+ Для Каждого Кандидат Из Кандидаты Цикл
4188
+ Если ПустаяСтрока(Кандидат) Тогда
4189
+ Продолжить;
4190
+ КонецЕсли;
4191
+ Поле = НайтиОбъектРекурсивно(АктивноеОкно, Кандидат, "", "тестируемоеполеформы", 0, МаксимальнаяГлубина);
4192
+ Если Поле <> Неопределено Тогда
4193
+ Возврат Поле;
4194
+ КонецЕсли;
4195
+ КонецЦикла;
4196
+
4197
+ Если ИскатьПоЗаголовку Тогда
4198
+ Поле = НайтиОбъектРекурсивно(АктивноеОкно, "", ИмяПоля, "тестируемоеполеформы", 0, МаксимальнаяГлубина);
4199
+ Если Поле <> Неопределено Тогда
4200
+ Возврат Поле;
4201
+ КонецЕсли;
4202
+ КонецЕсли;
4203
+
4204
+ Возврат НайтиПолеТаблицыMCP(Таблица, ИмяПоля, ИскатьПоЗаголовку);
4205
+ КонецФункции
4206
+
3989
4207
  &НаКлиенте
3990
4208
  Функция НайтиПолеТаблицыРекурсивноMCP(Элемент, ИмяПоля, ИскатьПоЗаголовку, Глубина, МаксимальнаяГлубина)
3991
4209
  Если Элемент = Неопределено Или Глубина > МаксимальнаяГлубина Тогда
@@ -4018,45 +4236,91 @@
4018
4236
  ИмяПоля = СвойствоСтруктуры(Параметры, "field", "");
4019
4237
  Значение = СвойствоСтруктуры(Параметры, "value", "");
4020
4238
  ИскатьПоЗаголовку = СвойствоСтруктуры(Параметры, "search_by_title", Ложь);
4239
+ МаксимальнаяГлубина = СвойствоСтруктуры(Параметры, "max_depth", 20);
4021
4240
  АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
4022
- Таблица = НайтиТаблицуПоПараметрам(АктивноеОкно, ИмяТаблицы, "", 0, 8);
4241
+ Таблица = НайтиТаблицуПоПараметрам(АктивноеОкно, ИмяТаблицы, "", 0, МаксимальнаяГлубина);
4023
4242
  Если Таблица = Неопределено Тогда
4024
4243
  ВызватьИсключение "Таблица не найдена: " + ИмяТаблицы;
4025
4244
  КонецЕсли;
4026
- Таблица.Активизировать();
4027
- Попытка
4028
- Если Не Таблица.ТекущийРежимРедактирование() Тогда
4029
- Таблица.ИзменитьСтроку();
4030
- КонецЕсли;
4031
- Исключение
4032
- Попытка
4033
- Таблица.ИзменитьСтроку();
4034
- Исключение
4035
- КонецПопытки;
4036
- КонецПопытки;
4037
- Поле = НайтиПолеТаблицыMCP(Таблица, ИмяПоля, ИскатьПоЗаголовку);
4245
+
4246
+ ОбеспечитьРедактированиеТекущейСтрокиТаблицыMCP(Таблица);
4247
+ АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
4248
+ Поле = НайтиПолеРедактораТаблицыMCP(АктивноеОкно, Таблица, ИмяТаблицы, ИмяПоля, ИскатьПоЗаголовку, МаксимальнаяГлубина);
4038
4249
  Если Поле = Неопределено Тогда
4039
- ВызватьИсключение "Поле таблицы не найдено: " + ИмяПоля;
4250
+ ВызватьИсключение "Поле редактора таблицы не найдено: " + ИмяПоля;
4040
4251
  КонецЕсли;
4252
+ ИмяРедактора = БезопасноеСвойство(Поле, "Имя", "");
4041
4253
  Поле.Активизировать();
4254
+
4255
+ ПараметрыПоля = Новый Структура("field_name,value,search_by_title,max_depth", ИмяРедактора, Значение, Ложь, МаксимальнаяГлубина);
4256
+ ОшибкаУстановки = "";
4257
+ РезультатУстановки = Новый Структура("method", "set_field_value");
4042
4258
  Попытка
4043
- Поле.ВвестиТекст(Значение);
4259
+ РезультатУстановки = УстановитьЗначениеПоля(ПараметрыПоля);
4044
4260
  Исключение
4045
- ОшибкаВвестиТекст = ОписаниеОшибки();
4046
- Попытка
4047
- Поле.Выбрать();
4048
- Поле.ОжидатьФормированияВыпадающегоСписка(10);
4049
- Поле.ВыполнитьВыборИзСпискаВыбора(Значение);
4050
- Исключение
4051
- ВызватьИсключение "Не удалось установить поле таблицы " + ИмяПоля + ": " + ОшибкаВвестиТекст + "; " + ОписаниеОшибки();
4052
- КонецПопытки;
4261
+ // Для числовых полей табличной части ВвестиТекст() может фактически
4262
+ // изменить значение, но представление текущей строки обновится только
4263
+ // после ЗакончитьРедактированиеСтроки(). Поэтому не падаем сразу:
4264
+ // завершаем редактирование и ниже проверяем значение в текущей строке.
4265
+ ОшибкаУстановки = ОписаниеОшибки();
4053
4266
  КонецПопытки;
4267
+
4054
4268
  Попытка
4055
- Таблица.ЗакончитьРедактированиеСтроки();
4269
+ Таблица.ЗакончитьРедактированиеСтроки(Ложь);
4056
4270
  Исключение
4057
- // Для некоторых таблиц/состояний строки режим редактирования уже завершён.
4271
+ Попытка
4272
+ Таблица.ЗакончитьРедактированиеСтроки();
4273
+ Исключение
4274
+ КонецПопытки;
4058
4275
  КонецПопытки;
4059
- Возврат Новый Структура("set,table,field,value", Истина, ИмяТаблицы, ИмяПоля, Значение);
4276
+
4277
+ Проверка = ЗначениеКолонкиТекущейСтрокиMCP(Таблица, ИмяПоля, ИскатьПоЗаголовку);
4278
+ Проверено = Проверка.found И ЗначенияСовпадаютДляПроверкиMCP(Проверка.value, Значение);
4279
+ Если Не Проверено Тогда
4280
+ ДополнениеОшибки = ?(ПустаяСтрока(ОшибкаУстановки), "", "; ошибка set_field_value: " + ОшибкаУстановки);
4281
+ ВызватьИсключение "Поле таблицы " + ИмяПоля + " установлено через " + ИмяРедактора + ", но значение не подтвердилось; текущее значение: " + Проверка.value + ДополнениеОшибки;
4282
+ КонецЕсли;
4283
+
4284
+ МетодУстановки = СвойствоСтруктуры(РезультатУстановки, "method", "set_field_value");
4285
+ Если Не ПустаяСтрока(ОшибкаУстановки) Тогда
4286
+ МетодУстановки = МетодУстановки + "+verified_after_exception";
4287
+ КонецЕсли;
4288
+ Возврат Новый Структура("set,table,field,value,editor_field,verified,verified_key,verified_value,method,current_row", Истина, ИмяТаблицы, ИмяПоля, Значение, ИмяРедактора, Проверено, Проверка.key, Проверка.value, МетодУстановки, СоответствиеВСтруктуру(Таблица.ПолучитьТекущуюСтроку()));
4289
+ КонецФункции
4290
+
4291
+ &НаКлиенте
4292
+ Функция СтруктураИзJSONПараметраMCP(Параметры, ИмяПараметра)
4293
+ СтрокаJSON = СвойствоСтруктуры(Параметры, ИмяПараметра, "{}");
4294
+ Если ТипЗнч(СтрокаJSON) = Тип("Структура") Тогда
4295
+ Возврат СтрокаJSON;
4296
+ КонецЕсли;
4297
+ Возврат JSONВЗначение(СтрокаJSON);
4298
+ КонецФункции
4299
+
4300
+ &НаКлиенте
4301
+ Функция УстановитьЗначенияСтрокиТаблицыMCP(Параметры)
4302
+ Значения = СтруктураИзJSONПараметраMCP(Параметры, "values");
4303
+ ИмяТаблицы = СвойствоСтруктуры(Параметры, "table", "ДополнительнаяИнформация");
4304
+ ИскатьПоЗаголовку = СвойствоСтруктуры(Параметры, "search_by_title", Ложь);
4305
+ Результаты = Новый Массив;
4306
+ Для Каждого КЗ Из Значения Цикл
4307
+ ПараметрыПоля = Новый Структура("table,field,value,search_by_title", ИмяТаблицы, Строка(КЗ.Ключ), Строка(КЗ.Значение), ИскатьПоЗаголовку);
4308
+ Результаты.Добавить(УстановитьПолеТаблицыMCP(ПараметрыПоля));
4309
+ КонецЦикла;
4310
+ Возврат Новый Структура("set,table,values,results", Истина, ИмяТаблицы, Значения, Результаты);
4311
+ КонецФункции
4312
+
4313
+ &НаКлиенте
4314
+ Функция УстановитьЗначенияПолейФормыMCP(Параметры)
4315
+ Значения = СтруктураИзJSONПараметраMCP(Параметры, "values");
4316
+ ИскатьПоЗаголовку = СвойствоСтруктуры(Параметры, "search_by_title", Ложь);
4317
+ МаксимальнаяГлубина = СвойствоСтруктуры(Параметры, "max_depth", 20);
4318
+ Результаты = Новый Массив;
4319
+ Для Каждого КЗ Из Значения Цикл
4320
+ ПараметрыПоля = Новый Структура("field_name,value,search_by_title,max_depth", Строка(КЗ.Ключ), Строка(КЗ.Значение), ИскатьПоЗаголовку, МаксимальнаяГлубина);
4321
+ Результаты.Добавить(УстановитьЗначениеПоля(ПараметрыПоля));
4322
+ КонецЦикла;
4323
+ Возврат Новый Структура("set,values,results", Истина, Значения, Результаты);
4060
4324
  КонецФункции
4061
4325
 
4062
4326
  &НаКлиенте
@@ -3294,7 +3294,59 @@ async def tabular_document_save(
3294
3294
  session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
3295
3295
  ) -> Any:
3296
3296
  """Save a tabular document field to a file in the selected format."""
3297
- return await _bridge_call_recorded(session_id, "tabular_document_save", {"name": name, "title": title, "path": path, "format": format, "max_depth": max_depth})
3297
+ result = await _bridge_call_recorded(session_id, "tabular_document_save", {"name": name, "title": title, "path": path, "format": format, "max_depth": max_depth})
3298
+ return _select_saved_tabular_document_file(result, format)
3299
+
3300
+
3301
+ def _select_saved_tabular_document_file(result: Any, format: str) -> Any:
3302
+ if not isinstance(result, dict):
3303
+ return result
3304
+ files = result.get("files")
3305
+ if not isinstance(files, list) or not files:
3306
+ return result
3307
+ selected = _find_saved_tabular_document_file(files, format)
3308
+ if selected is None:
3309
+ result["selected"] = None
3310
+ return result
3311
+ selected_path = selected.get("path")
3312
+ requested_path = result.get("path")
3313
+ if isinstance(selected_path, str) and isinstance(requested_path, str) and requested_path and selected_path != requested_path:
3314
+ with suppress(Exception):
3315
+ shutil.copyfile(selected_path, requested_path)
3316
+ selected = dict(selected)
3317
+ selected["copied_to"] = requested_path
3318
+ result["path"] = requested_path
3319
+ result["selected"] = selected
3320
+ result["saved"] = True
3321
+ return result
3322
+
3323
+
3324
+ def _find_saved_tabular_document_file(files: list[Any], format: str) -> dict[str, Any] | None:
3325
+ wanted = format.lower()
3326
+ for item in files:
3327
+ if not isinstance(item, dict):
3328
+ continue
3329
+ file_path = item.get("path")
3330
+ if not isinstance(file_path, str):
3331
+ continue
3332
+ p = Path(file_path)
3333
+ if not p.is_file():
3334
+ continue
3335
+ try:
3336
+ header = p.read_bytes()[:8]
3337
+ except OSError:
3338
+ continue
3339
+ if wanted == "pdf" and header.startswith(b"%PDF"):
3340
+ return item
3341
+ if wanted in {"xlsx", "docx"} and header.startswith(b"PK\x03\x04"):
3342
+ return item
3343
+ if wanted == "xls" and header.startswith(bytes.fromhex("d0cf11e0a1b11ae1")):
3344
+ return item
3345
+ if wanted in {"html", "htm"} and (header.startswith(b"<") or header.startswith(b"\xef\xbb\xbf<")):
3346
+ return item
3347
+ if wanted in {"txt", "mxl"} and p.stat().st_size > 0:
3348
+ return item
3349
+ return None
3298
3350
 
3299
3351
 
3300
3352
  @mcp.tool()
@@ -3871,11 +3923,40 @@ async def table_set_field(
3871
3923
  field: Annotated[str, "Column field technical name or title"],
3872
3924
  value: Annotated[str, "Value to set"],
3873
3925
  search_by_title: Annotated[bool, "If true, match visible title instead of object name"] = False,
3926
+ max_depth: Annotated[int, "Maximum UI search depth"] = 20,
3927
+ *,
3928
+ session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
3929
+ ) -> Any:
3930
+ """Set a field in the currently edited row of a form table.
3931
+
3932
+ Finds the editor field for the column (e.g. ТоварыНоменклатура), activates it,
3933
+ delegates to the general set_field_value logic, ends row editing, and verifies
3934
+ the value in the current table row."""
3935
+ return await _bridge_call_recorded(session_id, "table_set_field", {"table": table, "field": field, "value": value, "search_by_title": search_by_title, "max_depth": max_depth})
3936
+
3937
+
3938
+ @mcp.tool()
3939
+ async def table_set_row_values(
3940
+ table: Annotated[str, "Table technical name"],
3941
+ values: Annotated[str, "JSON object mapping column names/titles to values"],
3942
+ search_by_title: Annotated[bool, "If true, match visible title instead of object name"] = False,
3943
+ *,
3944
+ session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
3945
+ ) -> Any:
3946
+ """Set multiple fields in the current row of a form table and verify each value."""
3947
+ return await _bridge_call_recorded(session_id, "table_set_row_values", {"table": table, "values": values, "search_by_title": search_by_title})
3948
+
3949
+
3950
+ @mcp.tool()
3951
+ async def set_form_fields(
3952
+ values: Annotated[str, "JSON object mapping field names/titles to values"],
3953
+ search_by_title: Annotated[bool, "If true, match visible title instead of object name"] = False,
3954
+ max_depth: Annotated[int, "Maximum UI search depth"] = 20,
3874
3955
  *,
3875
3956
  session_id: Annotated[str, "Session id"] = DEFAULT_SESSION,
3876
3957
  ) -> Any:
3877
- """Set a field in the currently edited row of a form table."""
3878
- return await _bridge_call_recorded(session_id, "table_set_field", {"table": table, "field": field, "value": value, "search_by_title": search_by_title})
3958
+ """Set multiple ordinary form fields using the same logic as set_field_value."""
3959
+ return await _bridge_call_recorded(session_id, "set_form_fields", {"values": values, "search_by_title": search_by_title, "max_depth": max_depth})
3879
3960
 
3880
3961
 
3881
3962
  @mcp.tool()
File without changes
File without changes
File without changes
File without changes
File without changes