Данный документ адресован в первую очередь разработчикам.

Проект в формате Gradle.

Настройки IDE Eclipse

Загрузить Eclise for Java EE, эта сборка уже содержит GIT клиент, редакторы JSP и XML.

Установить плагины:

  • Buildship Gradle - поддержка Gradle;

  • AsciiDoctor - правка документации;

  • MoreUnit - удобная работа с Unit тестами.

Вызвать правым кликом на проекте меню Refresh Gradle Project. Его же вызывать при любом изменении библиотек проекта.

Запуск

Параметры для запуска:

  • Main class: ru.bgerp.Server

  • Program arguments: start

  • VM arguments: -Dbgerp.setup.data=bgerp_test

  • Classpath: User entries - Advanced - Add folders нажать и добавить каталог текущего проекта.

bgerp_test.propertes файл должен лежать в корне проекта. Его можно скопировать из bgerp.properties, он не будет сохранён в GIT.

Пустую БД создать из дистрибутива по инструкции.

Правила оформления кода

Именование

Переменные конфигураций, функции JS, HTTP StyleId

  • Все переменные конфигурации от плагинов начинать как <plugin>:

  • Все функции JS плагинов начинать с префикса <plugin>- В других местах тире запретить в названии функции.

  • Все идентификаторы (style id) DOM элементов для плагинов начинать как <plugin>- В других местах тире запретить в названиях идентификаторов.

База данных

1) Таблицы и поля в них именовать с нижним подчёркиванием. process_id param_id

Переменные Java, параметры HTTP запросов, переменные в JSP

В camelCase нотации: processId paramId

Дата - поле сущности

Если в поле только дата, то: createDate - Java - тип java.util.Date create_date - БД - тип date

Если в поле дата + время, то: createTime - Java - тип java.util.Date create_dt - БД - тип datetime

Дата - период сущности

В бинах дату периода хранить с типом java.util.Date с именем: dateFrom dateTo

Соответственно методы получения и установки: setDateFrom setDateTo getDateFrom getDateTo

Не использовать для хранения в бинах Calendar. Если нужно конверить в календарь или из календаря - использовать TimeUtils.

Если нужно хранить время, то делаем: timeFrom timeTo

Также тип java.util.Date.

Calendar использовать во всяких калькуляторах/тарификаторах, когда реально нужно постоянно двигать дату.

В базе использовать поля from_date и to_date, тип date.

from_dt, to_dt - тип datetime.

Неограниченные даты - NULL.

Форматирование Java

Форматирование Java кода должно соответствовать Java Conventions со следующими изменениями. Настройки в Windows - Preferences - Java - Code style - Formatter. Необходимо открыть стандартный форматтер и сохранить под новым именем, изменив параметры:

  • Indentation - Tab policy - Spaces only

  • Indentation - Tab size - 4

  • Line Wrapping - Maximum line width - 150

Windows - Preferences - Java - Code style - Organize Imports в двух полях поставить 99 и 1.

Сборка

Необходим установленный Apache Ant и Perl с модулями. На Linux, WSL, либо Mac.

Сборка пакета обновления, например в разрабатываемой ветке. В каталоге build/update выполнить:

bash -c ant

Сборка пакета обновления библиотек, можно также выполнять в отдельной ветке. Необходимо вызвать перед публикацией, если изменился состав библиотек. В каталоге build/update_lib выполнить:

bash -c ant

Сборка пакета обновления и публикация. Вызвать каталоге build для WSL:

bash -c build_and_copy.sh

Сборка и копирование JavaDoc, в том же каталоге:

bash -c create_doc.sh

Запуск тестов

Unit тесты

./gradlew test

Интеграционный тест создания БД

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

./gradlew buildErp

Команда делает вызов Ant сценария в build/bgerp, передавая классы, скомпилированные Gradle.

Интеграционный тест, устанавливающий приложение и заполняющий тестовую базу. Только под Linux либо WSL. sudo необходим для установки в стандартный /opt каталог.

Тест пересоздаст базу с именем bgerp и переустановит сервер в /opt/BGERP
sudo GRADLE_OPTS=-Xmx1000m ./gradlew -Ptest.single='ru.bgerp.itest.RunServerTest' -Pdb.host=DB_HOST -Pdb.user=ROOT_USER -Pdb.pswd=ROOT_PSWD integrationTest

Параметры DB_HOST, ROOT_USER, ROOT_PSWD - для доступа к MySQL серверу, где будет создана тестовая БД.

Selenium e-to-e тест

Может выполнять впоследствия действия на запущенном сервере. Необходима установка chromedriver и указание его в пути.

GRADLE_OPTS=-Xmx1000m ./gradlew -Ptest.single='ru.bgerp.itest.SeleniumTest' -Pwebdriver.chrome.driver=/usr/lib/chromium-browser/chromedriver integrationTest

Документация

Исходные файлы в формате AsciiDoctor размещаются в каталоге srcx/doc проекта. Для генерации используется система PzdcDoc. Основные идеи:

  • Документы упорядочены в файловую структуру с расширениями .adoc.

  • При генерации файловая структура сохраняется.

  • Все ресурсы (картинки и т.п.) должны быть расположены в каталоге _res рядом с .adoc файлом.

  • Ссылки на ресурсы проекта производятся только относительными путями от текущего .adoc файла.

  • Как результат должен работать предпросмотр документов в GitLab и корректно генерироваться HTML документация.

  • Во время генерации производится проверка всех ссылок внутри файлов документации с возвращением кода возврата 1 в случае ошибок.

Сборка выполняется командой:

gradlew clean buildDoc

Собранные HTML файлы доступны в каталоге target/doc.

Примеры форматирования и рекомендации можно посмотреть здесь.

Правка документации без привязки к изменениям (см. GIT workflow) происходит в ветке documentation, откуда при каждом коммите происходит публикация на: http://www.bgerp.ru/doc/3.0/manual Ветка master периодически мерджится на ветку документации, откуда в неё попадают все правки связанные с изменениями.

GIT workflow

Основная ветка проекта - master, ветка documentation всегда включает последние изменения от основной ветки плюс актуальную документацию.

  • Каждое изменение должно базироваться на процессе в BGERP BiTel, в котором происходит весь обмен информацией с заказчиками.

  • Идентификатор изменения - строка p<идентификатор процесса>, например p13455

  • Для каждого изменения создаётся отдельная ветка GitLab на базе documentation , название начинающееся с идентификатора изменения, разделители - дефисы. Например: i2-link-customer-process

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

  • В ветке может быть изменён файл .gitignore для хранения кастомизаций в GIT, они не будут перенесены в основную ветку.

  • На ветку изменения могут быть периодически смерджены основная ветка и документация (всегда впереди основной с актуальными правками документации).

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

  • По завершению разработки необходимо запросить перенос ветки с изменением на основную ветку в процессе BGERP.

  • После обсуждения процесс закрывается, а изменение переносится в виде единственного коммита, с комментарием начинающимся с идентификатора изменения в основную ветку.

  • Ветка разработки впоследствии может быть удалена. Автор изменения может быть установлен по идентификатору в комментарии.

  • Пакеты обновления для клиента, равно как и документацию, рекомендуется предоставлять в сетевом каталоге, содержащем идентификатор процесса. Например: http://bgerp.ru/patch/p10646 Установка обновлений производится клиентом с помощью installer.sh install

Невозможно одновременно тестировать несколько изменений параллельно на одной системе BGERP и пользоваться ими. Каждое изменение будет вероятнее всего перетирать другое при установке обновления. По окончанию приёмки изменения уведомьте разработчиков и изменение будет включено в основную ветку со сборкой нового билда.

Настройка GIT

Пример настройки .gitconfig:

[user]
        email = shamil@ufamail.ru
        name = Shamil Vakhitov
[credential]
        helper = store
[core]
        autocrlf = false
        fileMode = false
[pull]
        rebase = true

GIT команды

Почистить все ссылки на несуществующие более удалённые ветки:

git remote prune origin

Получение последних обновлений основной ветки не будучи в ней:

git fetch origin master:master

Примеры кода

Так как система очень быстро меняется, то здесь собираются примеры актуального кода, рекомендуемого к использованию. Сниппеты извлекаются непосредственно из исходных файлов проекта, поэтому всегда достоверны. При необходимости воспользуйтесь IDE для поиска классов или файлов.

Сохранение последних параметров запроса пользователя

Например, фильтров интерфейса. Используется метод restoreRequestParams в: ru.bgcrm.struts.action.BaseAction

Сохранение значения:

// PzdcDoc snippet of: 'ru.bgcrm.struts.action.MessageAction', lines: 61 - 67

public ActionForward message(ActionMapping mapping, DynActionForm form, ConnectionSet conSet) throws BGException {
        MessageTypeConfig config = setup.getConfig(MessageTypeConfig.class);

        Message message = null;
        MessageType type = null;
        
        restoreRequestParams(conSet.getConnection(), form, true, false, "messageTypeAdd");

Восстановление:

// PzdcDoc snippet of: 'ru.bgcrm.struts.action.MessageAction', lines: 232 - 244

public ActionForward messageUpdate(ActionMapping mapping, DynActionForm form, ConnectionSet conSet)
                throws Exception {
        MessageTypeConfig config = setup.getConfig(MessageTypeConfig.class);

        MessageType type = config.getTypeMap().get(form.getParamInt("typeId"));
        if (type == null)
                throw new BGException("Не определён тип сообщения.");
        
        // сохранение типа сообщения, чтобы в следующий раз выбрать в редакторе его
        if (form.getId() <= 0) {
            form.setParam("messageTypeAdd", String.valueOf(type.getId()));
            restoreRequestParams(conSet.getConnection(), form, false, true, "messageTypeAdd");
        }

Сохранение плюс восстановление сразу:

// PzdcDoc snippet of: 'ru.bgcrm.struts.action.ProcessAction', lines: 1086 - 1090

// процессы, к которым привязана сущность
public ActionForward linkedProcessList(ActionMapping mapping, DynActionForm form, Connection con) throws Exception {
    ProcessLinkDAO processLinkDAO = new ProcessLinkDAO(con, form.getUser());

    restoreRequestParams(con, form, true, true, "closed");

Отображение на вкладке количества элементов

Например, количества связанных процессов. Сохраняется при первом вызове. Используется класс ru.bgcrm.model.IfaceState

Обновление значения:

// PzdcDoc snippet of: 'ru.bgcrm.struts.action.ProcessAction', lines: 1254 - 1258

// проверка и обновление статуса вкладки, если нужно
IfaceState ifaceState = new IfaceState(form);
IfaceState currentState = new IfaceState(Process.OBJECT_TYPE, id, form, String.valueOf(searchResultLink.getPage().getRecordCount()),
        String.valueOf(searchResultLinked.getPage().getRecordCount()));
new IfaceStateDAO(con).compareAndUpdateState(ifaceState, currentState, form);

Отображение в JSP:

<%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/user/process/process/process_editor.jsp', lines: 85 - 99 --%>

<c:if test="${processType.properties.configMap['processShowProcessLinks'] eq '1'}">
    <c:set var="ifaceId" value="link_process"/>
    <c:set var="ifaceState" value="${ifaceStateMap[ifaceId]}"/>

        <c:url var="url" value="process.do">
                <c:param name="action" value="linkProcessList"/>
                <c:param name="id" value="${process.id}"/>
                <c:param name="linkedReferenceName" value="linkedProcessList"/>
                <c:param name="linkReferenceName" value="linkProcessList"/>
                <c:param name="ifaceId" value="${ifaceId}"/>
                <c:param name="ifaceState" value="${ifaceState.state}"/>
        </c:url>
        
        $tabs.tabs( "add", "${url}", "${l.l('Связанные процессы')}${ifaceState.getFormattedState()}" );
</c:if>

JSP

JS

Actions

Определяются в struts-config*.xml файлах, имя метода передаётся в параметре action. Должны расширять класс ru.bgcrm.struts.action.BaseAction и возвращать результат методом processUserTypedForward либо processJsonForward. Первый метод обрабатывается JSP страницей-форвардом и высылает HTML на клиент. Второй - предназначен для выполнения изменений и возвращает только JSON формат с результатом выполнения. Если метод не требует соединения к БД - используйте с параметром ConnectionSet.

Все параметры запроса передаются в объекте ru.bgcrm.struts.form.DynActionForm, он содержит вспомогательные методы для получения параметров разных типов.

Не использовать устаревший формат акшенов с параметрами HttpClientRequest и HttpClientResponse, они есть в form.

В этом же form следует передавать данные для отрисовки JSP, за исключением различных вспомогательных справочников. Для этого используется поле response формы. При responseType=json, всё отправленное в response сериализуется в JSON, именно поэтому справочники следует помещать в HttpResponse.

Исключение прерывает выполнение акшена, возвращается ошибка. Всегда в JSON формате. Исключение с классом ru.bgcrm.model.BGMessageException используется для прерывания акшена с высылкой уведомления. То есть это штатная ситуация. В методах акшенов рекомендуется указывать throws java.lang.Exception, для генерации своих исключений - new BGExeption().

Пример акшена: ru.bgcrm.struts.action.ProcessAction

Методы акшенов именовать как можно короче, пример: ru.bgcrm.plugin.task.struts.action.TaskAction Здесь имя класса плюс метод однозначно дают понять, что выводится. В случае, если этот акшен будет выводить ещё, например, типы, можно будет сделать методы типа typeList.

Разработка плагинов

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

В каких местах производится правка для плагина:

  • XML объявление плагина plugin/name.xml, там же объявляются точки расширений.

  • Таблицы БД при необходимости создаются и модифицируются в build/update/patch.sql

  • Java код плагина в пакете ru.bgerp.plugin.name

  • Java библиотеки подключать в build.gradle после комментария: "библиотеки, попадающие в сборку BGERP"

  • JS код плагина в файле webapps/js/name.js, подключается через точку расширения в XML объявлении.

  • Struts акшены плагина в файле webapps/WEB-INF/struts-config-name.xml

  • Действия из обычного интерфейса должны быть объявлены в action/plugin.xml для контроля прав.

  • JSP плагина в webapps/WEB-INF/jspf/user/plugin/name

  • Для добавления плагина в сборку править build/update/build.xml

Примеры

XML декларация

<!-- PzdcDoc snippet of: '../../plugin/blow.xml', lines: 1 - 8 -->

<?xml version="1.0" encoding="UTF-8"?>

<plugin package="ru.bgerp.plugin.blow">
        <endpoint id="user.menu.items.jsp" file="/WEB-INF/jspf/user/plugin/blow/menu_items.jsp"/>
        <endpoint id="js" file="/js/pl.blow.js"/>
        <endpoint id="css.jsp" file="/css/style.blow.css.jsp"/>
        <endpoint id="open.jsp" file="/WEB-INF/jspf/open/plugin/blow/url.jsp"/>
</plugin>

В файле определяются точки расширения:

  • JSP шаблоны;

  • JS файлы;

  • package - пакет плагина, в котором должен быть размещён главный класс.

Java класс плагина

В классе плагина могут определяться слушатели событий:

// PzdcDoc snippet of: 'ru.bgcrm.plugin.slack.Plugin', lines: 14 - 20

public Plugin(Document doc) {
        super(doc, ID);
        
        EventProcessor.subscribe((DefaultProcessorChangeContextEvent e, ConnectionSet conSet) -> {
                e.getContext().put(ID, new DefaultProcessorFunctions());
        }, DefaultProcessorChangeContextEvent.class );
}