Общие сведения
Надёжность и безопасность
Обзор системы
Начало работы
Проекты
Концепции
Компоненты
Инструкции
Финансы
Ресурсы
Таймшиты
Клиенты
Задачи
Вики
Затраты
Отчёты и аналитика
FAQ
Типы отчётов
Использование отчётов
Группировка данных источника
Группировка данных в отчёте
Типы виджетов
Общие отчёты и шаблоны
Настройка отчёта
Экспорт отчётов
Пользовательские настройки отчёта
Вычисляемые поля
Выражения вычисляемых полей
Особые колонки отчётов с временными рядами
Использование панелей мониторинга
Публикация панелей
Фильтры источников данных
Настройка и администрирование
Типовой порядок настройки системы
Жизненные циклы и воркфлоу
On-premises
API
История изменений
Термины и определения

Обработчик событий сущностей

Обновлено: 18.08.2025

Примечание

Функция доступна по запросу в поддержку: support@timetta.com.

Назначение и общая логика

Важно

Компонента позволяет кастомизировать логику работы с данными:

  • Предполагает понимание языка C# и структуры данных Timetta
  • Предназначена для прикладных разработчиков
  • Находится в стадии активного развития

Если вам нужно переопределение логики, рекомендуем обратиться в поддержку support@timetta.com для платной консультации.

Основные сценарии применения обработчика:

  • Исходящие интеграции (например, отправить проект в 1С)
  • Работа с данными (предварительно заполнить свойства, выполнить проверки)
  • Взаимодействие с пользователями (отправить уведомление, поставить задачу на выполнение)

Обработчик представляет собой класс на C# для каждого типа сущности. В системе он наследует абстрактный класс EntityTypeCustomHooks<TEntity>. Все методы в нём объявлены как virtual с реализацией по умолчанию Task.CompletedTask — переопределять нужно только те методы, логика которых действительно требуется.

Хуки операций с сущностью (CRUD)

В обработчике доступны четыре переопределяемых метода (хука), вызываемых при операциях с сущностью:

Хук Когда вызывается Параметры
BeforeUpsert До вставки или обновления сущности. Используется для базовых проверок. context, detachedEntity
AfterUpsert После вставки или обновления. Предназначен для бизнес-логики: предзаполнение свойств, исходящие интеграции. context, contextEntity, detachedEntity
BeforeDelete До удаления сущности. context, detachedEntity
AfterDelete После удаления сущности. context, contextEntity

Хуки жизненного цикла и воркфлоу

Помимо CRUD-хуков в обработчике доступны три метода, связанные со сменой состояния и запуском воркфлоу:

Хук Когда вызывается Параметры
BeforeSetState До смены состояния сущности в жизненном цикле context, entity, oldStateId, newStateId
AfterSetState После смены состояния context, entity, oldStateId, newStateId
BeforeStartWorkflow Перед запуском воркфлоу по сущности context, contextEntity

Их можно переопределять так же, как остальные хуки (по умолчанию — пустая реализация).

Параметры методов

В методы передаются параметры:

  • context — контекст обработчика (CustomHooksContext). Представляет собой SDK для ограниченной работы с приложением.
  • detachedEntity — сущность, переданная через API пользователем.
  • contextEntity — сущность, извлечённая из БД для обновления. При вставке идентична detachedEntity.

Доступ к обработчику

Примечание

Для доступа требуются права на гранулу Настройки менеджера объектов.

Чтобы изменить обработчик:

  1. Перейти в компоненту Менеджер сущностей.
  2. Найти нужную сущность.
  3. Открыть вкладку Обработчик.

Вкладка обработчика

В настройках генерируется класс с именем CustomHooks, наследующий EntityTypeCustomHooks<ИмяТипаСущности>.


CustomHooksContext

Объект контекста наследует DynamicCodeContext и предоставляет доступ к базовому API системы.

Базовый интерфейс (DynamicCodeContext)

public interface DynamicCodeContext
{
    /// Тип операции, для которой вызван обработчик
    EntityServiceOperation OperationType { get; }

    /// Событие до коммита транзакции
    event Func<Task> BeforeCommit;

    /// Событие после коммита транзакции
    event Func<Task> AfterCommit;

    /// Сервис работы с сущностями
    IContextEntityService<TEntity> GetEntityService<TEntity>();

    /// Логирование (асинхронное, с уровнем)
    Task Log(string message, DynamicCodeLogLevel level = DynamicCodeLogLevel.Info);
}

Дополнительные возможности контекста

  • GetLifecycleService<TEntity>() — сервис жизненного цикла для сущностей со состояниями (IStatedEntity).
  • GetNotificationService() — сервис уведомлений.
  • GetHttpClient() — HTTP-клиент для исходящих вызовов (например, к API).
  • GetLicenseProductLimitsAsync() — лимиты по продуктам лицензии.
  • GetUserScheduleForPeriodAsync(beginDate, endDate, userId) — расписание пользователя на период.
  • GetWorkResourcePlan(entityId) — ресурсный план по работе.

Логирование

Сигнатура метода логирования — асинхронная, с уровнем важности:

Task Log(string message, DynamicCodeLogLevel level = DynamicCodeLogLevel.Info);

Уровни: DynamicCodeLogLevel.Info, DynamicCodeLogLevel.Error. Сообщения записываются в сущность «Лог обработчика» (CustomHookLog) и могут просматриваться в системе. Вызовы пользовательского кода обработчика также оборачиваются системой: в журнал пишутся факт старта и завершения каждого хука, а при исключении — сообщение и стек (уровень Error).


IContextEntityService

Интерфейс сервиса работы с сущностями в контексте:

public interface IContextEntityService<TEntity> where TEntity : class, IEntity
{
    Task<TEntity> InsertAsync(TEntity detachedEntity, bool checkRights = true);
    Task<TEntity> UpdateAsync(TEntity detachedEntity, bool checkRights = true);
    Task<TEntity> DeleteAsync(Guid entityId, bool checkRights = true);
    Task<TEntity> GetAsync(Guid entityId, bool checkRights = true);

    IQueryable<TEntity> Get(
        Expression<Func<TEntity, bool>> condition = null,
        bool checkRights = true
    );

    Task CheckCanInsertAsync(TEntity entity);
    Task CheckCanEditAsync(TEntity entity);
    Task CheckCanEditAsync(Guid entityId);
    Task CheckCanDeleteAsync(TEntity entity);
    Task CheckCanExecuteAsync(Guid entityId, string granular);
    Task CheckCanViewAsync(Guid entityId, string granularName = null);

    Task<IQueryable<TCollectionType>> GetNavigationAsync<TCollectionType>(
        Guid key,
        Expression<Func<TCollectionType, Guid>> foreignKeyExpression
    ) where TCollectionType : Entity;

    Task<List<TCollectionType>> UpdateNavigationAsync<TCollectionType>(
        Guid key,
        Expression<Func<TCollectionType, Guid>> foreignKeyExpression,
        ICollection<TCollectionType> collection,
        Func<TCollectionType, object> groupByExpression = null
    ) where TCollectionType : Entity;
}
  • InsertAsync, UpdateAsync, DeleteAsync, GetAsync, Get — основные CRUD-операции и выборка. Параметр checkRights определяет, проверять ли права текущего пользователя.
  • CheckCanInsertAsync, CheckCanEditAsync, CheckCanDeleteAsync, CheckCanExecuteAsync, CheckCanViewAsync — проверка прав; при отсутствии прав выбрасывается исключение.
  • GetNavigationAsync, UpdateNavigationAsync — чтение и обновление связанных коллекций с учётом прав.

Примеры

Хуки CRUD

Проверка перед сохранением и логирование после создания/обновления/удаления:

public class CustomHooks : EntityTypeCustomHooks<Activity>
{
    public override async Task BeforeUpsert(
        CustomHooksContext context,
        Activity detachedEntity)
    {
        if (string.IsNullOrWhiteSpace(detachedEntity.Name))
            await context.Log("Имя не задано", DynamicCodeLogLevel.Error);
        await Task.CompletedTask;
    }

    public override async Task AfterUpsert(
        CustomHooksContext context,
        Activity contextEntity,
        Activity detachedEntity)
    {
        await context.Log($"Сущность сохранена: {contextEntity.Id}");
        await Task.CompletedTask;
    }

    public override async Task BeforeDelete(
        CustomHooksContext context,
        Activity detachedEntity)
    {
        await context.Log($"Попытка удаления: {detachedEntity.Id}");
        await Task.CompletedTask;
    }

    public override async Task AfterDelete(
        CustomHooksContext context,
        Activity contextEntity)
    {
        await context.Log($"Сущность удалена: {contextEntity.Id}");
        await Task.CompletedTask;
    }
}

Хуки жизненного цикла и воркфлоу

Логирование и уведомления при смене состояния проекта:

public class CustomHooks : EntityTypeCustomHooks<Project>
{
    public override async Task BeforeSetState(
        CustomHooksContext context,
        Project entity,
        Guid oldStateId,
        Guid newStateId)
    {
        await context.Log($"Смена состояния проекта {entity.Id}: {oldStateId}{newStateId}");
        await Task.CompletedTask;
    }

    public override async Task AfterSetState(
        CustomHooksContext context,
        Project entity,
        Guid oldStateId,
        Guid newStateId)
    {
        var notificationService = context.GetNotificationService();
        await context.Log($"Состояние проекта {entity.Id} обновлено на {newStateId}");
        await Task.CompletedTask;
    }

    public override async Task BeforeStartWorkflow(
        CustomHooksContext context,
        Project contextEntity)
    {
        await context.Log($"Запуск воркфлоу для проекта {contextEntity.Id}");
        await Task.CompletedTask;
    }
}

BeforeSetState вызывается до смены состояния (проверки, логирование «до»). AfterSetState — после сохранения нового состояния (интеграции, уведомления). BeforeStartWorkflow — непосредственно перед стартом воркфлоу по сущности.

Исходящий HTTP-вызов

public override async Task AfterUpsert(CustomHooksContext context, Activity contextEntity, Activity detachedEntity)
{
    const string url = "https://api.timetta.com/odata";
    const string token = "***";

    using var httClient = new HttpClient();
    httClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    var newActivity = new { Name = "New Activity" };
    var payload = JsonContent.Create(newActivity);

    var response = await httClient.PostAsync($"{url}/Activities", payload);
    var responseContent = await response.Content.ReadFromJsonAsync<JsonElement>();

    var idOfCreatedEntity = responseContent.GetProperty("id").GetGuid();
}

Для HTTP-запросов из обработчика можно также использовать context.GetHttpClient(), который возвращает настроенный клиент.

Содержание

Назначение и общая логика Хуки операций с сущностью (CRUD) Хуки жизненного цикла и воркфлоу Параметры методов Доступ к обработчику CustomHooksContext Базовый интерфейс (DynamicCodeContext) Дополнительные возможности контекста Логирование IContextEntityService Примеры Хуки CRUD Хуки жизненного цикла и воркфлоу Исходящий HTTP-вызов
Ничего не найдено

Перейти на русскую версию?