Online телефонный справочник из 1С: Зарплата и управление персоналом

Обмен - Интеграция с WEB

В интернете представлено много реализаций online телефонных справочников организаций. Есть справочники, которые использует для хранения информации базу Active Directory (LDAP), есть справочники, которые реализованы с использованием СУБД (например, MySQL). Но я не нашел справочника, который использует информацию из базы 1С. Далее я рассмотрю данную разработку.

Во многих организациях используются телефонные справочники сотрудников. Сначала у нас был красивый бумажный вариант, разработанный в редакторе векторной графики. Но постепенно мне стало надоедать, что приходится его распечатать, затем вырезать и скрепить, а таких справочников варировалось от 10 до 25 штук. В общем, занимало много времени, и особого желания заниматься этим не было. Далее справочник эволюционировал в электронную версию в Excel. Для меня это был идеальный вариант: работает поиск, простота заполнения. Но акктуализировать информацию приходилось мне, возможно из-за сложности формы, а возможно из-за нежелания сотрудника заниматься изучением справочника. В общем со временем мне надоел и этот вариант.  Поспрашивав друзей из других компаний,  я выяснил, что online телефонный справочник у них реализован с использованием LDAP или PHP+MySQL. Мне не понравились данные реализации по ряду причин:

  • при использовании LDAP пришлось бы забивать в базу контакты людей, которые не пользуются корпоративной сетью вообще;
  • при использовании LDAP для поддержания акктуальной информации пришлось кому-то из сотрудников давать доступ к серверу, а точнее к редактированию данных LDAP;
  • при использовании PHP+MySQL необходимо было бы писать и серверную, и клиентскую часть с нуля, либо модифицировать наработки товарищей под свои нужды. Поскольку, я не силен в PHP, то рассматривать данный вариант не стал.

В итоге я решил использовать в качестве хранения и обработки данных 1C, а для удобного вывода HTML и JavaScript. 

Задача разделилась на 3 этапа:

1. Разработка серверной части, т.е.  разработать HTTP-сервис для конфигурации 1С: ЗУП

2.  Разработка клиентской части. Вот тут для меня было сложнее всего, поскольку HTML и JavaScript я знал меньше чем на базовом уровне.

3. Публикация на web-сервере. Об этом я в статье писать не буду, поскольку информации в интернете, да и на infostart.ru много по этому вопросу.

В результате получилась система отображающая контактыне данные о сотрудниках и их ближайшие дни рождения. Итак начнем.

Разработка серверной части

Для начала давайте разберемся, как работать с  HTTP-сервисами в 1С. HTTP-сервисы представляют обработчики HTTP-запросов по определенному URL. URL HTTP-сервиса используется специальный, например:

http://<адрес сервера>/<имя базы>/hs/<корневой URL>/<относительный URL>

где

  • адрес сервера - это адрес серевера публикации базы 1С;
  • имя базы - это название базы данных конфигурации 1С;
  • hs - указывает на то, что мы обращаемся к HTTP-сервису;
  • корневой URL - это группа запросов, объеденных общим смыслом. Указывается в свойствах HTTP-запроса;
  • относительный URL - это сам запрос, который может использоваться по шаблону и указывается в объекте Шаблон URL.

Для телефонного справочника используем корневой URL - person, а относительные URL следующие (рисунок 1):

  1. personList - получение списка сотрудников;
  2. personInfo - получение контактной информации по сотруднику;
  3. birthdayList - получение списка дней рождения и дней оставшихся до него.
Для написания HTTP-сервиса используем методы в шаблонах URL, которые связываются в модуле HTTP-сервиса с функциями. В модуле HTTP-сервиса будут 3 функции getPersonList, getPersonInfo, getBirthdayList.
Конфигуратор 1С: ЗУП
Рисунок 1. HTTP-сервис и шаблоны URL
Алгоритмы трех функций очень схожи и сводятся к одному:
  1. Проверяем парметры HTTP-запроса;
  2. Составляем запрос к базе данных
  3. Обрабатываем результаты базы данных в объекты массив и структуры
  4. Формируем из полученных объектов сериализированную строку JSON
  5. Формируем ответ и отправляем обратно клиенту

Поскольку, алгоритмы схожи, то расмотрим только функцию personList, если вы поймете как работает эта функция, то разобраться с другими не составит труда. При формировании HTTP-ответа мы создаем JSON строку, в которой будут содержаться данные для отображения.  

Для начала создадим объект HTTPСервисОтвет с числовым параметром, соответствующий коду HTTP-состояния и укажем тип возвращаемых данных (javascript). Более подробнее про коды HTTP состояния можно почитать тут.

Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-type","application/javascript");

Пока работа с объектом HTTPСервисОтвет закончена. Для того, чтобы получить параметры от клиента мы обрабатываем Запрос методом Получить, аргументом которого идет название параметра:

ПараметрСостояния = ?(Запрос.ПараметрыЗапроса.Получить("fired") = "0", Перечисления.СостоянияСотрудника.Увольнение, Неопределено);

В данном случае, параметр состояния указывает на состояние сотрудника: уволен или нет. Поскольку, руководству иногда требуются контактные данные уволенных сотрудников, я решил реализовать данную возможность.

Дальше мы производим запрос к базе данных с параметром:

ЗапросКБазе = Новый Запрос;
	ЗапросКБазе.Параметры.Вставить("Состояние",ПараметрСостояния);
	ЗапросКБазе.Текст = "ВЫБРАТЬ
	                    |	ДанныеДляПодбораСотрудников.Сотрудник.Код КАК КодСотрудника,
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Наименование КАК Подразделение,
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Код КАК КодПодразделения,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Фамилия КАК Фамилия,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Имя КАК Имя,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Отчество КАК Отчество
	                    |ИЗ
	                    |	РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников
	                    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.СостоянияСотрудников.СрезПоследних КАК СостоянияСотрудниковСрезПоследних
	                    |		ПО ДанныеДляПодбораСотрудников.Сотрудник.Ссылка = СостоянияСотрудниковСрезПоследних.Сотрудник.Ссылка
	                    |ГДЕ
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Наименование <> """"
	                    |	И СостоянияСотрудниковСрезПоследних.Состояние.Ссылка <> &Состояние
	                    |
	                    |СГРУППИРОВАТЬ ПО
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Наименование,
	                    |	ДанныеДляПодбораСотрудников.Сотрудник.Код,
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Код,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Фамилия,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Имя,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Отчество
	                    |
	                    |УПОРЯДОЧИТЬ ПО
	                    |	Подразделение,
	                    |	Фамилия
	                    |ИТОГИ
	                    |	МАКСИМУМ(КодСотрудника),
	                    |	МАКСИМУМ(Подразделение)
	                    |ПО
	                    |	КодПодразделения";
	Результат = ЗапросКБазе.Выполнить();

Заострять внимание на зпросе не будем, поскольку я писать запросы не умею, тема публикации меньше отностися к запросам и поэтому перейду к обработке результатов запроса:

ВыборкаПодразделений = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам,"КодПодразделения");
СписокПодразделений = Новый Массив;
Пока ВыборкаПодразделений.Следующий() Цикл
	Подразделение = Новый Структура;
	Подразделение.Вставить("id",Строка(ВыборкаПодразделений.КодПодразделения));
	Подразделение.Вставить("open","true");	
	Подразделение.Вставить("value",Строка(ВыборкаПодразделений.Подразделение));
	СписокСотрудников = Новый Массив;
	ВыборкаСотрудники = ВыборкаПодразделений.Выбрать();
	Пока ВыборкаСотрудники.Следующий() Цикл
		Сотрудник = Новый Структура;
		ФИО = ВыборкаСотрудники.Фамилия + " " + Лев( ВыборкаСотрудники.Имя, 1) + ". " + Лев( ВыборкаСотрудники.Отчество, 1) + ".";
		Сотрудник.Вставить("id",Строка(ВыборкаСотрудники.КодСотрудника));
		Сотрудник.Вставить("value",Строка(ФИО));
		СписокСотрудников.Добавить(Сотрудник);		
	КонецЦикла; 	
	Подразделение.Вставить("data",СписокСотрудников);
	СписокПодразделений.Добавить(Подразделение);
КонецЦикла; 

При обработке результата формируется массив подразделений, состоящий из массива структур Подразделение. Подразделение включает в себя код подразделения, наименование и массив сотрудников. В подразделении также есть поле open, которое в дальнейшем в клиенте будет указывать на раскрытый элемент дерева. Структура Сотрудник состоит из фамилиии и инициалов.  Поля в структурах указываются на английском языке, чтобы в дальнейшем не возникло проблем при написании клиента.

Джсон = Новый ЗаписьJSON;
Джсон.УстановитьСтроку();
Настройка = Новый НастройкиСериализацииJSON;
ЗаписатьJSON(Джсон, СписокПодразделений,Настройка);
РезультатСтрока = Джсон.Закрыть(); 	
Ответ.УстановитьТелоИзСтроки(РезультатСтрока,КодировкаТекста.UTF8);
Возврат Ответ;

При формировании JSON использовал пример с its.1c.ru

Код функции GetPersonsList
Функция GetPersonsList(Запрос)
	Ответ = Новый HTTPСервисОтвет(200);
	Ответ.Заголовки.Вставить("Content-type","application/javascript");
	ПараметрСостояния = ?(Запрос.ПараметрыЗапроса.Получить("fired") = "0", Перечисления.СостоянияСотрудника.Увольнение, Неопределено); 

	ЗапросКБазе = Новый Запрос;
	ЗапросКБазе.Параметры.Вставить("Состояние",ПараметрСостояния);
	ЗапросКБазе.Текст = "ВЫБРАТЬ
	                    |	ДанныеДляПодбораСотрудников.Сотрудник.Код КАК КодСотрудника,
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Наименование КАК Подразделение,
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Код КАК КодПодразделения,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Фамилия КАК Фамилия,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Имя КАК Имя,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Отчество КАК Отчество
	                    |ИЗ
	                    |	РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников
	                    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.СостоянияСотрудников.СрезПоследних КАК СостоянияСотрудниковСрезПоследних
	                    |		ПО ДанныеДляПодбораСотрудников.Сотрудник.Ссылка = СостоянияСотрудниковСрезПоследних.Сотрудник.Ссылка
	                    |ГДЕ
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Наименование <> """"
	                    |	И СостоянияСотрудниковСрезПоследних.Состояние.Ссылка <> &Состояние
	                    |
	                    |СГРУППИРОВАТЬ ПО
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Наименование,
	                    |	ДанныеДляПодбораСотрудников.Сотрудник.Код,
	                    |	ДанныеДляПодбораСотрудников.Подразделение.Код,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Фамилия,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Имя,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Отчество
	                    |
	                    |УПОРЯДОЧИТЬ ПО
	                    |	Подразделение,
	                    |	Фамилия
	                    |ИТОГИ
	                    |	МАКСИМУМ(КодСотрудника),
	                    |	МАКСИМУМ(Подразделение)
	                    |ПО
	                    |	КодПодразделения";
	Результат = ЗапросКБазе.Выполнить();
	ВыборкаПодразделений = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам,"КодПодразделения");
	СписокПодразделений = Новый Массив;
	Пока ВыборкаПодразделений.Следующий() Цикл
		Подразделение = Новый Структура;
		Подразделение.Вставить("id",Строка(ВыборкаПодразделений.КодПодразделения));
		Подразделение.Вставить("open","true");	
		Подразделение.Вставить("value",Строка(ВыборкаПодразделений.Подразделение));
		СписокСотрудников = Новый Массив;
		ВыборкаСотрудники = ВыборкаПодразделений.Выбрать();
		Пока ВыборкаСотрудники.Следующий() Цикл
			Сотрудник = Новый Структура;
			ФИО = ВыборкаСотрудники.Фамилия + " " + Лев( ВыборкаСотрудники.Имя, 1) + ". " + Лев( ВыборкаСотрудники.Отчество, 1) + ".";
			Сотрудник.Вставить("id",Строка(ВыборкаСотрудники.КодСотрудника));
			Сотрудник.Вставить("value",Строка(ФИО));
			СписокСотрудников.Добавить(Сотрудник);		
		КонецЦикла; 	
		Подразделение.Вставить("data",СписокСотрудников);
		СписокПодразделений.Добавить(Подразделение);
	КонецЦикла; 
	Джсон = Новый ЗаписьJSON;
	Джсон.УстановитьСтроку();
	Настройка = Новый НастройкиСериализацииJSON;
	ЗаписатьJSON(Джсон, СписокПодразделений,Настройка);
	РезультатСтрока = Джсон.Закрыть(); 	
	Ответ.УстановитьТелоИзСтроки(РезультатСтрока,КодировкаТекста.UTF8);
	Возврат Ответ;
КонецФункции
Код функции GetPersonInfo
Функция GetPersonInfo(Запрос)
	Ответ = Новый HTTPСервисОтвет(200);
	Ответ.Заголовки.Вставить("Content-type","application/javascript");
	КодФизлица = Запрос.ПараметрыЗапроса.Получить("id");

	ЗапросКБазе = Новый Запрос;
	ЗапросКБазе.Параметры.Вставить("КодФизлица",КодФизлица);
	ВидыКонтактнойИнформации = Новый Массив;
	ВидыКонтактнойИнформации.Добавить(Справочники.ВидыКонтактнойИнформации.НайтиПоНаименованию("Мобильный телефон").Ссылка);
	ВидыКонтактнойИнформации.Добавить(Справочники.ВидыКонтактнойИнформации.НайтиПоНаименованию("Email").Ссылка);
	ВидыКонтактнойИнформации.Добавить(Справочники.ВидыКонтактнойИнформации.НайтиПоНаименованию("Домашний телефон").Ссылка);
	ВидыКонтактнойИнформации.Добавить(Справочники.ВидыКонтактнойИнформации.НайтиПоНаименованию("Рабочий телефон").Ссылка);
	ВидыКонтактнойИнформации.Добавить(Справочники.ВидыКонтактнойИнформации.НайтиПоНаименованию("Адрес места проживания").Ссылка);
	ЗапросКБазе.УстановитьПараметр("ВидыКонтактов",ВидыКонтактнойИнформации);
	ЗапросКБазе.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
	                    |	ФизическиеЛицаКонтактнаяИнформация.Вид,
	                    |	ФизическиеЛицаКонтактнаяИнформация.Представление,
	                    |	ФизическиеЛицаКонтактнаяИнформация.Ссылка.Ссылка КАК СсылкаФизЛицо
	                    |ПОМЕСТИТЬ Контакты
	                    |ИЗ
	                    |	Справочник.ФизическиеЛица.КонтактнаяИнформация КАК ФизическиеЛицаКонтактнаяИнформация
	                    |ГДЕ
	                    |	ФизическиеЛицаКонтактнаяИнформация.Вид В(&ВидыКонтактов)
	                    |;
	                    |
	                    |////////////////////////////////////////////////////////////////////////////////
	                    |ВЫБРАТЬ
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.ФИО КАК ФИО,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения КАК ДатаРождения,
	                    |	ДанныеДляПодбораСотрудников.Должность.Наименование КАК Должность,
	                    |	Контакты.Вид КАК ТипКонтакта,
	                    |	Контакты.Представление КАК Контакт
	                    |ИЗ
	                    |	Контакты КАК Контакты
	                    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников
	                    |		ПО Контакты.СсылкаФизЛицо = ДанныеДляПодбораСотрудников.ФизическоеЛицо.Ссылка
	                    |ГДЕ
	                    |	ДанныеДляПодбораСотрудников.Сотрудник.Код = &КодФизлица
	                    |	И ДанныеДляПодбораСотрудников.Должность.Наименование <> """"
	                    |
	                    |СГРУППИРОВАТЬ ПО
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.ФИО,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения,
	                    |	ДанныеДляПодбораСотрудников.Должность.Наименование,
	                    |	Контакты.Вид,
	                    |	Контакты.Представление
	                    |
	                    |УПОРЯДОЧИТЬ ПО
	                    |	ФИО
	                    |ИТОГИ ПО
	                    |	ФИО";
	Результат = ЗапросКБазе.Выполнить();
	ВыборкаСотрудник = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам,"ФИО");
	Пока ВыборкаСотрудник.Следующий() Цикл
		Сотрудник = Новый Структура;
		Сотрудник.Вставить("fio",Строка(ВыборкаСотрудник.ФИО));
		СписокКонтактов = Новый Массив;
		ВыборкаКонтактов = ВыборкаСотрудник.Выбрать();
		Пока ВыборкаКонтактов.Следующий() Цикл
			Контакт = Новый Структура;
			Контакт.Вставить("type",Строка(ВыборкаКонтактов.ТипКонтакта));
			Контакт.Вставить("value",Строка(ВыборкаКонтактов.Контакт));
			Сотрудник.Вставить("birthday",Строка(Лев(ВыборкаКонтактов.ДатаРождения,10)));	
			Сотрудник.Вставить("who",Строка(ВыборкаКонтактов.Должность));
			СписокКонтактов.Добавить(Контакт);		
		КонецЦикла; 	
		Сотрудник.Вставить("contacts",СписокКонтактов);;
	КонецЦикла; 
	Джсон = Новый ЗаписьJSON;
	Джсон.УстановитьСтроку();
	Настройка = Новый НастройкиСериализацииJSON;
	ЗаписатьJSON(Джсон, Сотрудник, Настройка);
	РезультатСтрока = Джсон.Закрыть(); 	
	Ответ.УстановитьТелоИзСтроки(РезультатСтрока,КодировкаТекста.UTF8);
	Возврат Ответ;
	
КонецФункции
Код функции GetBirthdayList
Функция GetBirthdayList(Запрос)
	Ответ = Новый HTTPСервисОтвет(200);
	КоличествоДней = Запрос.ПараметрыЗапроса.Получить("days");
	ЗапросКБазе = Новый Запрос();
	ЗапросКБазе.Параметры.Вставить("Дней", Число(КоличествоДней));
	ЗапросКБазе.Параметры.Вставить("Сегодня", ТекущаяДата());
	ЗапросКБазе.Текст = "ВЫБРАТЬ
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Наименование КАК ФИО,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения КАК ДатаРождения,
	                    |	ДЕНЬГОДА(ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения) - ДЕНЬГОДА(&Сегодня) КАК ДоДняРождения
	                    |ИЗ
	                    |	РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников
	                    |ГДЕ
	                    |	ДЕНЬГОДА(ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения) - ДЕНЬГОДА(&Сегодня) <= &Дней
	                    |	И ДЕНЬГОДА(ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения) - ДЕНЬГОДА(&Сегодня) > 0
	                    |
	                    |СГРУППИРОВАТЬ ПО
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.ДатаРождения,
	                    |	ДанныеДляПодбораСотрудников.ФизическоеЛицо.Наименование
	                    |
	                    |УПОРЯДОЧИТЬ ПО
	                    |	ДоДняРождения";
	Результат = ЗапросКБазе.Выполнить();
	ВыборкаСотрудник = Результат.Выбрать();
	СписокСотрудников = Новый Массив;
	Пока ВыборкаСотрудник.Следующий() Цикл
		Сотрудник = Новый Структура;
		Сотрудник.Вставить("fio",Строка(ВыборкаСотрудник.ФИО));
		Сотрудник.Вставить("days",Строка(ВыборкаСотрудник.ДоДняРождения));	
		Сотрудник.Вставить("birthday",Строка(Лев(ВыборкаСотрудник.ДатаРождения,10)));	
		СписокСотрудников.Добавить(Сотрудник);
	КонецЦикла; 
	Джсон = Новый ЗаписьJSON;
	Джсон.УстановитьСтроку();
	Настройка = Новый НастройкиСериализацииJSON;
	ЗаписатьJSON(Джсон, СписокСотрудников, Настройка);
	РезультатСтрока = Джсон.Закрыть(); 	
	Ответ.УстановитьТелоИзСтроки(РезультатСтрока,КодировкаТекста.UTF8);
	Возврат Ответ;
КонецФункции

Алгоритм остальных функций аналогичен. Переходим к разработке клиентской части

Разработка клиентской части

При разработке клиентской части я использовал библиотеку Webix. Данная библиотека позволяет ускорить разработку интерфейса и его алгоритма работы, а также уменьшить количество строк для написания кода.

До использования библиотеки Webix я пробовал сам сделать интерфейс, но получилось мягко говоря не очень. Для сравнения покажу старый и новый интерфейс:

До использования Webix UI

Рисунок 2. Интерфейс до использования Webix UI

Webix UI

Рисунок 3. Исопльзование Webix UI

Помимо данной библиотеки клиентская часть, состоит из  следующих файлов:

  • index.html - точка входа, именно на эту страницу попадает клиент и с нее уже подгружаются logic.js и ui.js
  • logic.js - файл описывающий логику интерфейса: действия при нажатии на элементы, запросы к серверу и т. д.
  • ui.js - файл описывающий сам интерфейс.
Код ui.js
var ui_scheme = {
	id:"phonebook",
	rows: [
	{view: "toolbar", cols: [
	{view: "label", label: "Телефонный справочник", align:"left"},
	{},
	{view: "label", label: "Отображать уволенных: ", align:"right"},
	{view: "toggle", id:"fired", offLabel:"нет", onLabel:"да", width:50},
	{view: "icon", id:"birthday", icon:"birthday-cake"}
	]},
	{cols:[
		{
			width: 300,
			rows:[
			{view: "search",id:"search", placeholder:"Поиск..."},
			{view: "tree", select:"true", id:"list_person"}
			]},
			{   
				id: "info",
				hidden:true ,
				width: "800",
				rows:[
				{view:"template", id:"main_info", template:"<h1>#fio#</h1><h3>Должность: #who#</h3><h5>Дата рождения: #birthday#</h5>",  autoheight:true},
				{
					view:"datatable", 
					id: "contact_info",
					header:false,
					autowidth:true,
					columns:[
					{ id:"type",    header:"", width:200},
					{ id:"value",   header:"", width:600}
					],
					fixedRowHeight:false,  rowLineHeight:25, rowHeight:25
				}
				]            
			}
			]}
			]
		};

		var win_birthday = {
			view:"window",
			id:"wBirthday",
			position: "center",
			modal: "true",
			resize: "true",
			move: "true",
			width: 500,
			head:{
				view: "toolbar", cols: [
				{view: "label", label: "Ближайшие дни рождения"},
				{view: "icon", icon:"close", id:"win_close", align: "right", click:"$$('wBirthday').hide();"}
				]
			},
			body:{
				rows:[
				{view:"counter", id:"days", label:"До дня рождения", labelWidth: 150, step:1, value:14, min:4, max:365},
				{view:"list", id: "birthday_persons",  template:"#fio# - #birthday# (#days#)", hidden:true, width:400}
				]
			}
		};

Интерфейс страницы (ui_scheme) представляет собой рабочую област,ь разделенную на три части:

  • панель инструментов в верхней части, на которой отображется заголовок, кнопка отображения уволенных сотрудников и кнопка отображения списка дней рождений;
  • дерево подразделений и сотрудников в левой части;
  • информация о сотруднике в правой части.

Дерево подразделений и сотрудников - это иерархический список где родителями являются подразделения, а подчиненными являются сотрудники.

Также имеется окно (win_birthday), которое состоит из заголовка окна, кнопки закрыть и списка сотрудников с указанием даты рождения и дней до дня рождения. Размеры и положения окна можно изменять и оно является модальным.

Код logic.js
function update_list()
{
	var url = "/../zup3/hs/person/personList?fired="+$$('fired').getValue();
	var auth = 'Basic ' + btoa(unescape(encodeURIComponent("web:web")))
	var res = null;
	webix.attachEvent("onBeforeAjax", 
		function(mode, url, data, request, headers, files, promise){
			headers["Authorization"]= auth;
		});
	webix.ajax(url,{
		error:function(text, data, XmlHttpRequest){
			alert("Ошибка работы с сервером");
		},
		success:function(text, data, XmlHttpRequest){
			$$("list_person").clearAll();
			$$("phonebook").disable();
			$$("phonebook").showProgress({
				type:"icon",
				delay:100,
				hide:true
			});
			setTimeout(function(){
				$$("list_person").parse(data.json());
				$$("phonebook").enable();
			}, 100);
		}
	});
}

function update_info(id)
{
	var url = "/../zup3/hs/person/personInfo?id="+id;
	var auth = 'Basic ' + btoa(unescape(encodeURIComponent("web:web")))
	var res = null;
	webix.attachEvent("onBeforeAjax", 
		function(mode, url, data, request, headers, files, promise){
			headers["Authorization"]= auth;
		});
	webix.ajax(url,{
		error:function(text, data, XmlHttpRequest){
			alert("Ошибка работы с сервером");
		},
		beforeSend:function(req) {
				req.setRequestHeader('Authorization', auth);
		},
		success:function(text, data, XmlHttpRequest){
		$$("phonebook").disable();
		$$("phonebook").showProgress({
			type:"icon",
			delay:100,
			hide:true
		});
		setTimeout(function(){
			$$("main_info").parse(data.json());
			$$("contact_info").clearAll();
			$$("contact_info").parse(data.json().contacts);
			$$("contact_info").adjustRowHeight("value", true); 
            $$("contact_info").render();
			$$("phonebook").enable();
			$$("info").show();
		}, 100);
	}
	});
}


function update_birthday(days)
{
	var url = "/../zup3/hs/person/birthdayList?days="+days;
	var auth = 'Basic ' + btoa(unescape(encodeURIComponent("web:web")))
	var res = null;
	webix.attachEvent("onBeforeAjax", 
		function(mode, url, data, request, headers, files, promise){
			headers["Authorization"]= auth;
		});
	webix.ajax(url,{
		error:function(text, data, XmlHttpRequest){
			alert("Ошибка работы с сервером");
		},
		beforeSend:function(req) {
				req.setRequestHeader('Authorization', auth);
		},
		success:function(text, data, XmlHttpRequest){
		$$("birthday_persons").disable();
		$$("birthday_persons").showProgress({
			type:"icon",
			delay:100,
			hide:true
		});
		setTimeout(function(){
			$$("birthday_persons").clearAll();
			$$("birthday_persons").parse(data.json());
			$$("birthday_persons").enable();
			$$("birthday_persons").show();
		}, 100);
	}
	});
}

webix.ready(function(){
	webix.ui(ui_scheme);
	webix.ui(win_birthday);
	webix.extend($$("phonebook"), webix.ProgressBar);
	webix.extend($$("birthday_persons"), webix.ProgressBar);
	update_list();
	$$('fired').attachEvent("onItemClick", function(){
		update_list();
	})
	$$('list_person').attachEvent("onItemClick", function(id, e, node){
		if ($$('list_person').isBranch(id) == false)
			update_info(id);
	})
	$$('birthday').attachEvent("onItemClick", function(){
		$$("wBirthday").show();
		update_birthday($$('days').getValue());
	})

	$$("days").attachEvent("onChange",function(){
		update_birthday(this.getValue());
	})

	$$("search").attachEvent("onTimedKeyPress",function(){
		$$("list_person").filter("#value#",this.getValue());
	})

})

Когда выполняется файл logic.js, он загружает интерфейс и подключает индикатор загрузки

webix.ui(ui_scheme);
webix.ui(win_birthday);
webix.extend($$("phonebook"), webix.ProgressBar);
webix.extend($$("birthday_persons"), webix.ProgressBar);

Затем вызывается функция update_list(), которая формирует список сотрудников и подразделений, отправляя запрос на сервер. После чего добавляются события к следующим объектам

$$('fired').attachEvent("onItemClick", function(){
		update_list();
	})

При нажатии на клавишу "Показывать уволенных сотрудников" происходит обновление списка подразделений и сотрудников

$$('list_person').attachEvent("onItemClick", function(id, e, node){
		if ($$('list_person').isBranch(id) == false)
			update_info(id);
	})

При нажатии на элементе дерева подразделений и сотрудников проверяется, является ли этот элемент сотрудником или подразделением (имеет ли дочерние объекты), если сотрудник, то обновляется область информации.

$$('birthday').attachEvent("onItemClick", function(){
		$$("wBirthday").show();
		update_birthday($$('days').getValue());
	})

При нажатии на кнопку "День рождения" отображается окно и обновляется список сотрудников и дат рождений в данном окне.

$$("search").attachEvent("onTimedKeyPress",function(){
        $$("list_person").filter("#value#",this.getValue());
    })

При изменении значения "До дня рождения" обновляется список сотрудников и дат рождений

$$("search").attachEvent("onTimedKeyPress",function(){
        $$("list_person").filter("#value#",this.getValue());
    })

Если поле search будет изменено то применяется фильтр к дереву подразделений и сотрудников.

Алгоритмы обновления информации о сотрудниках, подразделениях и дат рождения очень схожи:

  1. Формируется URL для запроса
  2. Формируется строка авторизации
  3. До отправки запроса отправляется запрос авторизации
  4. Отправляется запрос на сервер
  5. Обрабатывается результат и парсится в объекты страницы.

Я надеюсь, что данный материал был интересен и полезен для читателя. С радостью отвечу на вопросы в комментариях и выслушаю критику по данному материалу.

См. также

Вознаграждение за ответ
Показать полностью
Комментарии
1. Ruslan Khairullin (ruha) 38 10.03.17 17:04 Сейчас в теме
Исходники стоит прикреплять?
2. Андрей К. (andrei.k) 15.03.17 09:52 Сейчас в теме
(1) Если не сложно, прикрепите пожалуйста. Реализация интересная.
kraynev-navi; +1 Ответить 1
3. Вячеслав Алпатов (DonAlPatino) 23 15.03.17 12:13 Сейчас в теме
А что с клиентскими лицензиями на 1С в такой конфигурации происходит? Делал подобную разработку, но у меня получился комбинированный вариант AD + база в MS SQL, куда грузятся данные из 1С. Насчет AD могу сказать еще, что тормозное оно при отдаче данных (у меня там еще фотки сотрудников) до ужаса. Вот думаю может переделать.
4. Ruslan Khairullin (ruha) 38 15.03.17 14:08 Сейчас в теме
(3) А вот это интересный вопрос, даже не думал об этом. Если я не ошибаюсь, то 1С лицензия идет на сеанс, при открытие справочника, сеанс создается только на время обращения к базе. В консоле администрирования посмотрел сеансы только "живых душ", пользователь "web", который используется не висит. Вроде не зависал ни разу.
5. Ruslan Khairullin (ruha) 38 15.03.17 14:10 Сейчас в теме
(3) А сколько пользователей? Просто когда я делал, я все думал как реализовать: каждую запись с отдельным запросом к БД, или просто одним запросом все данные взять. И если вы будете реализовать у себя и будет большой объем данных, хотел узнать, как долго будут лететь данные общий запросом.
6. Ruslan Khairullin (ruha) 38 15.03.17 14:14 Сейчас в теме
(2)
(1) Если не сложно, прикрепите пожалуйста. Реализация интересная.

Не сложно. Прикрепил архив с клиентской частью, и там же файл http.txt с текстом для http-сервиса. Для работы клиентской части, необходимо добавить библиотеку webix.
Прикрепленные файлы:
phone.zip
7. Вячеслав Алпатов (DonAlPatino) 23 15.03.17 14:55 Сейчас в теме
(5) Под 200 человек. Соответственно тянется одним запросом весь список и отдельными запросами фотки.
8. Ruslan Khairullin (ruha) 38 15.03.17 16:00 Сейчас в теме
(7) Может отдельными запросами лучше? По необходимости подгружать информацию по нужному сотруднику
9. Вячеслав Алпатов (DonAlPatino) 23 15.03.17 17:21 Сейчас в теме
(8)У меня был проект по замене "старого" списка, который целиком велся в ручную и где карточка сотрудника сразу отображалась. И меня за можай загнали с криком "сделать как было". А потом, я так понимаю, запрос к AD долгий вне зависимости от того просто ты список сотрудников получаешь или список сотрудников с полями. Скорость одна. Ну либо где-то в коде (там .Net и я на этом проектике его изучал) какие-то откровенные глупости. Сейчас рефачу по-немного когда время есть. Может найду.
10. Максим Амишив (user723770) 20.04.17 17:38 Сейчас в теме
У нас крутится нечто подобное для рассылки корреспонденции. 1С на SQL и данные берем из базы на прямую из справочников. Лицензии 1С не задействованы.
Оставьте свое сообщение