29 янв. 2014 г.

Генерация UML диаграммы движений документов

При разработке более-менее сложной учетной системы разработчик обычно пытается предварительно определиться с взаимосвязями объектов этой системы. Может подойти как обычный листок бумаги с карандашом, так и специализированное ПО. Так же при проектировании диаграмм можно придерживаться определенных стандартов, например UML (Unified Modeling Language). С UML можно познакомиться с помощью курса "Моделирование на UML", почитать в онлайне книгу "Моделирование на UML" или курс лекций "Нотация и семантика языка UML" . Подборку программного обеспечения для создания UML диаграмм можно посмотреть по этим ссылкам: List of Unified Modeling Language toolsИнструменты UML-моделирования. Я в своих экспериментах использовал для генерации диаграмм онлайн-редактор CodeUML и плагин к IDE Eclipse, который можно скачать на сайте PlantUML.


Допустим, в проектируемой системе есть документы "Банковская выписка", "Реализация товаров и услуг" и "Поступление товаров", которые делают движения по регистрам "Продажи", "Расчеты с контрагентами" и "Склад".
@startuml
(Склад) <-- (РеализацияТоваровУслуг)
(Продажи) <|- (РеализацияТоваровУслуг)
(РасчетыСКонтрагентами) <--- (РеализацияТоваровУслуг)
(РасчетыСКонтрагентами) <--- (ПоступлениеТоваровУслуг)
(Склад) <-- (ПоступлениеТоваровУслуг)
(РасчетыСКонтрагентами) <-- (БанковскаяВыписка)
@enduml


Для лучшего восприятия можно добавить группировки по видам объектов.
@startuml
rectangle Документы {
(Реализация товаров и услуг) as (РеализацияТоваровУслуг)
(Поступление товаров и услуг) as (ПоступлениеТоваровУслуг)
(Банковская выписка) as (БанковскаяВыписка)
}
rectangle "Регистры накопления" {
rectangle "Остатки" {
(Расчеты с контрагентами) as (РасчетыСКонтрагентами)
(Складские запасы) as (Склад)
    }
    rectangle "Обороты" {
(Продажи)
    }
}
(Склад) <-- (РеализацияТоваровУслуг)
(Продажи) <|- (РеализацияТоваровУслуг)
(РасчетыСКонтрагентами) <--- (РеализацияТоваровУслуг)
(РасчетыСКонтрагентами) <--- (ПоступлениеТоваровУслуг)
(Склад) <-- (ПоступлениеТоваровУслуг)
(РасчетыСКонтрагентами) <-- (БанковскаяВыписка)
@enduml
Теперь набросаем обработку, генерирующую текст для диаграммы по метаданным конфигурации.


&НаКлиентеНаСервереБезКонтекста
Функция ДлинаСтрелки(КоличествоЭлементов)

 Тело = "-";
 Для сч = 1 По Цел(КоличествоЭлементов / 3)- 1 Цикл
  Тело = Тело + "-";
 КонецЦикла;
 
 Возврат Тело;

КонецФункции // ДлинаСтрелки()

&НаСервереБезКонтекста
Функция ТипРегистра(Регистр)
 
 Если Метаданные.РегистрыНакопления.Содержит(Регистр) Тогда
  ТипРегистра = "РегистрыНакопления";
 ИначеЕсли Метаданные.РегистрыРасчета.Содержит(Регистр) Тогда
  ТипРегистра = "РегистрыРасчета";
 ИначеЕсли Метаданные.РегистрыБухгалтерии.Содержит(Регистр) Тогда
  ТипРегистра = "РегистрыБухгалтерии";
 ИначеЕсли Метаданные.РегистрыСведений.Содержит(Регистр) Тогда
  ТипРегистра = "РегистрыСведений";
 Иначе 
  ТипРегистра = "";
 КонецЕсли;
 
 Возврат ТипРегистра;
  
КонецФункции

&НаКлиенте
Процедура Сформировать(Команда)
 СформироватьНаСервере();
КонецПроцедуры

&НаСервере
Процедура СформироватьНаСервере()
 
 Если Метаданные.Документы.Количество() = 0 Тогда
  Возврат;
 КонецЕсли;
 
 Регистры.Очистить();
 ТекстUML.Очистить();
 
 ТекстUML.ДобавитьСтроку("@startuml");
 
 Если LeftToRight Тогда
  ТекстUML.ДобавитьСтроку("left to right direction");
 КонецЕсли;
 
 ТекстUMLДобавитьСтрокуГруппы("rectangle Документы {");
 
 Для Каждого Док Из Метаданные.Документы Цикл
  
  ТекстUML.ДобавитьСтроку(" (" + УбратьСимволы(Док.Синоним) + ") as (" + Док.Имя + ")");
  
  Для Каждого Движение Из Док.Движения Цикл
   
   ТипРегистра = ТипРегистра(Движение);
   
   СтрокаТЗ = Регистры.Добавить();
   ЗаполнитьЗначенияСвойств(СтрокаТЗ, Движение);
   СтрокаТЗ.Документ = Док.Имя;
   СтрокаТЗ.ТипРегистра = ТипРегистра;
   
  КонецЦикла;
  
 КонецЦикла;
 
 ТекстUMLДобавитьСтрокуГруппы("}");
 
 #Область Запрос
 Запрос = Новый Запрос;
 Запрос.УстановитьПараметр("Регистры", Регистры.Выгрузить());
 Запрос.Текст = 
  "ВЫБРАТЬ
  | ВЫРАЗИТЬ(Регистры.Имя КАК СТРОКА(255)) КАК Имя,
  | ВЫРАЗИТЬ(Регистры.Документ КАК СТРОКА(255)) КАК Документ,
  | ВЫРАЗИТЬ(Регистры.ТипРегистра КАК СТРОКА(255)) КАК ТипРегистра,
  | ВЫРАЗИТЬ(Регистры.ВидРегистра КАК СТРОКА(255)) КАК ВидРегистра,
  | ВЫРАЗИТЬ(Регистры.Синоним КАК СТРОКА(1024)) КАК Синоним
  |ПОМЕСТИТЬ ТЗ
  |ИЗ
  | &Регистры КАК Регистры
  |
  |ИНДЕКСИРОВАТЬ ПО
  | ТипРегистра,
  | Имя
  |;
  |
  |////////////////////////////////////////////////////////////////////////////////
  |ВЫБРАТЬ
  | ТЗ.Имя КАК Имя,
  | КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЗ.Документ) КАК Количество,
  | ТЗ.ТипРегистра КАК ТипРегистра
  |ПОМЕСТИТЬ Связи
  |ИЗ
  | ТЗ КАК ТЗ
  |
  |СГРУППИРОВАТЬ ПО
  | ТЗ.Имя,
  | ТЗ.ТипРегистра
  |;
  |
  |////////////////////////////////////////////////////////////////////////////////
  |ВЫБРАТЬ
  | ТЗ.ТипРегистра КАК ТипРегистра,
  | ТЗ.Имя КАК Имя,
  | ТЗ.ВидРегистра КАК ВидРегистра,
  | ТЗ.Синоним
  |ИЗ
  | ТЗ КАК ТЗ
  |
  |СГРУППИРОВАТЬ ПО
  | ТЗ.Имя,
  | ТЗ.ТипРегистра,
  | ТЗ.ВидРегистра,
  | ТЗ.Синоним
  |
  |УПОРЯДОЧИТЬ ПО
  | ТипРегистра,
  | ВидРегистра,
  | Имя
  |ИТОГИ ПО
  | ТипРегистра,
  | ВидРегистра
  |;
  |
  |////////////////////////////////////////////////////////////////////////////////
  |ВЫБРАТЬ
  | ТЗ.Документ КАК Документ,
  | ТЗ.Имя КАК Имя,
  | ТЗ.ТипРегистра,
  | Связи.Количество
  |ИЗ
  | ТЗ КАК ТЗ
  |  ВНУТРЕННЕЕ СОЕДИНЕНИЕ Связи КАК Связи
  |  ПО ТЗ.Имя = Связи.Имя
  |   И ТЗ.ТипРегистра = Связи.ТипРегистра
  |
  |УПОРЯДОЧИТЬ ПО
  | Документ,
  | Имя";
  
 #КонецОбласти

 Результат = Запрос.ВыполнитьПакет();

 Группы = Результат[2].Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
 Пока Группы.Следующий() Цикл
  ТекстUMLДобавитьСтрокуГруппы("rectangle " + СокрЛП(Группы.ТипРегистра) + " {");
   Подгруппа = Группы.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
   Пока Подгруппа.Следующий() Цикл
    ТекстUMLДобавитьСтрокуГруппы(?(ПустаяСтрока(Подгруппа.ВидРегистра), "", " rectangle " + СокрЛП(Подгруппа.ВидРегистра) + " {"));
    РегистрыГруппы = Подгруппа.Выбрать();
    Пока РегистрыГруппы.Следующий() Цикл
     ТекстUML.ДобавитьСтроку(?(ПустаяСтрока(Подгруппа.ВидРегистра), " ", "  ") + "(" + УбратьСимволы(СокрЛП(РегистрыГруппы.Синоним)) + ") as (" + СокрЛП(РегистрыГруппы.ТипРегистра) + СокрЛП(РегистрыГруппы.Имя) + ")");
    КонецЦикла;
    ТекстUMLДобавитьСтрокуГруппы(?(ПустаяСтрока(Подгруппа.ВидРегистра), "", " }"));
   КонецЦикла;
  ТекстUMLДобавитьСтрокуГруппы("}");
 КонецЦикла;
 
 НаправлениеРегистра = Новый Структура;
 НаправлениеРегистра.Вставить("РегистрыНакопления", "-up");
 НаправлениеРегистра.Вставить("РегистрыРасчета", "-right");
 НаправлениеРегистра.Вставить("РегистрыБухгалтерии", "-down");
 НаправлениеРегистра.Вставить("РегистрыСведений", "-left");
 
 Связи = Результат[3].Выбрать();
 Пока Связи.Следующий() Цикл
  ТекстUML.ДобавитьСтроку(СокрЛП(Связи.ТипРегистра) + СокрЛП(Связи.Имя) + " <" + НаправлениеРегистра[СокрЛП(Связи.ТипРегистра)] + ДлинаСтрелки(Связи.Количество) + СокрЛП(Связи.Документ));
 КонецЦикла;
 
 ТекстUML.ДобавитьСтроку("@enduml");
 
КонецПроцедуры

Функция УбратьСимволы(Знач СтрокаДанных)
 
 СтрокаДанных = СтрЗаменить(СтрокаДанных, "(", "");
 СтрокаДанных = СтрЗаменить(СтрокаДанных, ")", "");
 Возврат СтрокаДанных;
 
КонецФункции

&НаСервере
Процедура ТекстUMLДобавитьСтрокуГруппы(СтрокаUML)
 
 Если ОтобразитьГруппы Тогда
  ТекстUML.ДобавитьСтроку(СтрокаUML);
 КонецЕсли;
 
КонецПроцедуры
 
Запуск обработки на конфигурации из поста Программирование за 21 день - Complete! дал следующий листинг:
@startuml
left to right direction
rectangle Документы {
(Заказ клиента) as (ЗаказКлиента)
(Поступление товаров и услуг) as (ПоступлениеТоваровУслуг)
(Реализация товаров и услуг) as (РеализацияТоваровУслуг)
(Оплата поставщику) as (ОплатаПоставщику)
(Оплата покупателя) as (ОплатаПокупателя)
(Выписка банка) as (ВыпискаБанка)
(Установка цен номенклатуры) as (УстановкаЦенНоменклатуры)
(Бухгалтерская операция) as (БухгалтерскаяОперация)
(Закрытие месяца) as (ЗакрытиеМесяца)
(Утверждение графика работ) as (УтверждениеГрафикаРабот)
(Начисление оклада) as (НачислениеОклада)
(Невыход сотрудника) as (НевыходСотрудника)
(Расчет премии) as (РасчетПремии)
(Выплата зарплаты) as (ВыплатаЗарплаты)
}
rectangle РегистрыБухгалтерии {

(Регистр "Основной бух. учет") as (РегистрыБухгалтерииОсновнойБухУчет)

}
rectangle РегистрыНакопления {
rectangle Обороты {
(Воронка продаж) as (РегистрыНакопленияВоронкаПродаж)
(Закупки) as (РегистрыНакопленияЗакупки)
(Продажи) as (РегистрыНакопленияПродажи)
(Эффективность продаж) as (РегистрыНакопленияЭффективностьПродаж)
}
rectangle Остатки {
(Взаиморасчеты) as (РегистрыНакопленияВзаиморасчеты)
(Долги по заработной плате) as (РегистрыНакопленияДолгиПоЗаработнойПлате)
(Остатки товаров) as (РегистрыНакопленияОстаткиТоваров)
}
}
rectangle РегистрыРасчета {

(Заработная плата) as (РегистрыРасчетаЗаработнаяПлата)

}
rectangle РегистрыСведений {

(График работы) as (РегистрыСведенийГрафикРаботы)
(Цены номенклатуры) as (РегистрыСведенийЦеныНоменклатуры)

}
РегистрыБухгалтерииОсновнойБухУчет <-down--БухгалтерскаяОперация
РегистрыНакопленияВзаиморасчеты <-up-ВыпискаБанка
РегистрыБухгалтерииОсновнойБухУчет <-down--ВыпискаБанка
РегистрыНакопленияДолгиПоЗаработнойПлате <-up-ВыплатаЗарплаты
РегистрыНакопленияВоронкаПродаж <-up-ЗаказКлиента
РегистрыНакопленияЭффективностьПродаж <-up-ЗаказКлиента
РегистрыБухгалтерииОсновнойБухУчет <-down--ЗакрытиеМесяца
РегистрыНакопленияДолгиПоЗаработнойПлате <-up-НачислениеОклада
РегистрыРасчетаЗаработнаяПлата <-right-НачислениеОклада
РегистрыРасчетаЗаработнаяПлата <-right-НевыходСотрудника
РегистрыНакопленияВзаиморасчеты <-up-ОплатаПокупателя
РегистрыБухгалтерииОсновнойБухУчет <-down--ОплатаПокупателя
РегистрыНакопленияВзаиморасчеты <-up-ОплатаПоставщику
РегистрыБухгалтерииОсновнойБухУчет <-down--ОплатаПоставщику
РегистрыНакопленияВзаиморасчеты <-up-ПоступлениеТоваровУслуг
РегистрыНакопленияЗакупки <-up-ПоступлениеТоваровУслуг
РегистрыБухгалтерииОсновнойБухУчет <-down--ПоступлениеТоваровУслуг
РегистрыНакопленияОстаткиТоваров <-up-ПоступлениеТоваровУслуг
РегистрыНакопленияДолгиПоЗаработнойПлате <-up-РасчетПремии
РегистрыРасчетаЗаработнаяПлата <-right-РасчетПремии
РегистрыНакопленияВзаиморасчеты <-up-РеализацияТоваровУслуг
РегистрыБухгалтерииОсновнойБухУчет <-down--РеализацияТоваровУслуг
РегистрыНакопленияОстаткиТоваров <-up-РеализацияТоваровУслуг
РегистрыНакопленияПродажи <-up-РеализацияТоваровУслуг
РегистрыНакопленияЭффективностьПродаж <-up-РеализацияТоваровУслуг
РегистрыСведенийЦеныНоменклатуры <-left-УстановкаЦенНоменклатуры
РегистрыСведенийГрафикРаботы <-left-УтверждениеГрафикаРабот
@enduml
CodeUML на пару с Eclipse без проблем построили мне нижеприведенную диаграмму по этому коду.


К сожалению, ту простыню текста, получившуюся на УТ11 (990 строк), ни онлайн-редактор, ни плагин к Eclipse не смогли обработать :) Можно было бы добавить фильтрацию по подсистемам для уменьшения объема кода, но это уже отдельная тема.

1 комментарий: