Как сохранить историю изменения параметров задачи

В TrackStudio начиная с версии 4.0.9 есть встроенный механизм аудита изменений. Для того, чтобы его задействовать, нужно в интересующем вас процессе создать операцию с названием *. После этого все изменения, происходящие с задачей, будут записываться с помощью этой операции, причем без учета прав пользователя (если пользователь может редактировать задачу, либо выполнять любую операцию с ней - системное сообщение также будет создаваться). Вы можете настроить права на просмотр этой операции, точно так же, как и для остальных операций.

В TrackStudio версий с 4.0 по 4.0.8 встроенной возможности нет, но такую функциональность можно организовать с помощью триггеров. Это решение можно рассматривать также просто как пример использования триггеров.

Свойства задачи могут измениться как при редактировании самой задачи, так и при выполенении операции. Значит нам понадобится не один, а два триггера разных типов, работающих по одному принципу. Т.к. для отслеживания изменений нам нужно знать состояние задачи до внесения этих изменений, триггер типа After не подойдет - тогда изменения уже записаны. Триггер типа Before нам не подойдет потому, что запись об изменениях произойдет раньше самих изменений, что, во-первых, может снова изменить свойства задачи, во-вторых, может отразить неверную информацию, если само изменение не состоялось из-за ошибки.
Следовательно нам нужны два триггера типа Instead Of.

Для редактирования задачи напишем триггер Instead Of Edit Task. Он должен соответствовать интерфейсу com.trackstudio.external.TaskTrigger и располагаться в папке

./etc/plugins/scripts/instead_of_edit_task/
package scripts.instead_of_edit_task;

import com.trackstudio.app.adapter.AdapterManager;
import com.trackstudio.app.csv.CSVImport;
import com.trackstudio.exception.GranException;
import com.trackstudio.external.TaskTrigger;
import com.trackstudio.secured.*;
import com.trackstudio.startup.I18n;

import java.util.Calendar;

/**
 * Скрипт сохраняет все изменения задачи
 */
public class LogChanges implements TaskTrigger {
    public SecuredTaskTriggerBean execute(SecuredTaskTriggerBean task) throws GranException {
        StringBuffer sb2 = new StringBuffer();
        StringBuffer sb = new StringBuffer();
        sb2.append("<table class=\"general\" cellpadding=4>");
        String budget = task.getBudgetAsString();

        Calendar deadline = task.getDeadline();
        SecuredPrstatusBean group = task.getHandlerGroup();
        SecuredUserBean user = task.getHandlerUser();
        SecuredPriorityBean priority = task.getPriority();
        SecuredResolutionBean resolution = task.getResolution();
        String description = task.getDescription();
        String name = task.getName();
        String alias = task.getShortname();

        SecuredTaskBean oldTask = new SecuredTaskBean(task.getId(), task.getSecure());
        String _name = oldTask.getName();
        String _alias = oldTask.getShortname();
        String _budget = oldTask.getBudgetAsString();
        Calendar _deadline = oldTask.getDeadline();
        SecuredPrstatusBean _group = oldTask.getHandlerGroup();
        SecuredUserBean _user = oldTask.getHandlerUser();
        SecuredPriorityBean _priority = oldTask.getPriority();
        SecuredResolutionBean _resolution = oldTask.getResolution();
        String _description = oldTask.getDescription();
        if ((_name != null && !_name.equals(name)) || (_name == null && name != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "NAME"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_name);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(name);
            sb.append("</td>");
            sb.append("</tr>\n");

        }

        if ((_alias != null && !_alias.equals(alias)) || (_alias == null && alias != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "ALIAS"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_alias);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(alias);
            sb.append("</td>");
            sb.append("</tr>\n");

        }

        if ((_budget != null && !_budget.equals(budget)) || (_budget == null && budget != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "BUDGET"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_budget);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(budget);
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_deadline != null && !_deadline.equals(deadline)) || (_deadline == null && deadline != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "DEADLINE"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_deadline != null)
                sb.append(task.getSecure().getUser().getDateFormatter().parse(_deadline));
            sb.append("</strike></td>");
            sb.append("<td>");
            if (deadline != null)
                sb.append(task.getSecure().getUser().getDateFormatter().parse(deadline));
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_priority != null && !_priority.equals(priority)) || (_priority == null && priority != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "PRIORITY"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_priority != null)
                sb.append(_priority.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (priority != null)
                sb.append(priority.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_resolution != null && !_resolution.equals(resolution)) || (_resolution == null && resolution != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "RESOLUTION"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_resolution != null)
                sb.append(_resolution.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (resolution != null)
                sb.append(resolution.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_user != null && !_user.equals(user)) || (_user == null && user != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_user != null)
                sb.append(_user.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (user != null)
                sb.append(user.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_group != null && !_group.equals(group)) || (_group == null && group != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_group != null)
                sb.append(_group.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (group != null)
                sb.append(group.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_description != null && !_description.equals(description)) || (_description == null && description != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(task.getSecure(), "DESCRIPTION"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_description);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(description);
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if (task.getUdfValues() != null && !task.getUdfValues().isEmpty()) {

            for (Object okey : task.getUdfValues().keySet()) {
                String key = okey.toString();
                String value = task.getUdfValues().get(key).toString();
                String oldValue = AdapterManager.getInstance().getSecuredUDFAdapterManager()
                        .getTaskUDFValue(task.getSecure(), task.getId(), key);
                if ((oldValue != null && !oldValue.equals(value))
                        || (oldValue == null && value != null)) {
                    sb.append("<tr>");
                    sb.append("<th align=\"right\">");
                    sb.append(key);
                    sb.append("</th>");
                    sb.append("<td><strike>");
                    sb.append(oldValue);
                    sb.append("</strike></td>");
                    sb.append("<td>");
                    sb.append(value);
                    sb.append("</td>");
                    sb.append("</tr>\n");

                }
            }
        }
        SecuredMessageTriggerBean createMessage = null;
        if (sb.length() > 0) {
            sb2.append(sb);
            sb2.append("</table>\n");
            String mstatusId = CSVImport.findMessageTypeIdByName("Log", task.getCategory().getName());
            /**
             * Создаем SecuredMessageTriggerBean
             */
            createMessage = new SecuredMessageTriggerBean(
                    null /* идентификатор */,
                    sb2.toString() /* текст комментария */,
                    Calendar.getInstance() /* время выполнения операции */,
                    null /* потраченное время */,
                    task.getDeadline() /* Сроки выполнения задачи (deadline) */,
                    task.getBudget() /* бюджет */,
                    task.getId() /* задача */,
                    task.getSecure().getUserId() /* автор операции */,
                    null /* резолюция */,
                    task.getPriorityId() /* приоритет */,
                    task.getHandlerId() /* ответственные */,
                    task.getHandlerUserId() /* ответственный */,
                    task.getHandlerGroupId() /* ответственный, если нужно задать группу в качестве ответственного */,
                    mstatusId /* тип операции */,
                    null /* Map с дополнительными полями */,
                    task.getSecure() /* SessionContext */,
                    null /* вложения */);
            /**
             * выполняем
             */

        }
        task.update(true);
        if (createMessage != null) createMessage.create(false);
        return task;
    }
}

Этот триггер нужно подключить в настройках категорий тех задач, изменения в которых требуется отслеживать. При этом в процессах задач должна быть операция "Log", которая не изменяет состояние задачи. Название операции можно, конечно, поменять. Не забудьте тогда сменить его и в обоих триггерах.

Далее, для того, чтобы отслеживать изменения в задачах, произведенные с помощью операций (добавилении сообщений), нам понадобится триггер Instead Of Add Message. Он должен соответствовать интерфейсу com.trackstudio.external.OperationTrigger и располагаться в папке

./etc/plugins/scripts/instead_of_add_message/
package scripts.instead_of_add_message;

import com.trackstudio.app.adapter.AdapterManager;
import com.trackstudio.app.csv.CSVImport;
import com.trackstudio.exception.GranException;
import com.trackstudio.external.OperationTrigger;
import com.trackstudio.secured.*;
import com.trackstudio.startup.I18n;


import java.util.Calendar;

public class LogChanges implements OperationTrigger {
    public SecuredMessageTriggerBean execute(SecuredMessageTriggerBean message) throws GranException {
        StringBuffer sb2 = new StringBuffer();
        StringBuffer sb = new StringBuffer();
        sb2.append("<table class=\"general\" cellpadding=4>");
        String budget = message.getBudgetAsString();
        Calendar deadline = message.getDeadline();
        SecuredPrstatusBean group = message.getHandlerGroup();
        SecuredUserBean user = message.getHandlerUser();
        SecuredPriorityBean priority = message.getPriority();
        SecuredResolutionBean resolution = message.getResolution();
        SecuredStatusBean state = message.getTask().getStatus();

        String _budget = message.getTask().getBudgetAsString();
        Calendar _deadline = message.getTask().getDeadline();
        SecuredPrstatusBean _group = message.getTask().getHandlerGroup();
        SecuredUserBean _user = message.getTask().getHandlerUser();
        SecuredPriorityBean _priority = message.getTask().getPriority();
        SecuredResolutionBean _resolution = message.getTask().getResolution();
        SecuredStatusBean _state = null;
        for (SecuredTransitionBean t : message.getMstatus().getTransitions()) {
            if (t.getStart().equals(state)) _state = t.getFinish();
            break;
        }

        if ((_budget != null && !_budget.equals(budget)) || (_budget == null && budget != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "BUDGET"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_budget);
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(budget);
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_deadline != null && !_deadline.equals(deadline)) || (_deadline == null && deadline != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "DEADLINE"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_deadline != null)
                sb.append(message.getSecure().getUser().getDateFormatter().parse(_deadline));
            sb.append("</strike></td>");
            sb.append("<td>");
            if (deadline != null)
                sb.append(message.getSecure().getUser().getDateFormatter().parse(deadline));
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_priority != null && !_priority.equals(priority)) || (_priority == null && priority != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "PRIORITY"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_priority != null)
                sb.append(_priority.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (priority != null)
                sb.append(priority.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_resolution != null && !_resolution.equals(resolution)) || (_resolution == null && resolution != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "RESOLUTION"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_resolution != null)
                sb.append(_resolution.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (resolution != null)
                sb.append(resolution.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_user != null && !_user.equals(user)) || (_user == null && user != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_user != null)
                sb.append(_user.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (user != null)
                sb.append(user.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_group != null && !_group.equals(group)) || (_group == null && group != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "HANDLER"));
            sb.append("</th>");
            sb.append("<td><strike>");
            if (_group != null)
                sb.append(_group.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            if (group != null)
                sb.append(group.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if ((_state != null && !_state.equals(state)) || (_state == null && state != null)) {
            sb.append("<tr>");
            sb.append("<th align=\"right\">");
            sb.append(I18n.getString(message.getSecure(), "TASK_STATE"));
            sb.append("</th>");
            sb.append("<td><strike>");
            sb.append(_state.getName());
            sb.append("</strike></td>");
            sb.append("<td>");
            sb.append(state.getName());
            sb.append("</td>");
            sb.append("</tr>\n");

        }
        if (message.getUdfValues() != null && !message.getUdfValues().isEmpty()) {

            for (Object okey : message.getUdfValues().keySet()) {
                String key = okey.toString();
                String value = message.getUdfValues().get(key).toString();
                String oldValue = AdapterManager.getInstance().getSecuredUDFAdapterManager()
                        .getTaskUDFValue(message.getSecure(), message.getTaskId(), key);
                if ((oldValue != null && !oldValue.equals(value))
                        || (oldValue == null && value != null)) {
                    sb.append("<tr>");
                    sb.append("<th align=\"right\">");
                    sb.append(key);
                    sb.append("</th>");
                    sb.append("<td><strike>");
                    sb.append(oldValue);
                    sb.append("</strike></td>");
                    sb.append("<td>");
                    sb.append(value);
                    sb.append("</td>");
                    sb.append("</tr>\n");

                }
            }
        }
        SecuredMessageTriggerBean createMessage = null;
        if (sb.length() > 0) {
            sb2.append(sb);
            sb2.append("</table>\n");
            String mstatusId = CSVImport.findMessageTypeIdByName("Log", message.getTask().getCategory().getName());
            /**
             * Создаем SecuredMessageTriggerBean
             */
            createMessage = new SecuredMessageTriggerBean(
                    null /* индентификатор */,
                    sb2.toString() /* текст комментария */,
                    Calendar.getInstance() /* время выполнения операции */,
                    null /* потраченное время */,
                    message.getDeadline() /* Сроки выполнения задачи (deadline) */,
                    message.getBudget() /* бюджет */,
                    message.getTaskId() /* задача */,
                    message.getSecure().getUserId() /* автор операции */,
                    null /* резолюция */,
                    message.getPriorityId() /* приоритет */,
                    message.getHandlerId() /* ответственные */,
                    message.getHandlerUserId() /* ответственный */,
                    message.getHandlerGroupId() /* ответственный, если нужно задать группу в качестве ответственного */,
                    mstatusId /* тип операции */,
                    null /* Map с дополнительными полями */,
                    message.getSecure() /* SessionContext */,
                    null /* вложения */);
            /**
             * выполняем
             */

        }
        message.create(true);
        if (createMessage != null) createMessage.create(false);
        return message;
    }
}

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

AttachmentSize
log-scripts-classes.zip7.6 KB
log-scripts-src.zip4.4 KB