Humanresources 26.700.0: триггер на перевод и смену роли, «Появился новый сотрудник» только по отделам и командам
Ломающее обновление
Удалены или изменены публичные API — прикладной код может перестать работать.
В humanresources 26.700.0 новый конструктор процессов получил триггер на перевод сотрудника, смену его роли и увольнение, а в обмене с 1С для КЭДО появились исключения из сопоставления и отключение компании. Ломаются сценарии «Появился новый сотрудник» с поимённо выбранными людьми, а AJAX Mapper.delete и $arResult грида сотрудников 1С сменили контракт. Прошлый разбор модуля — Humanresources 26.600.0.
Новое
Запускайте сценарий при переводе и смене роли
Триггер CBPStructurePositionChangedTrigger называется в каталоге «Изменилась позиция сотрудника в структуре», в официальном описании версии — «Изменение позиции в структуре». В обязательном поле «Типы изменений» выбирают, на что реагировать: добавление в отдел или команду, исключение или перевод, назначение на структурную роль (руководитель, заместитель и другие) или снятие с неё. В поле «Сотрудники» можно выбрать только людей или выражения, пустое поле значит «любой сотрудник».
Триггер отдаёт сотрудника (USER), тип изменения (CHANGE_TYPE: added, removed, moved, role_assigned, role_revoked), прежние и новые отделы и команды (ID и названия), прежнюю и новую роль (MEMBER_HEAD, MEMBER_TEAM_DEPUTY_HEAD и другие) и время изменения.
Изменения членства модуль пишет в журнал и сверяет со структурой в фоновой задаче после хита. На сотрудника за хит приходится одно изменение. Исключение вместе с добавлением дают moved, перенос участников удалённого отдела в родительский тоже. Назначение руководителем или заместителем даёт role_assigned, остальные смены роли дают role_revoked. Виртуальных пользователей триггер не видит, а без bizproc или на несконвертированной структуре журнал выключен. Процесс стартует вне текущего запроса, ошибки запуска уходят в логгер humanresources.structurePositionJournal. Увольнение тоже доходит до триггера: новый обработчик intranet:onAfterUserFire на сконвертированной структуре деактивирует все членства уволенного, сверка видит это как removed, а выборки по активному членству, например getCommonUsersFromRelation(), перестают его считать.
Узел «Получить данные отделов и команд» пока не появится
В официальном описании версии узел CBPGetStructureDataNode заявлен как добавленный, но в каталог конструктора не попадает. Его .description.php требует класс Bitrix\Bizproc\Public\Feature\Node\GetStructureDataNodeFlag, а в bizproc 26.1800.0 такого нет. Узел будет отдавать отделы и команды от выбранного узла с руководителями, заместителями и сотрудниками, когда флаг появится в bizproc и его включат.
Проверяйте отделы в своём триггере тем же фильтром
Привязку триггеров сотрудников к отделам и командам вынесли в классы StructureNodeFilter, StructureNodeBinding и StructureNodeValue из Integration\Bizproc. На них построены «Появился новый сотрудник» и «Сотрудник уволен» из bizproc (разбор Bizproc 26.1800.0), второй учитывает неактивные членства (withInactiveMembership: true). Свой триггер может проверять привязку тем же StructureNodeFilter::check(int $userId, mixed $storedValue, mixed $resolvedValue, bool $withInactiveMembership = false).
Селектор привязки показывает всю структуру без проверки прав. По комментарию в коде, доступ к конструктору сценариев сам даёт такое право.
Исключайте сотрудников из сопоставления с 1С
В гриде сопоставленных сотрудников появился список людей, которых не связывают с персонами 1С. В окне подготовки документа несопоставленного участника можно исключить на месте, процент синхронизации его не считает. Ещё окно может попросить sign убрать участника из документа событием BX.Humanresources.HcmLink.DataMapper:onMemberRemoveRequested ({ userId, companyId, documentUid }).
Из своего кода исключениями управляет AJAX-контроллер humanresources.HcmLink.Exclusion с действиями add, delete и list (companyId, userIds). Только POST с JSON, до 500 пользователей за вызов. В JS те же вызовы и disconnectCompany() есть в Api расширения humanresources.hcmlink.api, код ошибки из ответа достаёт getErrorCode().
Исключение в той же транзакции снимает связь с персоной 1С. Ручное сопоставление и REST humanresources.hcmlink.employee.set наоборот убирают человека из исключений, данные 1С важнее. Mapper.load в режиме direct отдаёт ещё excludedUserIds, matchedUserIds и canManageExclusions. Исключениями управляет тот, кто работает с интеграцией: при модуле sign обладатель права ACTION_B2E_DOCUMENT_ADD, без sign администратор. Для добавления нужна ещё функция sign hcmLinkExclusions, её включает Sign 26.1100.0. До него добавление отвечает HCMLINK_ACCESS_DENIED, а кнопки нет.
Отключайте компанию 1С из плитки
Администратор портала видит в плитке компании (humanresources.hcmlink.company.list) пункт «Отключить интеграцию». Действие humanresources.HcmLink.Company.disconnect через CompanyDisconnectService::disconnect() в одной транзакции удаляет поля, исключения и компанию. По комментарию в коде, обычное право на интеграцию роль «Сотрудник» по умолчанию даёт всем, поэтому отключать может только администратор. REST humanresources.hcmlink.company.delete идёт через тот же сервис, на отсутствующую компанию по-прежнему отвечает ObjectNotFoundException, на остальные ошибки — RestException с кодом сервиса.
Следите за обменом с 1С из своего кода
Public\Service\Container::getHcmLinkMonitoringService() отдаёт сводку обмена заданиями с 1С. isAvailable() отвечает, доступна ли интеграция и подключена ли хоть одна компания. У каждой компании из getSummary()->companies есть health() (enum ExchangeHealth: NO_DATA, OK, UNFINISHED, PARTIAL, BROKEN, вердикт по последним суткам), счётчики заданий за месяц и сутки и время последнего ответа 1С. portalHealth() возвращает BROKEN, только когда сломаны все компании с заданиями, иначе null. Сам модуль сервис пока не вызывает, права сервис не проверяет, описание — в lib/Public/docs/hcm-link-monitoring.md.
<?php declare(strict_types=1);
namespace Vendor\Hr\HcmLink;
use Bitrix\HumanResources\Public\Service\Container;
use Bitrix\HumanResources\Type\HcmLink\Monitoring\ExchangeHealth;
use Bitrix\Main\Engine\CurrentUser;
use Bitrix\Main\Loader;
final class ExchangeAlert
{
/**
* @return list<string> компании, где обмен с 1С не работает или теряет задания
*/
public function troubledCompanies(): array
{
// Сервис прав не проверяет: сводку видит только администратор
if (!CurrentUser::get()->isAdmin() || !Loader::includeModule('humanresources')) {
return [];
}
$monitoring = Container::getHcmLinkMonitoringService();
if (!$monitoring->isAvailable()) {
return [];
}
$titles = [];
foreach ($monitoring->getSummary()->companies as $company) {
if (in_array($company->health(), [ExchangeHealth::BROKEN, ExchangeHealth::PARTIAL], true)) {
$titles[] = $company->companyTitle;
}
}
return $titles;
}
}
Читайте структуру новыми методами публичных сервисов
NodeService::getNodeIdsGroupedByAccessCode(array $accessCodes)— ID узлов по каждому коду доступа.NodeService::findAll()иfindAllByIds()сviewerUserIdиstructureActionпроверяют права этого пользователя вместо текущего.NodeMemberService::findAllByEntityIds(..., memberActive: null)снимает фильтр по активности членства, по умолчаниюtrue.Department\UserService::filterActiveEmployeeIds(array $userIds)оставляет сотрудников с активным членством в отделе.
Что сломается
Пересохраните «Появился новый сотрудник» без поимённых людей
Свойство триггера теперь называется «Подразделения и команды», и селектор предлагает только их. Если в опубликованной привязке остался хоть один сотрудник (user_<id>), даже рядом с отделами, она отклоняется целиком (HUMANRESOURCES_NEW_EMPLOYEE_TRIGGER_STALE_USER_CODES), и сценарий не запускается. При пересохранении настроек сотрудники молча выпадают из привязки. Если в ней были только люди, она становится пустой, и сценарий запускается для каждого нового сотрудника. Решите, какими отделами или командами заменить людей, пока никто не пересохранил настройки триггера.
Передавайте companyId в Mapper.delete
Действие humanresources.HcmLink.Mapper.delete стало deleteAction(?int $companyId = null, array $mappingIds = []) и снимает сопоставление только в пределах компании. Без companyId оно отвечает ошибкой COMPANY_ID_REQUIRED. Нечисловой или неположительный ID либо запись другой компании отклоняют весь набор с INVALID_MAPPING_IDS, а строки исключений в гриде имеют ID excl_<userId>. BX.Humanresources.Hcmlink.MappedPerson.deleteLinkMappedPerson() тоже ждёт { companyId } и без аргумента падает. Свой вызов:
// Extension::load(['humanresources.hcmlink.exclusions', 'ui.notification'])
async function unlinkPersons(companyId, selectedRowIds)
{
// строки исключений (excl_<userId>) в mappingIds не отправляем
const { personIds } = BX.Humanresources.Hcmlink.splitGridSelection(selectedRowIds);
if (personIds.length === 0)
{
return;
}
try
{
await BX.ajax.runAction('humanresources.HcmLink.Mapper.delete', {
json: { companyId, mappingIds: personIds },
});
}
catch (response)
{
// COMPANY_ID_REQUIRED или INVALID_MAPPING_IDS: набор отклонён целиком
BX.UI.Notification.Center.notify({ content: BX.Text.encode(response.errors?.[0]?.message ?? 'Ошибка') });
}
}
Обновите копии шаблона humanresources.hcmlink.mapped.users
Компонент больше не отдаёт $arResult['MAPPED_PERSONS'] и $arResult['USERS']. Строки грида приходят готовыми и уже экранированными в $arResult['ROWS'], добавлены флаги CAN_MANAGE_EXCLUSIONS и IS_EXCLUSION_LIST. Кнопку удаления в копии переведите на deleteLinkMappedPerson({ companyId }), номер компании лежит в $arResult['COMPANY_ID'].
Допишите методы в свои реализации интерфейсов
Это касается только своих классов, подменённых через ServiceLocator. В Contract\Repository\HcmLink\PersonRepository добавлены filterMappedUserIds(int $companyId, array $userIds): array и dropLinksByUserIds(int $companyId, array $userIds): int, в Integration\Bizproc\ProcessLauncherInterface — launchForPositionChanges(StructurePositionChange ...$changes): Result. Без них класс не загрузится. Обёртку над штатным лаунчером проще дописать делегированием:
<?php declare(strict_types=1);
namespace Vendor\Hr\Bizproc;
use Bitrix\HumanResources\Integration\Bizproc\ProcessLauncher;
use Bitrix\HumanResources\Integration\Bizproc\ProcessLauncherInterface;
use Bitrix\HumanResources\Integration\Bizproc\StructurePositionChange;
use Bitrix\Main\Diag\Debug;
use Bitrix\Main\Result;
final class LoggingProcessLauncher implements ProcessLauncherInterface
{
public function __construct(
private readonly ProcessLauncherInterface $inner = new ProcessLauncher(),
) {
}
public function launchForNewEmployee(int $userId): Result
{
return $this->logFailure($this->inner->launchForNewEmployee($userId));
}
// Метод из 26.700.0: без него класс не загрузится
public function launchForPositionChanges(StructurePositionChange ...$changes): Result
{
return $this->logFailure($this->inner->launchForPositionChanges(...$changes));
}
private function logFailure(Result $result): Result
{
if (!$result->isSuccess()) {
Debug::writeToFile($result->getErrorMessages(), 'hr-launcher', '/local/logs/hr-launcher.log');
}
return $result;
}
}
БД
Новая таблица b_hr_hcmlink_exclusion (ORM Model\HcmLink\ExclusionTable) с полями ID, COMPANY_ID, USER_ID, CREATED_BY, CREATED_AT, пара COMPANY_ID + USER_ID в ней не повторяется. Пишите через ExclusionService, прямая вставка не снимет связь с персоной 1С.
Мелочи и находки
- Закрыли две дыры.
NodeMemberRepository::getCommonUsersFromRelation()собирал SQL из ID без приведения типов, теперь ID приводятся к int и идут плейсхолдерами. Так же чистит входNodeRelationService::getUsersNotInRelation(), его вызывают im и socialnetwork. Грид сотрудников 1С теперь экранирует имена персон и пользователей. - При удалении отдела участники переезжают в родительский постранично по
ENTITY_ID. Раньшеoffsetв отделе больше 1000 человек перескакивал через часть участников. - Мастер оргструктуры при создании пускает назад без проверки шага, на шаге сотрудников стоит компактная заглушка поиска (
getCompactSearchTabOptions()вcompany-structure/structure-components). Запросыcompany-structure/apiидут безContent-Type: application/json, и, по официальному описанию, сетевые фильтры портала больше не блокируют создание отдела. - REST
humanresources.node.member.setна сбой удаления отвечаетMEMBER_DELETE_FAILED.