diff --git a/bitra-cfe/CommonModules/bfd_IntegrationAPIHelpers.xml b/bitra-cfe/CommonModules/bfd_IntegrationAPIHelpers.xml new file mode 100644 index 0000000..448dd97 --- /dev/null +++ b/bitra-cfe/CommonModules/bfd_IntegrationAPIHelpers.xml @@ -0,0 +1,23 @@ + + + + + bfd_IntegrationAPIHelpers + + + ru + BFD: API helpers + + + + false + false + true + false + false + true + false + DontUse + + + diff --git a/bitra-cfe/CommonModules/bfd_IntegrationAPIHelpers/Ext/Module.bsl b/bitra-cfe/CommonModules/bfd_IntegrationAPIHelpers/Ext/Module.bsl new file mode 100644 index 0000000..c13f4fd --- /dev/null +++ b/bitra-cfe/CommonModules/bfd_IntegrationAPIHelpers/Ext/Module.bsl @@ -0,0 +1,130 @@ +//////////////////////////////////////////////////////////////////////////////// +// bfd_IntegrationAPIHelpers +// +// Назначение: вспомогательные процедуры/функции для bfd_IntegrationAPI +// (read-only REST API для проекта bit-flight-deck). +// +// Context: Server, Privileged. +//////////////////////////////////////////////////////////////////////////////// + +#Область ПрограммныйИнтерфейс + +// Формирует HTTPСервисОтвет с JSON-телом. +// Параметры: +// Данные — Произвольный (Структура / Массив / Число / Строка / Дата / Булево / Неопределено) +// Код — Число (по умолчанию 200) +// Возвращаемое значение: HTTPСервисОтвет +// +Функция СформироватьОтветJSON(Знач Данные, Знач Код = 200) Экспорт + + ЗаписьJSON = Новый ЗаписьJSON; + ПараметрыJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.БезПереносов, "", Ложь, "yyyy-MM-ddTHH:mm:ss"); + ЗаписьJSON.УстановитьСтроку(ПараметрыJSON); + ЗаписатьJSON(ЗаписьJSON, Данные); + + Ответ = Новый HTTPСервисОтвет(Код); + Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8"); + Ответ.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть(), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать); + Возврат Ответ; + +КонецФункции + +// Сериализованная ошибка. +// +Функция СформироватьОтветОшибки(Знач СообщениеОшибки, Знач Код = 500) Экспорт + + Структура = Новый Структура("error", СообщениеОшибки); + Возврат СформироватьОтветJSON(Структура, Код); + +КонецФункции + +// Парсит ISO 8601 дату из строки query-параметра. +// Возвращает Дату или '00010101' при ошибке/пусто. +// +Функция ПарсДатуISO(Знач СтрокаДаты) Экспорт + + Если ПустаяСтрока(СтрокаДаты) Тогда + Возврат Дата(1, 1, 1); + КонецЕсли; + + ОчищеннаяСтрока = СтрЗаменить(СтрЗаменить(СтрокаДаты, "T", " "), "Z", ""); + Попытка + Возврат Дата(ОчищеннаяСтрока); + Исключение + Возврат Дата(1, 1, 1); + КонецПопытки; + +КонецФункции + +// Строковое представление GUID ссылки или пустая строка. +// +Функция UUID(Знач Ссылка) Экспорт + + Если НЕ ЗначениеЗаполнено(Ссылка) Тогда + Возврат ""; + КонецЕсли; + Возврат Строка(Ссылка.УникальныйИдентификатор()); + +КонецФункции + +// Извлекает email из ТЧ КонтактнаяИнформация пользователя. +// +Функция ИзвлечьEmail(Знач Пользователь) Экспорт + + Если НЕ ЗначениеЗаполнено(Пользователь) Тогда + Возврат ""; + КонецЕсли; + + Попытка + Объект = Пользователь.ПолучитьОбъект(); + Исключение + Возврат ""; + КонецПопытки; + + Если Объект = Неопределено Тогда + Возврат ""; + КонецЕсли; + + Попытка + Для Каждого Стр Из Объект.КонтактнаяИнформация Цикл + ВидНаименование = ""; + Если ЗначениеЗаполнено(Стр.Вид) Тогда + ВидНаименование = Строка(Стр.Вид); + КонецЕсли; + Если СтрНайти(НРег(ВидНаименование), "email") > 0 ИЛИ СтрНайти(НРег(ВидНаименование), "почта") > 0 Тогда + Если ЗначениеЗаполнено(Стр.Представление) Тогда + Возврат Стр.Представление; + КонецЕсли; + КонецЕсли; + КонецЦикла; + Исключение + Возврат ""; + КонецПопытки; + + Возврат ""; + +КонецФункции + +// Лимит выборки из query-параметра. +// +Функция ПарсЛимит(Знач Запрос, Знач MaxDefault = 1000, Знач MaxAllowed = 10000) Экспорт + + Параметр = Запрос.ПараметрыЗапроса.Получить("limit"); + Если ПустаяСтрока(Параметр) Тогда + Возврат MaxDefault; + КонецЕсли; + + Попытка + Значение = Число(Параметр); + Исключение + Возврат MaxDefault; + КонецПопытки; + + Если Значение <= 0 Тогда + Возврат MaxDefault; + КонецЕсли; + Возврат Мин(Значение, MaxAllowed); + +КонецФункции + +#КонецОбласти diff --git a/bitra-cfe/Configuration.xml b/bitra-cfe/Configuration.xml new file mode 100644 index 0000000..3410e74 --- /dev/null +++ b/bitra-cfe/Configuration.xml @@ -0,0 +1,73 @@ + + + + + + 9cd510cd-abfc-11d4-9434-004095e12fc7 + 617fa0c0-9a19-4f15-bebd-1858bb176245 + + + 9fcd25a0-4822-11d4-9414-008048da11f9 + 12af5411-d422-4db1-aebd-095700acce53 + + + e3687481-0a87-462c-a166-9f34594f9bba + 2911c4d3-5fce-4d58-bcbe-b656f73272a9 + + + 9de14907-ec23-4a07-96f0-85521cb6b53b + f3a0b9ca-ad92-40f7-a543-1728863ba9a5 + + + 51f2d5d8-ea4d-4064-8892-82951750031e + e371da3e-ae95-4395-b867-456aaadbe66c + + + e68182ea-4237-4383-967f-90c1e3370bc7 + b7783cb4-7160-4c0b-a329-f7679c9f9bed + + + fb282519-d103-4dd3-bc12-cb271d631dfc + bf401d2b-0f97-4ebe-b962-ef712d65db37 + + + + Adopted + BIT_FlightDeck + + + ru + BIT Flight Deck Read-Only API + + + + AddOn + true + bfd_ + Version8_3_14 + ManagedApplication + + PlatformApplication + + Russian + + Role.bfd_ОсновнаяРоль + + bit-flight-deck + 1.0.0.1 + Language.Русский + + + + + + TaxiEnableVersion8_2 + + + Русский + bfd_ОсновнаяРоль + bfd_IntegrationAPIHelpers + bfd_IntegrationAPI + + + \ No newline at end of file diff --git a/bitra-cfe/HTTPServices/bfd_IntegrationAPI.xml b/bitra-cfe/HTTPServices/bfd_IntegrationAPI.xml new file mode 100644 index 0000000..4027626 --- /dev/null +++ b/bitra-cfe/HTTPServices/bfd_IntegrationAPI.xml @@ -0,0 +1,317 @@ + + + + + bfd_IntegrationAPI + + + ru + BFD: Integration API + + + + bfd-api + DontUse + 20 + + + + + Dictionaries + + + ru + Dictionaries + + + + + + + + Get + + + ru + Get + + + GET + DictionariesGet + + + + + + + EvaMappingClients + + + ru + Eva mapping clients + + + + + + + + Get + + + ru + Get + + + GET + EvaMappingClientsGet + + + + + + + Stages + + + ru + Stages + + + + + + + + Get + + + ru + Get + + + GET + StagesGet + + + + + + + Projects + + + ru + Projects + + + + + + + + Get + + + ru + Get + + + GET + ProjectsGet + + + + + + + DeptHistory + + + ru + Dept history + + + + + + + + Get + + + ru + Get + + + GET + DeptHistoryGet + + + + + + + WorkTypes + + + ru + Work types + + + + + + + + Get + + + ru + Get + + + GET + WorkTypesGet + + + + + + + EvaMappingProjects + + + ru + Eva mapping projects + + + + + + + + Get + + + ru + Get + + + GET + EvaMappingProjectsGet + + + + + + + Employees + + + ru + Employees + + + + + + + + Get + + + ru + Get + + + GET + EmployeesGet + + + + + + + ProjectRegister + + + ru + Project register + + + + + + + + Get + + + ru + Get + + + GET + ProjectRegisterGet + + + + + + + Health + + + ru + Health + + + + + + + + Get + + + ru + Get + + + GET + HealthGet + + + + + + + Works + + + ru + Works + + + + + + + + Get + + + ru + Get + + + GET + WorksGet + + + + + + + diff --git a/bitra-cfe/HTTPServices/bfd_IntegrationAPI/Ext/Module.bsl b/bitra-cfe/HTTPServices/bfd_IntegrationAPI/Ext/Module.bsl new file mode 100644 index 0000000..aeeb005 --- /dev/null +++ b/bitra-cfe/HTTPServices/bfd_IntegrationAPI/Ext/Module.bsl @@ -0,0 +1,477 @@ +//////////////////////////////////////////////////////////////////////////////// +// HTTPService bfd_IntegrationAPI — read-only REST API для bit-flight-deck. +// +// Корневой URL после публикации: http:////hs/bfd-api/v1/... +// +// Все методы GET. Аутентификация Basic (пользователь bfd_api_user). +// Ошибки → JSON {"error":"..."} с HTTP-кодом 500. +// Все ID объектов сериализуются как строковый UUID (без префикса класса). +//////////////////////////////////////////////////////////////////////////////// + +#Область ОбработчикиЗапросов + +// GET /v1/health +// +Функция HealthGet(Запрос) Экспорт + + Ответ = Новый Структура; + Ответ.Вставить("status", "ok"); + Ответ.Вставить("service", "bfd_IntegrationAPI"); + Ответ.Вставить("server_time", Формат(ТекущаяДатаСеанса(), "ДФ=yyyy-MM-ddTHH:mm:ss")); + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Ответ); + +КонецФункции + +// GET /v1/employees +// +Функция EmployeesGet(Запрос) Экспорт + + Попытка + Запрос1С = Новый Запрос; + Запрос1С.Текст = + "ВЫБРАТЬ + | Пользователи.Ссылка КАК Ссылка, + | Пользователи.Наименование КАК ФИО, + | Пользователи.EVA_ID КАК EvaID, + | Пользователи.Офис КАК Офис, + | Пользователи.Подразделение КАК Подразделение, + | Пользователи.Ставка КАК Ставка, + | Пользователи.Недействителен КАК Недействителен, + | Пользователи.ДолженЗаполнятьОтчет КАК ДолженЗаполнятьОтчет + |ИЗ + | Справочник.Пользователи КАК Пользователи + |ГДЕ + | НЕ Пользователи.ПометкаУдаления + |УПОРЯДОЧИТЬ ПО + | Пользователи.Наименование"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("id", bfd_IntegrationAPIHelpers.UUID(Выборка.Ссылка)); + Запись.Вставить("full_name", Выборка.ФИО); + Запись.Вставить("email", НРег(bfd_IntegrationAPIHelpers.ИзвлечьEmail(Выборка.Ссылка))); + Запись.Вставить("eva_id", Выборка.EvaID); + Запись.Вставить("office", Строка(Выборка.Офис)); + Запись.Вставить("department", Строка(Выборка.Подразделение)); + Запись.Вставить("rate", Выборка.Ставка); + Запись.Вставить("is_active", НЕ Выборка.Недействителен); + Запись.Вставить("should_fill_report", Выборка.ДолженЗаполнятьОтчет); + Результат.Добавить(Запись); + КонецЦикла; + + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/works?modified_since=&limit=N +// +Функция WorksGet(Запрос) Экспорт + + Попытка + МодифицированоПосле = bfd_IntegrationAPIHelpers.ПарсДатуISO(Запрос.ПараметрыЗапроса.Получить("modified_since")); + Лимит = bfd_IntegrationAPIHelpers.ПарсЛимит(Запрос, 1000, 10000); + + Запрос1С = Новый Запрос; + Запрос1С.УстановитьПараметр("МодифицированоПосле", МодифицированоПосле); + + Запрос1С.Текст = + "ВЫБРАТЬ ПЕРВЫЕ " + Формат(Лимит, "ЧГ=") + " + | Работы.Ссылка КАК Ссылка, + | Работы.Номер КАК Номер, + | Работы.Дата КАК Дата, + | Работы.Исполнитель КАК Исполнитель, + | Работы.Автор КАК Автор, + | Работы.Подразделение КАК Подразделение, + | Работы.Офис КАК Офис, + | Работы.Утвержден КАК Утвержден, + | Работы.Комментарий КАК Комментарий, + | Работы.ИтогоЧасов КАК ИтогоЧасов + |ИЗ + | Документ.Работы КАК Работы + |ГДЕ + | Работы.Дата >= &МодифицированоПосле + | И Работы.Проведен + |УПОРЯДОЧИТЬ ПО + | Работы.Дата УБЫВ"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("id", bfd_IntegrationAPIHelpers.UUID(Выборка.Ссылка)); + Запись.Вставить("number", Выборка.Номер); + Запись.Вставить("date", Формат(Выборка.Дата, "ДФ=yyyy-MM-ddTHH:mm:ss")); + Запись.Вставить("employee_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Исполнитель)); + Запись.Вставить("author_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Автор)); + Запись.Вставить("department", Строка(Выборка.Подразделение)); + Запись.Вставить("office", Строка(Выборка.Офис)); + Запись.Вставить("approved", Выборка.Утвержден); + Запись.Вставить("comment", Выборка.Комментарий); + Запись.Вставить("total_hours", Выборка.ИтогоЧасов); + Запись.Вставить("rows", ПолучитьСтрокиРаботы(Выборка.Ссылка)); + Результат.Добавить(Запись); + КонецЦикла; + + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/projects?modified_since= +// +Функция ProjectsGet(Запрос) Экспорт + + Попытка + Запрос1С = Новый Запрос; + Запрос1С.Текст = + "ВЫБРАТЬ + | Проекты.Ссылка КАК Ссылка, + | Проекты.Код КАК Код, + | Проекты.Наименование КАК Наименование, + | Проекты.EVA_ID КАК EvaID, + | Проекты.Кодификатор КАК Кодификатор, + | Проекты.Владелец КАК Клиент, + | Проекты.Владелец.Наименование КАК КлиентИмя, + | Проекты.Договор КАК Договор, + | Проекты.Конфигурация КАК Конфигурация, + | Проекты.Офис КАК Офис, + | Проекты.РуководительПроекта КАК РуководительПроекта, + | Проекты.МенеджерПроекта КАК МенеджерПроекта, + | Проекты.СтатусПроекта КАК СтатусПроекта, + | Проекты.СтатусДоговора КАК СтатусДоговора, + | Проекты.ДатаСтарта КАК ДатаСтарта, + | Проекты.ДатаФиниш КАК ДатаФиниш, + | Проекты.Бюджет КАК Бюджет, + | Проекты.ПометкаУдаления КАК ПометкаУдаления + |ИЗ + | Справочник.Проекты КАК Проекты"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("id", bfd_IntegrationAPIHelpers.UUID(Выборка.Ссылка)); + Запись.Вставить("code", Выборка.Код); + Запись.Вставить("name", Выборка.Наименование); + Запись.Вставить("eva_id", Выборка.EvaID); + Запись.Вставить("project_code", Выборка.Кодификатор); + Запись.Вставить("client_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Клиент)); + Запись.Вставить("client_name", Выборка.КлиентИмя); + Запись.Вставить("contract_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Договор)); + Запись.Вставить("configuration", Строка(Выборка.Конфигурация)); + Запись.Вставить("office", Строка(Выборка.Офис)); + Запись.Вставить("manager_id", bfd_IntegrationAPIHelpers.UUID(Выборка.РуководительПроекта)); + Запись.Вставить("project_manager_id", bfd_IntegrationAPIHelpers.UUID(Выборка.МенеджерПроекта)); + Запись.Вставить("status", Строка(Выборка.СтатусПроекта)); + Запись.Вставить("contract_status", Строка(Выборка.СтатусДоговора)); + Запись.Вставить("start_date", ?(ЗначениеЗаполнено(Выборка.ДатаСтарта), Формат(Выборка.ДатаСтарта, "ДФ=yyyy-MM-dd"), "")); + Запись.Вставить("finish_date", ?(ЗначениеЗаполнено(Выборка.ДатаФиниш), Формат(Выборка.ДатаФиниш, "ДФ=yyyy-MM-dd"), "")); + Запись.Вставить("budget", Выборка.Бюджет); + Запись.Вставить("deleted", Выборка.ПометкаУдаления); + Результат.Добавить(Запись); + КонецЦикла; + + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/stages +// +Функция StagesGet(Запрос) Экспорт + + Попытка + Запрос1С = Новый Запрос; + Запрос1С.Текст = + "ВЫБРАТЬ + | ЭтапыПроектов.Ссылка КАК Ссылка, + | ЭтапыПроектов.Код КАК Код, + | ЭтапыПроектов.Наименование КАК Наименование, + | ЭтапыПроектов.Владелец КАК Владелец, + | ЭтапыПроектов.Кодификатор КАК Кодификатор, + | ЭтапыПроектов.ДатаНачала КАК ДатаНачала, + | ЭтапыПроектов.ДатаОкончания КАК ДатаОкончания, + | ЭтапыПроектов.Выполнен КАК Выполнен, + | ЭтапыПроектов.АктПодписан КАК АктПодписан, + | ЭтапыПроектов.ПометкаУдаления КАК ПометкаУдаления + |ИЗ + | Справочник.ЭтапыПроектов КАК ЭтапыПроектов"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("id", bfd_IntegrationAPIHelpers.UUID(Выборка.Ссылка)); + Запись.Вставить("code", Выборка.Код); + Запись.Вставить("name", Выборка.Наименование); + Запись.Вставить("project_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Владелец)); + Запись.Вставить("project_code",Выборка.Кодификатор); + Запись.Вставить("start_date", ?(ЗначениеЗаполнено(Выборка.ДатаНачала), Формат(Выборка.ДатаНачала, "ДФ=yyyy-MM-dd"), "")); + Запись.Вставить("end_date", ?(ЗначениеЗаполнено(Выборка.ДатаОкончания), Формат(Выборка.ДатаОкончания, "ДФ=yyyy-MM-dd"), "")); + Запись.Вставить("completed", Выборка.Выполнен); + Запись.Вставить("act_signed", Выборка.АктПодписан); + Запись.Вставить("deleted", Выборка.ПометкаУдаления); + Результат.Добавить(Запись); + КонецЦикла; + + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/work_types — значения Enum.ВидыРабот. +// +Функция WorkTypesGet(Запрос) Экспорт + + Попытка + Результат = Новый Массив; + МетаОбъект = Метаданные.Перечисления.ВидыРабот; + Для Каждого ЗначениеМД Из МетаОбъект.ЗначенияПеречисления Цикл + Запись = Новый Структура; + Запись.Вставить("code", ЗначениеМД.Имя); + Запись.Вставить("label", ЗначениеМД.Синоним); + Результат.Добавить(Запись); + КонецЦикла; + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/dictionaries — справочники с редким обновлением. +// +Функция DictionariesGet(Запрос) Экспорт + + Попытка + Ответ = Новый Структура; + Ответ.Вставить("offices", ПростойСправочник("Офис")); + Ответ.Вставить("departments", ПростойСправочник("Подразделение")); + Ответ.Вставить("managers", ПростойСправочник("Менеджеры")); + Ответ.Вставить("configurations", ПростойСправочник("Конфигурации")); + Ответ.Вставить("contracts", ПростойСправочник("Договоры")); + Ответ.Вставить("planning_scenarios", ПростойСправочник("СценарииПланирования")); + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Ответ); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/dept_history?modified_since= +// +Функция DeptHistoryGet(Запрос) Экспорт + + Попытка + МодифицированоПосле = bfd_IntegrationAPIHelpers.ПарсДатуISO(Запрос.ПараметрыЗапроса.Получить("modified_since")); + + Запрос1С = Новый Запрос; + Запрос1С.УстановитьПараметр("МодифицированоПосле", МодифицированоПосле); + Запрос1С.Текст = + "ВЫБРАТЬ + | ИсторияПодразделений.Период КАК Период, + | ИсторияПодразделений.Сотрудник КАК Сотрудник, + | ИсторияПодразделений.Подразделение КАК Подразделение + |ИЗ + | РегистрСведений.ПодразделениеСотрудников КАК ИсторияПодразделений + |ГДЕ + | ИсторияПодразделений.Период >= &МодифицированоПосле"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("period", Формат(Выборка.Период, "ДФ=yyyy-MM-dd")); + Запись.Вставить("employee_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Сотрудник)); + Запись.Вставить("department_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Подразделение)); + Запись.Вставить("department_name", Строка(Выборка.Подразделение)); + Результат.Добавить(Запись); + КонецЦикла; + + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/project_register?modified_since= +// Для MVP-3, на старте можно не использовать. +// +Функция ProjectRegisterGet(Запрос) Экспорт + + Попытка + МодифицированоПосле = bfd_IntegrationAPIHelpers.ПарсДатуISO(Запрос.ПараметрыЗапроса.Получить("modified_since")); + + Запрос1С = Новый Запрос; + Запрос1С.УстановитьПараметр("МодифицированоПосле", МодифицированоПосле); + Запрос1С.Текст = + "ВЫБРАТЬ + | Регистр.Период КАК Период, + | Регистр.Регистратор КАК Регистратор, + | Регистр.Сценарий КАК Сценарий, + | Регистр.Проект КАК Проект, + | Регистр.ЭтапПроекта КАК ЭтапПроекта, + | Регистр.ВидАналитики КАК ВидАналитики, + | Регистр.Исполнитель КАК Исполнитель, + | Регистр.Сумма КАК Сумма, + | Регистр.СуммаАкт КАК СуммаАкт, + | Регистр.СуммаРасход КАК СуммаРасход + |ИЗ + | РегистрНакопления.ОборотыПроектныхПоказателей_v2 КАК Регистр + |ГДЕ + | Регистр.Период >= &МодифицированоПосле"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("period", Формат(Выборка.Период, "ДФ=yyyy-MM-ddTHH:mm:ss")); + Запись.Вставить("registrator_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Регистратор)); + Запись.Вставить("scenario", Строка(Выборка.Сценарий)); + Запись.Вставить("project_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Проект)); + Запись.Вставить("stage_id", bfd_IntegrationAPIHelpers.UUID(Выборка.ЭтапПроекта)); + Запись.Вставить("analytics_type", Строка(Выборка.ВидАналитики)); + Запись.Вставить("employee_id", bfd_IntegrationAPIHelpers.UUID(Выборка.Исполнитель)); + Запись.Вставить("sum_total", Выборка.Сумма); + Запись.Вставить("sum_acted", Выборка.СуммаАкт); + Запись.Вставить("sum_expense", Выборка.СуммаРасход); + Результат.Добавить(Запись); + КонецЦикла; + + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +// GET /v1/eva_mapping/projects +// +Функция EvaMappingProjectsGet(Запрос) Экспорт + + Возврат ОтдатьРегистрМаппинга("СоответствиеПроектовEVA_РА"); + +КонецФункции + +// GET /v1/eva_mapping/clients +// +Функция EvaMappingClientsGet(Запрос) Экспорт + + Возврат ОтдатьРегистрМаппинга("СоответствиеКонтрагентовEVA_РА"); + +КонецФункции + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +// Возвращает массив строк ТЧ "Работы" документа. +// +Функция ПолучитьСтрокиРаботы(Знач ДокументСсылка) + + Объект = ДокументСсылка.ПолучитьОбъект(); + Если Объект = Неопределено Тогда + Возврат Новый Массив; + КонецЕсли; + + Строки = Новый Массив; + Для Каждого Стр Из Объект.Работы Цикл + Запись = Новый Структура; + Запись.Вставить("row_index", Стр.НомерСтроки); + Запись.Вставить("description", Стр.СодержаниеРабот); + Запись.Вставить("hours", Стр.КоличествоЧасов); + Запись.Вставить("work_type", Строка(Стр.ВидРаботы)); + Запись.Вставить("work_type_code", ?(ЗначениеЗаполнено(Стр.ВидРаботы), XMLСтрока(Стр.ВидРаботы), "")); + Запись.Вставить("client_id", bfd_IntegrationAPIHelpers.UUID(Стр.Клиент)); + Запись.Вставить("client_name", Строка(Стр.Клиент)); + Запись.Вставить("manager_id", bfd_IntegrationAPIHelpers.UUID(Стр.Менеджер)); + Запись.Вставить("project_id", bfd_IntegrationAPIHelpers.UUID(Стр.Проект)); + Запись.Вставить("stage_id", bfd_IntegrationAPIHelpers.UUID(Стр.Этап)); + Запись.Вставить("request_number", Стр.НомерЗаявки); + Запись.Вставить("lt_id", bfd_IntegrationAPIHelpers.UUID(Стр.ЛТ)); + Запись.Вставить("work_done", Стр.РаботаВыполнена); + Строки.Добавить(Запись); + КонецЦикла; + Возврат Строки; + +КонецФункции + +// Возвращает простой плоский справочник как массив объектов {id, code, name}. +// +Функция ПростойСправочник(Знач ИмяСправочника) + + Запрос1С = Новый Запрос; + Запрос1С.Текст = + "ВЫБРАТЬ + | Спр.Ссылка КАК Ссылка, + | Спр.Код КАК Код, + | Спр.Наименование КАК Наименование + |ИЗ + | Справочник." + ИмяСправочника + " КАК Спр + |ГДЕ + | НЕ Спр.ПометкаУдаления"; + + Выборка = Запрос1С.Выполнить().Выбрать(); + Результат = Новый Массив; + Пока Выборка.Следующий() Цикл + Запись = Новый Структура; + Запись.Вставить("id", bfd_IntegrationAPIHelpers.UUID(Выборка.Ссылка)); + Запись.Вставить("code", Выборка.Код); + Запись.Вставить("name", Выборка.Наименование); + Результат.Добавить(Запись); + КонецЦикла; + Возврат Результат; + +КонецФункции + +// Универсальный helper для регистров маппинга EVA_РА. +// Перебирает колонки регистра, ссылки сериализует UUID, даты ISO. +// +Функция ОтдатьРегистрМаппинга(Знач ИмяРегистра) + + Попытка + Запрос1С = Новый Запрос; + Запрос1С.Текст = "ВЫБРАТЬ * ИЗ РегистрСведений." + ИмяРегистра; + ТЗ = Запрос1С.Выполнить().Выгрузить(); + + Результат = Новый Массив; + Для Каждого Стр Из ТЗ Цикл + Запись = Новый Структура; + Для Каждого Колонка Из ТЗ.Колонки Цикл + Значение = Стр[Колонка.Имя]; + Если ТипЗнч(Значение) = Тип("Дата") Тогда + Запись.Вставить(Колонка.Имя, Формат(Значение, "ДФ=yyyy-MM-ddTHH:mm:ss")); + ИначеЕсли ТипЗнч(Значение) = Тип("Булево") ИЛИ ТипЗнч(Значение) = Тип("Число") ИЛИ ТипЗнч(Значение) = Тип("Строка") Тогда + Запись.Вставить(Колонка.Имя, Значение); + Иначе + Запись.Вставить(Колонка.Имя, ?(ЗначениеЗаполнено(Значение), bfd_IntegrationAPIHelpers.UUID(Значение), "")); + КонецЕсли; + КонецЦикла; + Результат.Добавить(Запись); + КонецЦикла; + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветJSON(Результат); + Исключение + Возврат bfd_IntegrationAPIHelpers.СформироватьОтветОшибки(ОписаниеОшибки()); + КонецПопытки; + +КонецФункции + +#КонецОбласти diff --git a/bitra-cfe/Languages/Русский.xml b/bitra-cfe/Languages/Русский.xml new file mode 100644 index 0000000..626f509 --- /dev/null +++ b/bitra-cfe/Languages/Русский.xml @@ -0,0 +1,13 @@ + + + + + + Adopted + Русский + + eb0c1f80-0194-40b4-8447-89fd07487e25 + ru + + + \ No newline at end of file diff --git a/bitra-cfe/README.md b/bitra-cfe/README.md new file mode 100644 index 0000000..14fa174 --- /dev/null +++ b/bitra-cfe/README.md @@ -0,0 +1,225 @@ +# BIT_FlightDeck — расширение BIT.RA с REST API для bit-flight-deck + +Расширение конфигурации (CFE) для **BIT.RA** (1С:Предприятие 8.3, режим совместимости `Version8_3_14`). Добавляет HTTP-сервис read-only для аналитического слоя проекта `bit-flight-deck`. Не изменяет существующие объекты конфигурации (никаких заимствований). + +## Состав расширения + +``` +bitra-cfe/ +├── Configuration.xml Заголовок расширения, Purpose=AddOn, Prefix=bfd_ +├── Languages/ +│ └── Русский.xml Язык расширения +├── Roles/ +│ └── bfd_ОсновнаяРоль/ ⚠ Дефолтная роль из cfe-init — НУЖНО НАСТРОИТЬ ПРАВА (см. ниже) +├── CommonModules/ +│ └── bfd_IntegrationAPIHelpers/ +│ └── Ext/Module.bsl Хелперы (JSON-ответ, парс даты, UUID, ИзвлечьEmail, ПарсЛимит) +└── HTTPServices/ + └── bfd_IntegrationAPI/ + └── Ext/Module.bsl 11 обработчиков GET-эндпоинтов +``` + +## Что нужно сделать 1С-разработчику (по шагам) + +### 1. Импорт расширения в Конфигуратор + +1. Открыть Конфигуратор BIT.RA. +2. **Конфигурация → Расширения конфигурации → Добавить** (или клавиша Insert на дереве расширений). +3. Указать имя расширения `BIT_FlightDeck`, назначение `Дополнение`, префикс `bfd_`. +4. В созданном расширении: **Конфигурация → Загрузить конфигурацию из файлов...** — указать каталог `bitra-cfe/`. +5. Проверить дерево объектов: должны быть `bfd_IntegrationAPI` (HTTP-сервис), `bfd_IntegrationAPIHelpers` (общий модуль), `bfd_ОсновнаяРоль` (роль). +6. **Обновить конфигурацию базы данных** (F7). + +### 2. Настройка роли `bfd_ОсновнаяРоль` + +Дефолтная роль из шаблона **пустая**. Нужно добавить права на чтение объектов, к которым обращается API: + +| Объект | Право | +|---|---| +| `Справочник.Пользователи` | Чтение, Просмотр | +| `Справочник.Подразделение` | Чтение, Просмотр | +| `Справочник.Офис` | Чтение, Просмотр | +| `Справочник.Менеджеры` | Чтение, Просмотр | +| `Справочник.Клиенты` | Чтение, Просмотр | +| `Справочник.Проекты` | Чтение, Просмотр | +| `Справочник.ЭтапыПроектов` | Чтение, Просмотр | +| `Справочник.Конфигурации` | Чтение, Просмотр | +| `Справочник.Договоры` | Чтение, Просмотр | +| `Справочник.СценарииПланирования` | Чтение, Просмотр | +| `Документ.Работы` | Чтение, Просмотр | +| `РегистрСведений.ПодразделениеСотрудников` | Чтение | +| `РегистрНакопления.ОборотыПроектныхПоказателей_v2` | Чтение | +| `РегистрСведений.СоответствиеПроектовEVA_РА` | Чтение | +| `РегистрСведений.СоответствиеКонтрагентовEVA_РА` | Чтение | +| `Перечисление.ВидыРабот` | (доступно по умолчанию) | +| `HTTPСервис.bfd_IntegrationAPI` | Использование | +| `ОбщийМодуль.bfd_IntegrationAPIHelpers` | Использование | + +Также **в свойстве расширения**: **«Активные роли» / «Назначаемые роли»** → отметить `bfd_ОсновнаяРоль`. + +### 3. Создание сервисного пользователя + +В режиме **Предприятие** (или в Конфигураторе → Администрирование → Пользователи): +1. Создать пользователя `bfd_api_user`. +2. Пароль — сгенерировать криптостойкий (~16 символов), сохранить отдельно для передачи в `.env` файла N8N. +3. Аутентификация: **Аутентификация 1С:Предприятия** (для Basic auth по HTTP). +4. Запретить интерактивный вход (галка «Запрещено изменять пароль», без членства в группах с интерактивными правами). +5. Назначить роли: + - `bfd_ОсновнаяРоль` (из расширения). + - Также понадобятся базовые БСП-роли: `БазовыеПрава` (или эквивалент в BIT.RA), чтобы пользователь мог войти в сеанс. + +### 4. Публикация HTTP-сервиса через Apache + +``` +В Конфигураторе: Администрирование → Публикация на веб-сервере... +``` + +В диалоге: +- **Web-сервер:** Apache 2.4 (выбрать установленный). +- **Каталог:** имя публикации (например `bitra`) — будет частью URL. +- **Поставить галку:** «Публиковать HTTP-сервисы расширений конфигурации по умолчанию». +- В списке `HTTP-сервисы` отметить `bfd_IntegrationAPI`. +- ОК → перезапуск Apache. + +URL сервиса после публикации: `http:////hs/bfd-api/v1/...` + +Пример (полный URL): +``` +http://server.local/bitra/hs/bfd-api/v1/health +http://server.local/bitra/hs/bfd-api/v1/employees +http://server.local/bitra/hs/bfd-api/v1/works?modified_since=2026-05-01 +``` + +### 5. Тестирование через curl + +```bash +# health (без auth тоже работает, но Basic auth нужна для остальных) +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/health + +# Сотрудники +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/employees | head -c 500 + +# Работы за вчера +curl -u bfd_api_user: "http://server.local/bitra/hs/bfd-api/v1/works?modified_since=$(date -d 'yesterday' '+%Y-%m-%d')" + +# Проекты +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/projects | head -c 1000 + +# Этапы +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/stages | head -c 500 + +# Виды работ +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/work_types + +# Справочники (одним запросом) +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/dictionaries | head -c 2000 + +# История подразделений +curl -u bfd_api_user: "http://server.local/bitra/hs/bfd-api/v1/dept_history?modified_since=2024-01-01" | head -c 500 + +# Маппинг EVA проектов (может быть пустым — пользователь подтвердил что интеграция мёртвая) +curl -u bfd_api_user: http://server.local/bitra/hs/bfd-api/v1/eva_mapping/projects +``` + +### 6. Передать команде N8N (роль владельца проекта) + +После успешной публикации передать: +- `BITRA_BASE_URL` — `http:////hs/bfd-api/v1` +- `BITRA_USER` — `bfd_api_user` +- `BITRA_PASSWORD` — пароль + +Эти значения попадают в файл `.env` проекта `bit-flight-deck`. На стороне N8N будут настроены HTTP Request ноды с Basic-auth. + +## Список эндпоинтов (полная карта) + +| Метод | URL | Назначение | Параметры | +|---|---|---|---| +| GET | `/v1/health` | Healthcheck | — | +| GET | `/v1/employees` | `Справочник.Пользователи` | — | +| GET | `/v1/works` | `Документ.Работы` + ТЧ | `modified_since`, `limit` | +| GET | `/v1/projects` | `Справочник.Проекты` | — | +| GET | `/v1/stages` | `Справочник.ЭтапыПроектов` | — | +| GET | `/v1/work_types` | `Перечисление.ВидыРабот` | — | +| GET | `/v1/dictionaries` | Офисы/Подразделения/Менеджеры/Конфигурации/Договоры/Сценарии | — | +| GET | `/v1/dept_history` | `РегистрСведений.ПодразделениеСотрудников` | `modified_since` | +| GET | `/v1/project_register` | `РегНак.ОборотыПроектныхПоказателей_v2` (MVP-3) | `modified_since` | +| GET | `/v1/eva_mapping/projects` | `РегистрСведений.СоответствиеПроектовEVA_РА` | — | +| GET | `/v1/eva_mapping/clients` | `РегистрСведений.СоответствиеКонтрагентовEVA_РА` | — | + +## Формат ответа + +Всегда JSON, UTF-8 (без BOM), массив объектов или объект-обёртка. + +### Пример ответа `/v1/employees` + +```json +[ + { + "id": "f5631644-1948-11ee-94f0-c578ab9a5932", + "full_name": "Иванов Иван", + "email": "iivanov@1cbit.ru", + "eva_id": "CmfPerson:abc...", + "office": "ЕКБ", + "department": "Группа РП №2", + "rate": 1500.00, + "is_active": true, + "should_fill_report": true + } +] +``` + +### Пример ответа `/v1/works` + +```json +[ + { + "id": "...", + "number": "WK-000123", + "date": "2026-05-13T00:00:00", + "employee_id": "...", + "department": "Группа РП №2", + "office": "ЕКБ", + "approved": true, + "total_hours": 8.0, + "comment": "", + "rows": [ + { + "row_index": 1, + "description": "Разработка отчёта по продажам", + "hours": 4.0, + "work_type": "ЛУРВ (платно)", + "work_type_code": "ЛУРВ", + "client_id": "...", + "client_name": "ООО Ромашка", + "manager_id": "...", + "project_id": "...", + "stage_id": "...", + "request_number": "RQ-555", + "lt_id": "", + "work_done": true + } + ] + } +] +``` + +### Ошибки + +При исключении внутри обработчика возвращается: +```json +{ "error": "Описание ошибки 1С" } +``` +с HTTP-кодом 500. + +## Безопасность + +- Все эндпоинты требуют Basic Auth от `bfd_api_user`. +- Modul `bfd_IntegrationAPIHelpers` помечен как `Privileged=true` — внутри него игнорируются ограничения RLS. Пользователь BFD_api имеет доступ ко всем строкам справочников/документов через API. **Это намеренно** — аналитический слой должен видеть всё. +- Пароль `bfd_api_user` хранится в `.env` проекта (не в git). +- Доступ к публикации Apache желательно ограничить firewall'ом IP-адресом N8N-сервера (или ходить через CF Tunnel/VPN внутри сети). + +## Связанные документы + +- Спецификация: [`../docs/superpowers/specs/2026-05-13-mvp1-workload-design.md`](../docs/superpowers/specs/2026-05-13-mvp1-workload-design.md) +- План: [`../docs/superpowers/plans/2026-05-13-mvp1-workload.md`](../docs/superpowers/plans/2026-05-13-mvp1-workload.md) — Phase 3 +- Решения по транспорту: см. memory `architecture_transport_decisions` diff --git a/bitra-cfe/Roles/bfd_ОсновнаяРоль.xml b/bitra-cfe/Roles/bfd_ОсновнаяРоль.xml new file mode 100644 index 0000000..6bab539 --- /dev/null +++ b/bitra-cfe/Roles/bfd_ОсновнаяРоль.xml @@ -0,0 +1,10 @@ + + + + + bfd_ОсновнаяРоль + + + + + \ No newline at end of file