Initial commit
This commit is contained in:
2
custom/Espo/Modules/.htaccess
Normal file
2
custom/Espo/Modules/.htaccess
Normal file
@@ -0,0 +1,2 @@
|
||||
Order Deny,Allow
|
||||
Deny from all
|
||||
35
custom/Espo/Modules/Advanced/Binding.php
Normal file
35
custom/Espo/Modules/Advanced/Binding.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced;
|
||||
|
||||
use Espo\Core\Binding\Binder;
|
||||
use Espo\Core\Binding\BindingProcessor;
|
||||
use Espo\Modules\Advanced\Core\SignalManager;
|
||||
use Espo\Modules\Advanced\Core\Workflow\Helper as WorkflowHelper;
|
||||
use Espo\Modules\Advanced\Core\WorkflowManager;
|
||||
|
||||
class Binding implements BindingProcessor
|
||||
{
|
||||
public function process(Binder $binder): void
|
||||
{
|
||||
$binder->bindService(WorkflowManager::class, 'workflowManager');
|
||||
$binder->bindService(WorkflowHelper::class, 'workflowHelper');
|
||||
$binder->bindService(SignalManager::class, 'signalManager');
|
||||
}
|
||||
}
|
||||
1366
custom/Espo/Modules/Advanced/Business/Report/EmailBuilder.php
Normal file
1366
custom/Espo/Modules/Advanced/Business/Report/EmailBuilder.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Business\Workflow\AssignmentRules;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Advanced\Tools\Report\ReportHelper;
|
||||
use Espo\Modules\Advanced\Tools\Report\Service;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
use PDO;
|
||||
|
||||
class LeastBusy
|
||||
{
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
/** @var string */
|
||||
private $entityType;
|
||||
/** @var Service */
|
||||
private $reportService;
|
||||
/** @var ReportHelper */
|
||||
private $reportHelper;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
Service $reportService,
|
||||
string $entityType,
|
||||
ReportHelper $reportHelper
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->reportService = $reportService;
|
||||
$this->entityType = $entityType;
|
||||
$this->reportHelper = $reportHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, mixed>|WhereItem|null $whereClause
|
||||
* @return array<string, mixed>
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
*/
|
||||
public function getAssignmentAttributes(
|
||||
Entity $entity,
|
||||
string $targetTeamId,
|
||||
?string $targetUserPosition,
|
||||
?string $listReportId = null,
|
||||
$whereClause = null
|
||||
): array {
|
||||
|
||||
$team = $this->entityManager->getEntityById(Team::ENTITY_TYPE, $targetTeamId);
|
||||
|
||||
if (!$team) {
|
||||
throw new Error("LeastBusy: No team $targetTeamId.");
|
||||
}
|
||||
|
||||
$where = [
|
||||
'isActive' => true,
|
||||
];
|
||||
|
||||
if ($targetUserPosition) {
|
||||
$where['@relation.role'] = $targetUserPosition;
|
||||
}
|
||||
|
||||
$userList = $this->entityManager
|
||||
->getRDBRepository(Team::ENTITY_TYPE)
|
||||
->getRelation($team, 'users')
|
||||
->select('id')
|
||||
->order('userName')
|
||||
->where($where)
|
||||
->find();
|
||||
|
||||
if (is_countable($userList) && count($userList) === 0) {
|
||||
throw new Error("LeastBusy: No users in team $targetTeamId.");
|
||||
}
|
||||
|
||||
$userIdList = [];
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userIdList[] = $user->getId();
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
|
||||
foreach ($userIdList as $id) {
|
||||
$counts[$id] = 0;
|
||||
}
|
||||
|
||||
$selectBuilder = SelectBuilder::create()->from($this->entityType);
|
||||
|
||||
if ($listReportId) {
|
||||
/** @var ?Report $report */
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $listReportId);
|
||||
|
||||
if (!$report) {
|
||||
throw new Error("No report $listReportId.");
|
||||
}
|
||||
|
||||
$this->reportHelper->checkReportCanBeRun($report);
|
||||
|
||||
$selectBuilder = $this->reportService->prepareSelectBuilder($report);
|
||||
}
|
||||
|
||||
$selectBuilder
|
||||
->where([
|
||||
'assignedUserId' => $userIdList,
|
||||
'id!=' => $entity->hasId() ? $entity->getId() : null,
|
||||
])
|
||||
->group('assignedUserId')
|
||||
->select([
|
||||
'assignedUserId',
|
||||
['COUNT:(id)', 'COUNT:id'],
|
||||
])
|
||||
->order([])
|
||||
->order('assignedUserId')
|
||||
->leftJoin('assignedUser', 'assignedUserAssignedRule')
|
||||
->where(['assignedUserAssignedRule.isActive' => true]);
|
||||
|
||||
if ($whereClause) {
|
||||
$selectBuilder->where($whereClause);
|
||||
}
|
||||
|
||||
$sth = $this->entityManager
|
||||
->getQueryExecutor()
|
||||
->execute($selectBuilder->build());
|
||||
|
||||
$rowList = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($rowList as $row) {
|
||||
$id = $row['assignedUserId'];
|
||||
|
||||
if (!$id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$counts[$id] = $row['COUNT:id'];
|
||||
}
|
||||
|
||||
$countValues = array_values($counts);
|
||||
|
||||
if (!count($countValues)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$minCount = min($countValues);
|
||||
$minCountIdList = [];
|
||||
|
||||
foreach ($counts as $id => $count) {
|
||||
if ($count === $minCount) {
|
||||
$minCountIdList[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!count($minCountIdList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
|
||||
$attributes['assignedUserId'] = $minCountIdList[array_rand($minCountIdList)];
|
||||
|
||||
/** @var ?User $user */
|
||||
$user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $attributes['assignedUserId']);
|
||||
|
||||
if ($user) {
|
||||
$attributes['assignedUserName'] = $user->getName();
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Business\Workflow\AssignmentRules;
|
||||
|
||||
use Espo\Entities\Team;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Modules\Advanced\Entities\WorkflowRoundRobin;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
class RoundRobin
|
||||
{
|
||||
/** @var EntityManager */
|
||||
private $entityManager;
|
||||
/** @var string */
|
||||
private $entityType;
|
||||
/** @var string */
|
||||
private $actionId;
|
||||
/** @var string|null */
|
||||
private $workflowId;
|
||||
/** @var string|null */
|
||||
private $flowchartId;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
string $entityType,
|
||||
string $actionId,
|
||||
?string $workflowId = null,
|
||||
?string $flowchartId = null
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entityType = $entityType;
|
||||
$this->actionId = $actionId;
|
||||
$this->workflowId = $workflowId;
|
||||
$this->flowchartId = $flowchartId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed>|WhereItem|null $whereClause
|
||||
* @return array<string, mixed>
|
||||
* @throws Error
|
||||
*/
|
||||
public function getAssignmentAttributes(
|
||||
Entity $entity,
|
||||
string $targetTeamId,
|
||||
?string $targetUserPosition,
|
||||
?string $listReportId = null,
|
||||
$whereClause = null
|
||||
): array {
|
||||
|
||||
$team = $this->entityManager->getEntityById(Team::ENTITY_TYPE, $targetTeamId);
|
||||
|
||||
if (!$team) {
|
||||
throw new Error('RoundRobin: No team with id ' . $targetTeamId);
|
||||
}
|
||||
|
||||
$where = [
|
||||
'isActive' => true,
|
||||
];
|
||||
|
||||
if ($targetUserPosition) {
|
||||
$where['@relation.role'] = $targetUserPosition;
|
||||
}
|
||||
|
||||
$userList = $this->entityManager
|
||||
->getRDBRepository(Team::ENTITY_TYPE)
|
||||
->getRelation($team, 'users')
|
||||
->select('id')
|
||||
->order('id')
|
||||
->where($where)
|
||||
->find();
|
||||
|
||||
if (is_countable($userList) && count($userList) === 0) {
|
||||
throw new Error('RoundRobin: No users found in team ' . $targetTeamId);
|
||||
}
|
||||
|
||||
$userIdList = [];
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userIdList[] = $user->getId();
|
||||
}
|
||||
|
||||
// @todo lock table ?
|
||||
|
||||
$lastUserId = $this->getLastUserId();
|
||||
|
||||
if ($lastUserId === null) {
|
||||
$num = 0;
|
||||
}
|
||||
else {
|
||||
$num = array_search($lastUserId, $userIdList);
|
||||
|
||||
if ($num === false || $num == count($userIdList) - 1) {
|
||||
$num = 0;
|
||||
}
|
||||
else {
|
||||
$num++;
|
||||
}
|
||||
}
|
||||
|
||||
$userId = $userIdList[$num];
|
||||
|
||||
$this->storeLastUserId($userId);
|
||||
|
||||
$attributes = ['assignedUserId' => $userId];
|
||||
|
||||
if ($attributes['assignedUserId']) {
|
||||
/** @var ?User $user */
|
||||
$user = $this->entityManager->getEntityById(User::ENTITY_TYPE, $attributes['assignedUserId']);
|
||||
|
||||
if ($user) {
|
||||
$attributes['assignedUserName'] = $user->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
private function getLastUserId(): ?string
|
||||
{
|
||||
$item = $this->entityManager
|
||||
->getRDBRepository(WorkflowRoundRobin::ENTITY_TYPE)
|
||||
->select(['lastUserId'])
|
||||
->where([
|
||||
'actionId' => $this->actionId,
|
||||
'workflowId' => $this->workflowId,
|
||||
'flowchartId' => $this->flowchartId,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $item->get('lastUserId');
|
||||
}
|
||||
|
||||
private function storeLastUserId(string $userId): void
|
||||
{
|
||||
$item = $this->entityManager
|
||||
->getRDBRepository(WorkflowRoundRobin::ENTITY_TYPE)
|
||||
->select(['id', 'lastUserId'])
|
||||
->where([
|
||||
'actionId' => $this->actionId,
|
||||
'workflowId' => $this->workflowId,
|
||||
'flowchartId' => $this->flowchartId,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$item) {
|
||||
$this->entityManager->createEntity(WorkflowRoundRobin::ENTITY_TYPE, [
|
||||
'actionId' => $this->actionId,
|
||||
'lastUserId' => $userId,
|
||||
'entityType' => $this->entityType,
|
||||
'workflowId' => $this->workflowId,
|
||||
'flowchartId' => $this->flowchartId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$item->set('lastUserId', $userId);
|
||||
|
||||
$this->entityManager->saveEntity($item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Acl\BpmnFlowNode;
|
||||
|
||||
use Espo\Core\Acl\AccessChecker as AccessCheckerInterface;
|
||||
use Espo\Core\Acl\AccessReadChecker;
|
||||
use Espo\Core\Acl\ScopeData;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
|
||||
class AccessChecker implements AccessCheckerInterface, AccessReadChecker
|
||||
{
|
||||
private AclManager $aclManager;
|
||||
|
||||
public function __construct(AclManager $aclManager)
|
||||
{
|
||||
$this->aclManager = $aclManager;
|
||||
}
|
||||
|
||||
public function check(User $user, ScopeData $data): bool
|
||||
{
|
||||
return $this->aclManager->checkScope($user, BpmnProcess::ENTITY_TYPE);
|
||||
}
|
||||
|
||||
public function checkRead(User $user, ScopeData $data): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Acl\BpmnProcess;
|
||||
|
||||
use Espo\Core\Acl\DefaultOwnershipChecker;
|
||||
use Espo\Core\Acl\OwnershipOwnChecker;
|
||||
use Espo\Core\Acl\OwnershipTeamChecker;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements OwnershipOwnChecker<BpmnProcess>
|
||||
* @implements OwnershipTeamChecker<BpmnProcess>
|
||||
*/
|
||||
class OwnershipChecker implements OwnershipOwnChecker, OwnershipTeamChecker
|
||||
{
|
||||
private DefaultOwnershipChecker $defaultOwnershipChecker;
|
||||
private EntityManager $entityManager;
|
||||
|
||||
public function __construct(
|
||||
DefaultOwnershipChecker $defaultOwnershipChecker,
|
||||
EntityManager $entityManager
|
||||
) {
|
||||
$this->defaultOwnershipChecker = $defaultOwnershipChecker;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BpmnProcess $entity
|
||||
*/
|
||||
public function checkOwn(User $user, Entity $entity): bool
|
||||
{
|
||||
if (!$entity->getParentProcessId() || $entity->getParentProcessId() === $entity->getId()) {
|
||||
return $this->defaultOwnershipChecker->checkOwn($user, $entity);
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntityById(BpmnProcess::ENTITY_TYPE, $entity->getParentProcessId());
|
||||
|
||||
if (!$parent instanceof CoreEntity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->defaultOwnershipChecker->checkOwn($user, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BpmnProcess $entity
|
||||
*/
|
||||
public function checkTeam(User $user, Entity $entity): bool
|
||||
{
|
||||
if (!$entity->getParentProcessId() || $entity->getParentProcessId() === $entity->getId()) {
|
||||
return $this->defaultOwnershipChecker->checkTeam($user, $entity);
|
||||
}
|
||||
|
||||
$parent = $this->entityManager->getEntityById(BpmnProcess::ENTITY_TYPE, $entity->getParentProcessId());
|
||||
|
||||
if (!$parent instanceof CoreEntity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->defaultOwnershipChecker->checkTeam($user, $parent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Acl\Report;
|
||||
|
||||
use Espo\Core\Acl\AccessEntityCREDChecker;
|
||||
use Espo\Core\Acl\DefaultAccessChecker;
|
||||
use Espo\Core\Acl\ScopeData;
|
||||
use Espo\Core\Acl\Traits\DefaultAccessCheckerDependency;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements AccessEntityCREDChecker<Report>
|
||||
*/
|
||||
class AccessChecker implements AccessEntityCREDChecker
|
||||
{
|
||||
use DefaultAccessCheckerDependency;
|
||||
|
||||
private AclManager $aclManager;
|
||||
|
||||
public function __construct(
|
||||
DefaultAccessChecker $defaultAccessChecker,
|
||||
AclManager $aclManager
|
||||
) {
|
||||
$this->defaultAccessChecker = $defaultAccessChecker;
|
||||
$this->aclManager = $aclManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Report $entity
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (
|
||||
$entity->getTargetEntityType() &&
|
||||
!$this->aclManager->checkScope($user, $entity->getTargetEntityType())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->defaultAccessChecker->checkEntityRead($user, $entity, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\AclPortal\Report;
|
||||
|
||||
use Espo\Core\Acl\AccessEntityCREDChecker;
|
||||
use Espo\Core\Acl\ScopeData;
|
||||
use Espo\Core\Portal\Acl\DefaultAccessChecker;
|
||||
use Espo\Core\Portal\Acl\Traits\DefaultAccessCheckerDependency;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements AccessEntityCREDChecker<Report>
|
||||
*/
|
||||
class AccessChecker implements AccessEntityCREDChecker
|
||||
{
|
||||
use DefaultAccessCheckerDependency;
|
||||
|
||||
public function __construct(DefaultAccessChecker $defaultAccessChecker)
|
||||
{
|
||||
$this->defaultAccessChecker = $defaultAccessChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Report $entity
|
||||
*/
|
||||
public function checkEntityRead(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
if (!$this->defaultAccessChecker->checkEntityRead($user, $entity, $data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$user->getPortalId()) {
|
||||
$portalIdList = $user->getLinkMultipleIdList('portals');
|
||||
|
||||
return count(
|
||||
array_intersect($portalIdList, $entity->getPortals()->getIdList())
|
||||
) > 0;
|
||||
}
|
||||
|
||||
return in_array($user->getPortalId(), $entity->getPortals()->getIdList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\AppParams;
|
||||
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Workflow;
|
||||
use Espo\ORM\EntityManager;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class ManualWorkflows
|
||||
{
|
||||
private EntityManager $entityManager;
|
||||
private User $user;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
User $user
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$data = (object) [];
|
||||
|
||||
$builder = $this->entityManager
|
||||
->getRDBRepositoryByClass(Workflow::class)
|
||||
->where([
|
||||
'type' => Workflow::TYPE_MANUAL,
|
||||
'isActive' => true,
|
||||
]);
|
||||
|
||||
if (!$this->user->isAdmin()) {
|
||||
$builder
|
||||
->distinct()
|
||||
->join('manualTeams')
|
||||
->where(['manualTeams.id' => $this->user->getTeamIdList()]);
|
||||
|
||||
$builder->where(['manualAccessRequired!=' => 'admin']);
|
||||
}
|
||||
|
||||
/** @var Workflow[] $workflows */
|
||||
$workflows = iterator_to_array($builder->find());
|
||||
|
||||
usort($workflows, function (Workflow $a, Workflow $b) {
|
||||
return strcmp($a->getManualLabel() ?? '', $b->getManualLabel() ?? '');
|
||||
});
|
||||
|
||||
foreach ($workflows as $workflow) {
|
||||
$entityType = $workflow->getTargetEntityType();
|
||||
|
||||
if (!property_exists($data, $entityType)) {
|
||||
$data->$entityType = [];
|
||||
}
|
||||
|
||||
$item = (object) [
|
||||
'id' => $workflow->getId(),
|
||||
'label' => $workflow->get('manualLabel'),
|
||||
'accessRequired' => $workflow->get('manualAccessRequired'),
|
||||
'elementType' => $workflow->get('manualElementType'),
|
||||
'dynamicLogic' => $workflow->get('manualDynamicLogic'),
|
||||
'confirmation' => $workflow->get('manualConfirmation'),
|
||||
'confirmationText' => $workflow->get('manualConfirmationText'),
|
||||
'style' => $workflow->get('manualStyle'),
|
||||
];
|
||||
|
||||
$data->$entityType[] = $item;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\AssignmentNotificators;
|
||||
|
||||
use Espo\Core\Notification\AssignmentNotificator;
|
||||
use Espo\Core\Notification\AssignmentNotificator\Params;
|
||||
use Espo\Entities\Notification;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\BpmnUserTask as BpmnUserTaskEntity;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements AssignmentNotificator<BpmnUserTaskEntity>
|
||||
*/
|
||||
class BpmnUserTask implements AssignmentNotificator
|
||||
{
|
||||
private EntityManager $entityManager;
|
||||
private User $user;
|
||||
|
||||
public function __construct(EntityManager $entityManager, User $user)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
if (!$entity->get('assignedUserId')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->isAttributeChanged('assignedUserId')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assignedUserId = $entity->get('assignedUserId');
|
||||
|
||||
$isNotSelfAssignment = $entity->isNew() ?
|
||||
$assignedUserId !== $entity->get('createdById') :
|
||||
$assignedUserId !== $entity->get('modifiedById');
|
||||
|
||||
if (!$isNotSelfAssignment) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notification = $this->entityManager->getNewEntity(Notification::ENTITY_TYPE);
|
||||
|
||||
$notification->set([
|
||||
'type' => Notification::TYPE_ASSIGN,
|
||||
'userId' => $assignedUserId,
|
||||
'data' => [
|
||||
'entityType' => $entity->getEntityType(),
|
||||
'entityId' => $entity->getId(),
|
||||
'entityName' => $entity->get('name'),
|
||||
'isNew' => $entity->isNew(),
|
||||
'userId' => $this->user->getId(),
|
||||
'userName' => $this->user->get('name')
|
||||
],
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($notification);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\FieldProcessing\BpmnFlowNode;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnUserTask;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements Loader<BpmnUserTask>
|
||||
*/
|
||||
class UserTaskLoader implements Loader
|
||||
{
|
||||
private EntityManager $entityManager;
|
||||
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
if ($entity->getElementType() !== 'taskUser') {
|
||||
return;
|
||||
}
|
||||
|
||||
$userTask = $this->entityManager
|
||||
->getRDBRepository(BpmnUserTask::ENTITY_TYPE)
|
||||
->where(['flowNodeId' => $entity->getId()])
|
||||
->findOne();
|
||||
|
||||
if ($userTask) {
|
||||
$entity->set('userTaskId', $userTask->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\FieldProcessing\BpmnProcess;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
use Espo\ORM\Collection;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @implements Loader<BpmnProcess>
|
||||
*/
|
||||
class FlowchartDataLoader implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
$flowchartData = $entity->get('flowchartData') ?? (object) [];
|
||||
|
||||
$list = $flowchartData->list ?? [];
|
||||
|
||||
$flowNodes = $this->entityManager
|
||||
->getRDBRepositoryByClass(BpmnFlowNode::class)
|
||||
->where(['processId' => $entity->getId()])
|
||||
->order('number', true)
|
||||
->limit(0, 400)
|
||||
->find();
|
||||
|
||||
foreach ($list as $item) {
|
||||
$this->loadOutlineData($item, $flowNodes);
|
||||
}
|
||||
|
||||
$entity->set('flowchartData', $flowchartData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stdClass $item
|
||||
* @param Collection<BpmnFlowNode> $flowNodeList
|
||||
*/
|
||||
private function loadOutlineData($item, $flowNodeList): void
|
||||
{
|
||||
$type = $item->type ?? null;
|
||||
$id = $item->id ?? null;
|
||||
|
||||
if (!$type || !$id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type === 'flow') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($type === 'eventSubProcess' || $type === 'subProcess') {
|
||||
$list = $item->dataList ?? [];
|
||||
|
||||
foreach ($flowNodeList as $flowNode) {
|
||||
if ($flowNode->get('elementId') == $id) {
|
||||
$subProcessId = $flowNode->getDataItemValue('subProcessId');
|
||||
|
||||
$spFlowNodeList = $this->entityManager
|
||||
->getRDBRepositoryByClass(BpmnFlowNode::class)
|
||||
->where(['processId' => $subProcessId])
|
||||
->order('number', true)
|
||||
->limit(0, 400)
|
||||
->find();
|
||||
|
||||
foreach ($list as $spItem) {
|
||||
$this->loadOutlineData($spItem, $spFlowNodeList);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($flowNodeList as $flowNode) {
|
||||
$status = $flowNode->get('status');
|
||||
|
||||
if ($flowNode->get('elementId') == $id) {
|
||||
if ($status === BpmnFlowNode::STATUS_PROCESSED) {
|
||||
$item->outline = 3;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($flowNodeList as $flowNode) {
|
||||
$status = $flowNode->get('status');
|
||||
|
||||
if ($flowNode->get('elementId') == $id) {
|
||||
if ($status === BpmnFlowNode::STATUS_IN_PROCESS) {
|
||||
$item->outline = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($flowNodeList as $flowNode) {
|
||||
$status = $flowNode->get('status');
|
||||
|
||||
if ($flowNode->get('elementId') == $id) {
|
||||
if ($status === BpmnFlowNode::STATUS_PENDING) {
|
||||
$item->outline = 2;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($flowNodeList as $flowNode) {
|
||||
$status = $flowNode->get('status');
|
||||
|
||||
if ($flowNode->get('elementId') == $id) {
|
||||
if (
|
||||
$status === BpmnFlowNode::STATUS_FAILED ||
|
||||
$status === BpmnFlowNode::STATUS_INTERRUPTED
|
||||
) {
|
||||
$item->outline = 4;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\FieldProcessing\Report;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Modules\Advanced\Entities\Report ;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Loader<Report>
|
||||
*/
|
||||
class ChartOneLoader implements Loader
|
||||
{
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
|
||||
$entity->set('chartOneColumns', []);
|
||||
$entity->set('chartOneY2Columns', []);
|
||||
|
||||
if ($entity->getType() === Report::TYPE_GRID) {
|
||||
$chartDataList = $entity->get('chartDataList');
|
||||
|
||||
if ($chartDataList && count($chartDataList)) {
|
||||
$y = $chartDataList[0]->columnList ?? null;
|
||||
$y2 = $chartDataList[0]->y2ColumnList ?? null;
|
||||
|
||||
$entity->set('chartOneColumns', $y);
|
||||
$entity->set('chartOneY2Columns', $y2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\FieldProcessing\Report;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Loader<Report>
|
||||
*/
|
||||
class UnsetPortals implements Loader
|
||||
{
|
||||
private Acl $acl;
|
||||
|
||||
public function __construct(
|
||||
Acl $acl
|
||||
) {
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
if ($this->acl->getPermissionLevel('portalPermission') !== Table::LEVEL_NO) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->clear('portalsIds');
|
||||
$entity->clear('portalsNames');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\FieldProcessing\ReportPanel;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Modules\Advanced\Entities\Report as ReportEntity;
|
||||
use Espo\Modules\Advanced\Entities\ReportPanel;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridType\Helper;
|
||||
use Espo\Modules\Advanced\Tools\Report\ReportHelper;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements Loader<ReportPanel>
|
||||
*/
|
||||
class Additional implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private Helper $helper,
|
||||
private EntityManager $entityManager,
|
||||
private ReportHelper $reportHelper
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
if (
|
||||
$entity->get('reportType') === ReportEntity::TYPE_GRID &&
|
||||
$entity->get('reportId')
|
||||
) {
|
||||
/** @var ?ReportEntity $report */
|
||||
$report = $this->entityManager->getEntityById(ReportEntity::ENTITY_TYPE, $entity->get('reportId'));
|
||||
|
||||
if ($report) {
|
||||
$columnList = $report->getColumns();
|
||||
|
||||
$numericColumnList = [];
|
||||
|
||||
$gridData = $this->reportHelper->fetchGridDataFromReport($report);
|
||||
|
||||
foreach ($columnList as $column) {
|
||||
if ($this->helper->isColumnNumeric($column, $gridData)) {
|
||||
$numericColumnList[] = $column;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(
|
||||
count($report->getGroupBy()) === 1 ||
|
||||
count($report->getGroupBy()) === 0
|
||||
) &&
|
||||
count($numericColumnList) > 1
|
||||
) {
|
||||
array_unshift($numericColumnList, '');
|
||||
}
|
||||
|
||||
$entity->set('columnList', $numericColumnList);
|
||||
}
|
||||
|
||||
$entity->set('columnsData', $report->getColumnsData());
|
||||
}
|
||||
|
||||
$displayType = $entity->get('displayType');
|
||||
$reportType = $entity->get('reportType');
|
||||
$displayTotal = $entity->get('displayTotal');
|
||||
$displayOnlyTotal = $entity->get('displayOnlyTotal');
|
||||
|
||||
if (!$displayType) {
|
||||
if (
|
||||
$reportType === ReportEntity::TYPE_GRID ||
|
||||
$reportType === ReportEntity::TYPE_JOINT_GRID
|
||||
) {
|
||||
if ($displayOnlyTotal) {
|
||||
$displayType = 'Total';
|
||||
}
|
||||
else if ($displayTotal) {
|
||||
$displayType = 'Chart-Total';
|
||||
}
|
||||
else {
|
||||
$displayType = 'Chart';
|
||||
}
|
||||
}
|
||||
else if ($reportType === ReportEntity::TYPE_LIST) {
|
||||
if ($displayOnlyTotal) {
|
||||
$displayType = 'Total';
|
||||
}
|
||||
else {
|
||||
$displayType = 'List';
|
||||
}
|
||||
}
|
||||
|
||||
$entity->set('displayType', $displayType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\FieldValidators\Workflow\Scheduling;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Espo\Core\FieldValidation\Validator;
|
||||
use Espo\Core\FieldValidation\Validator\Data;
|
||||
use Espo\Core\FieldValidation\Validator\Failure;
|
||||
use Espo\Modules\Advanced\Entities\Workflow;
|
||||
use Espo\ORM\Entity;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @implements Validator<Workflow>
|
||||
*/
|
||||
class Valid implements Validator
|
||||
{
|
||||
|
||||
public function validate(Entity $entity, string $field, Data $data): ?Failure
|
||||
{
|
||||
$scheduling = $entity->getScheduling();
|
||||
|
||||
if ($scheduling === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
new CronExpression($scheduling);
|
||||
} catch (Exception) {
|
||||
return Failure::create();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\RecordHooks\BpmnProcess;
|
||||
|
||||
use Espo\Core\Record\Hook\UpdateHook;
|
||||
use Espo\Core\Record\UpdateParams;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements UpdateHook<BpmnProcess>
|
||||
*/
|
||||
class BeforeUpdate implements UpdateHook
|
||||
{
|
||||
public function process(Entity $entity, UpdateParams $params): void
|
||||
{
|
||||
$entity->clear('flowchartId');
|
||||
$entity->clear('targetId');
|
||||
$entity->clear('targetType');
|
||||
$entity->clear('startElementId');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\RecordHooks\BpmnUserTask;
|
||||
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\Hook\UpdateHook;
|
||||
use Espo\Core\Record\UpdateParams;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\BpmnUserTask;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements UpdateHook<BpmnUserTask>
|
||||
*/
|
||||
class BeforeUpdate implements UpdateHook
|
||||
{
|
||||
private User $user;
|
||||
|
||||
public function __construct(
|
||||
User $user
|
||||
) {
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, UpdateParams $params): void
|
||||
{
|
||||
if (!$this->user->isAdmin()) {
|
||||
if ($entity->getFetched('isResolved') && $entity->isAttributeChanged('resolution')) {
|
||||
throw new Forbidden("Can't change resolution. Already resolved.");
|
||||
}
|
||||
|
||||
if ($entity->getFetched('isCanceled') && $entity->isAttributeChanged('resolution')) {
|
||||
throw new Forbidden("Can't change resolution. Already canceled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\RecordHooks\Report;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Record\CreateParams;
|
||||
use Espo\Core\Record\Hook\CreateHook;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements CreateHook<Report>
|
||||
*/
|
||||
class BeforeCreate implements CreateHook
|
||||
{
|
||||
private Acl $acl;
|
||||
private EntityManager $entityManager;
|
||||
|
||||
public function __construct(
|
||||
Acl $acl,
|
||||
EntityManager $entityManager
|
||||
) {
|
||||
$this->acl = $acl;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, CreateParams $params): void
|
||||
{
|
||||
$this->processJointGridBeforeSave($entity);
|
||||
|
||||
if (
|
||||
in_array('applyAcl', $this->acl->getScopeForbiddenFieldList(Report::ENTITY_TYPE, Table::ACTION_EDIT))
|
||||
) {
|
||||
$entity->setApplyAcl();
|
||||
}
|
||||
|
||||
if (!$this->acl->check($entity->getTargetEntityType(), Table::ACTION_READ)) {
|
||||
throw new Forbidden("No 'read' access to target entity type.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function processJointGridBeforeSave(Report $entity): void
|
||||
{
|
||||
if ($entity->getType() !== Report::TYPE_JOINT_GRID) {
|
||||
return;
|
||||
}
|
||||
|
||||
$joinedReportDataList = $entity->get('joinedReportDataList');
|
||||
|
||||
if (!is_array($joinedReportDataList) || !count($joinedReportDataList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$groupCount = 0;
|
||||
|
||||
foreach ($joinedReportDataList as $i => $item) {
|
||||
if (empty($item->id)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
/** @var ?Report $report */
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $item->id);
|
||||
|
||||
if (!$report) {
|
||||
throw new Forbidden('Report not found.');
|
||||
}
|
||||
|
||||
if (!$this->acl->check($report->getTargetEntityType(), Table::ACTION_READ)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$groupBy = $report->getGroupBy();
|
||||
|
||||
if (
|
||||
count($groupBy) > 1 ||
|
||||
$report->getType() !== Report::TYPE_GRID
|
||||
) {
|
||||
throw new Forbidden("Sub-report $item->id is not supported in joint report.");
|
||||
}
|
||||
|
||||
if ($i === 0) {
|
||||
$groupCount = count($groupBy);
|
||||
|
||||
$entityType = $report->getTargetEntityType();
|
||||
$entity->set('entityType', $entityType);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($groupCount !== count($groupBy)) {
|
||||
throw new BadRequest("Sub-reports must have the same Group By number.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\RecordHooks\Report;
|
||||
|
||||
use Espo\Core\Record\Hook\UpdateHook;
|
||||
use Espo\Core\Record\UpdateParams;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements UpdateHook<Report>
|
||||
*/
|
||||
class BeforeUpdate implements UpdateHook
|
||||
{
|
||||
private BeforeCreate $beforeCreate;
|
||||
|
||||
public function __construct(
|
||||
BeforeCreate $beforeCreate
|
||||
) {
|
||||
$this->beforeCreate = $beforeCreate;
|
||||
}
|
||||
|
||||
public function process(Entity $entity, UpdateParams $params): void
|
||||
{
|
||||
if ($entity->isAttributeChanged('type')) {
|
||||
$entity->set('type', $entity->getFetched('type'));
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->getType() !== Report::TYPE_JOINT_GRID &&
|
||||
$entity->isAttributeChanged('entityType')
|
||||
) {
|
||||
$entity->set('entityType', $entity->getFetched('entityType'));
|
||||
}
|
||||
|
||||
$this->beforeCreate->processJointGridBeforeSave($entity);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnFlowchart\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Active implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['isActive' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnFlowchart\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ActiveHasNoneStartEvent implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'isActive' => true,
|
||||
'hasNoneStartEvent' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnFlowchart\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class IsManuallyStartable implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['isActive' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnProcess\AccessControlFilters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Mandatory implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['parentProcessId' => null]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnProcess\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess as BpmnProcessEntity;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Actual implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'status!=' => [
|
||||
BpmnProcessEntity::STATUS_ENDED,
|
||||
BpmnProcessEntity::STATUS_INTERRUPTED,
|
||||
BpmnProcessEntity::STATUS_STOPPED,
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnProcess\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess as BpmnProcessEntity;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Ended implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['status' => BpmnProcessEntity::STATUS_ENDED]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnUserTask\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Actual implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'isResolved' => false,
|
||||
'isCanceled' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnUserTask\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Canceled implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['isCanceled' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\BpmnUserTask\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Resolved implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['isResolved' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Common\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Advanced\Entities\ReportFilter as ReportFilterEntity;
|
||||
use Espo\Modules\Advanced\Tools\Report\Service;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
use RuntimeException;
|
||||
|
||||
class ReportFilter implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $entityType,
|
||||
private EntityManager $entityManager,
|
||||
private Metadata $metadata,
|
||||
private User $user,
|
||||
private Service $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
*/
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
/** @var ?string $reportFilterId */
|
||||
$reportFilterId = $this->metadata
|
||||
->get(['entityDefs', $this->entityType, 'collection', 'filters', $this->name, 'id']);
|
||||
|
||||
if (!$reportFilterId) {
|
||||
throw new RuntimeException("Report Filter $reportFilterId error.");
|
||||
}
|
||||
|
||||
/** @var ?ReportFilterEntity $reportFilter */
|
||||
$reportFilter = $this->entityManager->getEntityById(ReportFilterEntity::ENTITY_TYPE, $reportFilterId);
|
||||
|
||||
if (!$reportFilter) {
|
||||
throw new RuntimeException("Report Filter $reportFilterId not found.");
|
||||
}
|
||||
|
||||
$teamIdList = $reportFilter->getLinkMultipleIdList('teams');
|
||||
|
||||
if (count($teamIdList) && !$this->user->isAdmin()) {
|
||||
$isInTeam = false;
|
||||
$userTeamIdList = $this->user->getLinkMultipleIdList('teams');
|
||||
|
||||
foreach ($userTeamIdList as $teamId) {
|
||||
if (in_array($teamId, $teamIdList)) {
|
||||
$isInTeam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isInTeam) {
|
||||
throw new Forbidden("Access denied to Report Filter $reportFilterId.");
|
||||
}
|
||||
}
|
||||
|
||||
$reportId = $reportFilter->get('reportId');
|
||||
|
||||
if (!$reportId) {
|
||||
throw new RuntimeException('Report Filter error. No report.');
|
||||
}
|
||||
|
||||
/** @var ?Report $report */
|
||||
$report = $this->entityManager->getEntityById(Report::ENTITY_TYPE, $reportId);
|
||||
|
||||
if (!$report) {
|
||||
throw new Error('Report Filter error. Report not found.');
|
||||
}
|
||||
|
||||
$query = $this->service
|
||||
->prepareSelectBuilder($report)
|
||||
->build();
|
||||
|
||||
foreach ($query->getLeftJoins() as $join) {
|
||||
$queryBuilder->leftJoin($join);
|
||||
}
|
||||
|
||||
foreach ($query->getJoins() as $join) {
|
||||
$queryBuilder->join($join);
|
||||
}
|
||||
|
||||
if ($query->getWhere()) {
|
||||
$queryBuilder->where($query->getWhere());
|
||||
}
|
||||
|
||||
if ($query->isDistinct()) {
|
||||
$queryBuilder->distinct();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\AccessControlFilters;
|
||||
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\AclManager;
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Mandatory implements Filter
|
||||
{
|
||||
public function __construct(
|
||||
private User $user,
|
||||
private Metadata $metadata,
|
||||
private AclManager $aclManager
|
||||
) {}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
if (!$this->user->getPortalId()) {
|
||||
$forbiddenEntityTypeList = [];
|
||||
|
||||
$scopes = $this->metadata->get('scopes', []);
|
||||
|
||||
foreach ($scopes as $scope => $defs) {
|
||||
if (empty($defs['entity'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->aclManager->checkScope($this->user, $scope, Table::ACTION_READ)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$forbiddenEntityTypeList[] = $scope;
|
||||
}
|
||||
|
||||
if ($forbiddenEntityTypeList !== []) {
|
||||
$queryBuilder->where(['entityType!=' => $forbiddenEntityTypeList]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$queryBuilder
|
||||
->distinct()
|
||||
->leftJoin('portals', 'portalsAccess')
|
||||
->where(['portalsAccess.id' => $this->user->getPortalId()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class GridType implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['type' => Report::TYPE_GRID]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ListAccounts implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'type' => Report::TYPE_LIST,
|
||||
'entityType' => Account::ENTITY_TYPE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ListContacts implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'type' => Report::TYPE_LIST,
|
||||
'entityType' => Contact::ENTITY_TYPE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Crm\Entities\Lead;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ListLeads implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'type' => Report::TYPE_LIST,
|
||||
'entityType' => Lead::ENTITY_TYPE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Core\Templates\Entities\Company;
|
||||
use Espo\Core\Templates\Entities\Person;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\Modules\Crm\Entities\Account;
|
||||
use Espo\Modules\Crm\Entities\Contact;
|
||||
use Espo\Modules\Crm\Entities\Lead;
|
||||
use Espo\Modules\Crm\Entities\TargetList;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ListTargets implements Filter
|
||||
{
|
||||
private Metadata $metadata;
|
||||
|
||||
public function __construct(Metadata $metadata) {
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$entityTypeList = [
|
||||
Contact::ENTITY_TYPE,
|
||||
Lead::ENTITY_TYPE,
|
||||
User::ENTITY_TYPE,
|
||||
Account::ENTITY_TYPE,
|
||||
];
|
||||
|
||||
foreach ($this->metadata->get(['entityDefs', TargetList::ENTITY_TYPE, 'links']) as $defs) {
|
||||
$entityType = $defs['entity'] ?? null;
|
||||
$type = $defs['type'] ?? null;
|
||||
|
||||
if ($type !== Entity::HAS_MANY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entityType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($entityType, $entityTypeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$templateType = $this->metadata->get(['scopes', $entityType, 'type']);
|
||||
|
||||
if (
|
||||
$templateType !== Person::TEMPLATE_TYPE &&
|
||||
$templateType !== Company::TEMPLATE_TYPE
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entityTypeList[] = $entityType;
|
||||
}
|
||||
|
||||
$queryBuilder->where([
|
||||
'type' => Report::TYPE_LIST,
|
||||
'entityType' => $entityTypeList,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ListType implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['type' => Report::TYPE_LIST]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Report\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Entities\Report;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class ListUsers implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
'type' => Report::TYPE_LIST,
|
||||
'entityType' => User::ENTITY_TYPE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Workflow\AccessControlFilters;
|
||||
|
||||
use Espo\Core\Select\AccessControl\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Mandatory implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['isInternal' => false]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\Select\Workflow\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\ORM\Query\SelectBuilder as QueryBuilder;
|
||||
|
||||
class Active implements Filter
|
||||
{
|
||||
public function apply(QueryBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where(['isActive' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\ServiceActions\Meeting;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Tools\Workflow\Action\RunAction\ServiceAction;
|
||||
use Espo\Modules\Crm\Business\Event\Invitations;
|
||||
use Espo\Modules\Crm\Entities\Call;
|
||||
use Espo\Modules\Crm\Entities\Meeting;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @implements ServiceAction<Meeting|Call>
|
||||
*/
|
||||
class SendInvitations implements ServiceAction
|
||||
{
|
||||
private InjectableFactory $injectableFactory;
|
||||
private EntityManager $entityManager;
|
||||
private User $user;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
EntityManager $entityManager,
|
||||
User $user
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
private function getInvitationManager(): Invitations
|
||||
{
|
||||
return $this->injectableFactory->create(Invitations::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function run(Entity $entity, mixed $data): mixed
|
||||
{
|
||||
if (!$entity instanceof CoreEntity) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$invitationManager = $this->getInvitationManager();
|
||||
$emailHash = [];
|
||||
|
||||
$users = $this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, 'users')
|
||||
->find();
|
||||
|
||||
foreach ($users as $user) {
|
||||
if ($user->getId() === $this->user->getId()) {
|
||||
if (
|
||||
$entity->getLinkMultipleColumn('users', 'status', $user->getId()) ===
|
||||
Meeting::ATTENDEE_STATUS_ACCEPTED
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($user->get('emailAddress') && !array_key_exists($user->get('emailAddress'), $emailHash)) {
|
||||
$invitationManager->sendInvitation($entity, $user, 'users');
|
||||
$emailHash[$user->get('emailAddress')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$contacts = $this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, 'contacts')
|
||||
->find();
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
if ($contact->get('emailAddress') && !array_key_exists($contact->get('emailAddress'), $emailHash)) {
|
||||
$invitationManager->sendInvitation($entity, $contact, 'contacts');
|
||||
$emailHash[$contact->get('emailAddress')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$leads = $this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, 'leads')
|
||||
->find();
|
||||
|
||||
foreach ($leads as $lead) {
|
||||
if ($lead->get('emailAddress') && !array_key_exists($lead->get('emailAddress'), $emailHash)) {
|
||||
$invitationManager->sendInvitation($entity, $lead, 'leads');
|
||||
$emailHash[$lead->get('emailAddress')] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\ServiceActions\Person;
|
||||
|
||||
use Espo\Core\ORM\Entity as CoreEntity;
|
||||
use Espo\Entities\EmailAddress;
|
||||
use Espo\Modules\Advanced\Tools\Workflow\Action\RunAction\ServiceAction;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Repositories\EmailAddress as EmailAddressRepository;
|
||||
|
||||
/**
|
||||
* @implements ServiceAction<CoreEntity>
|
||||
*/
|
||||
class OptOut implements ServiceAction
|
||||
{
|
||||
private EntityManager $entityManager;
|
||||
|
||||
public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @noinspection PhpHierarchyChecksInspection
|
||||
* @noinspection PhpUndefinedClassInspection
|
||||
* @noinspection PhpSignatureMismatchDuringInheritanceInspection
|
||||
*/
|
||||
public function run(Entity $entity, mixed $data): mixed
|
||||
{
|
||||
$targetListId = $data->targetListId ?? null;
|
||||
|
||||
if ($targetListId) {
|
||||
$this->entityManager
|
||||
->getRDBRepository($entity->getEntityType())
|
||||
->getRelation($entity, 'targetLists')
|
||||
->updateColumnsById($targetListId, ['optedOut' => true]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$emailAddress = $entity->get('emailAddress');
|
||||
|
||||
if (!$emailAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var EmailAddressRepository $emailAddressRepository */
|
||||
$emailAddressRepository = $this->entityManager->getRepository(EmailAddress::ENTITY_TYPE);
|
||||
|
||||
$addressEntity = $emailAddressRepository->getByAddress($emailAddress);
|
||||
|
||||
if ($addressEntity) {
|
||||
$addressEntity->set('optOut', true);
|
||||
$this->entityManager->saveEntity($addressEntity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Classes\ServiceActions\User;
|
||||
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Record\ServiceContainer;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Modules\Advanced\Tools\Workflow\Action\RunAction\ServiceAction;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\UserSecurity\Password\Service;
|
||||
|
||||
/**
|
||||
* @implements ServiceAction<User>
|
||||
*/
|
||||
class GenerateAndSendPassword implements ServiceAction
|
||||
{
|
||||
private InjectableFactory $injectableFactory;
|
||||
private ServiceContainer $serviceContainer;
|
||||
|
||||
public function __construct(
|
||||
InjectableFactory $injectableFactory,
|
||||
ServiceContainer $serviceContainer
|
||||
) {
|
||||
$this->injectableFactory = $injectableFactory;
|
||||
$this->serviceContainer = $serviceContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @noinspection PhpHierarchyChecksInspection
|
||||
* @noinspection PhpUndefinedClassInspection
|
||||
* @noinspection PhpSignatureMismatchDuringInheritanceInspection
|
||||
*/
|
||||
public function run(Entity $entity, mixed $data): mixed
|
||||
{
|
||||
if (class_exists("Espo\\Tools\\UserSecurity\\Password\\Service")) {
|
||||
/** @var Service $service */
|
||||
$service = $this->injectableFactory->create("Espo\\Tools\\UserSecurity\\Password\\Service");
|
||||
|
||||
// @todo Support non-admin users.
|
||||
|
||||
$service->generateAndSendNewPasswordForUser($entity->getId());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$service = $this->serviceContainer->get(User::ENTITY_TYPE);
|
||||
|
||||
if (method_exists($service, 'generateNewPasswordForUser')) {
|
||||
$service->generateNewPasswordForUser($entity->getId(), true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
24
custom/Espo/Modules/Advanced/Controllers/BpmnFlowchart.php
Normal file
24
custom/Espo/Modules/Advanced/Controllers/BpmnFlowchart.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Controllers\Record;
|
||||
|
||||
class BpmnFlowchart extends Record
|
||||
{}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Templates\Controllers\CategoryTree;
|
||||
|
||||
class BpmnFlowchartCategory extends CategoryTree
|
||||
{}
|
||||
141
custom/Espo/Modules/Advanced/Controllers/BpmnProcess.php
Normal file
141
custom/Espo/Modules/Advanced/Controllers/BpmnProcess.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Controllers\Record;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess as BpmnProcessEntity;
|
||||
use Espo\Modules\Advanced\Services\BpmnProcess as BpmnProcessService;
|
||||
|
||||
class BpmnProcess extends Record
|
||||
{
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionStop(Request $request): bool
|
||||
{
|
||||
$id = $request->getParsedBody()->id ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$this->acl->checKScope(BpmnProcessEntity::ENTITY_TYPE, Table::ACTION_EDIT)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
/** @var BpmnProcessService $service */
|
||||
$service = $this->getRecordService();
|
||||
|
||||
$service->stopProcess($id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionReactivate(Request $request): bool
|
||||
{
|
||||
$id = $request->getParsedBody()->id ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$this->acl->checKScope(BpmnProcessEntity::ENTITY_TYPE, Table::ACTION_EDIT)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
/** @var BpmnProcessService $service */
|
||||
$service = $this->getRecordService();
|
||||
|
||||
$service->reactivateProcess($id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
*/
|
||||
public function postActionRejectFlowNode(Request $request): bool
|
||||
{
|
||||
$id = $request->getParsedBody()->id ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$this->acl->checKScope(BpmnProcessEntity::ENTITY_TYPE, Table::ACTION_EDIT)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
/** @var BpmnProcessService $service */
|
||||
$service = $this->getRecordService();
|
||||
|
||||
$service->rejectFlowNode($id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionStartFlowFromElement(Request $request): bool
|
||||
{
|
||||
$processId = $request->getParsedBody()->processId ?? null;
|
||||
$elementId = $request->getParsedBody()->elementId ?? null;
|
||||
|
||||
if (!$processId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$elementId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$this->acl->checKScope(BpmnProcessEntity::ENTITY_TYPE, Table::ACTION_EDIT)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
/** @var BpmnProcessService $service */
|
||||
$service = $this->getRecordService();
|
||||
|
||||
$service->startFlowFromElement($processId, $elementId);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
24
custom/Espo/Modules/Advanced/Controllers/BpmnUserTask.php
Normal file
24
custom/Espo/Modules/Advanced/Controllers/BpmnUserTask.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Controllers\Record;
|
||||
|
||||
class BpmnUserTask extends Record
|
||||
{}
|
||||
404
custom/Espo/Modules/Advanced/Controllers/Report.php
Normal file
404
custom/Espo/Modules/Advanced/Controllers/Report.php
Normal file
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Controllers\Record;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Record\SearchParamsFetcher;
|
||||
use Espo\Core\Select\SearchParams;
|
||||
use Espo\Core\Select\Where\Item as WhereItem;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Modules\Advanced\Tools\Report\GridExportService;
|
||||
use Espo\Modules\Advanced\Tools\Report\ListExportService;
|
||||
use Espo\Modules\Advanced\Tools\Report\ListType\ExportParams;
|
||||
use Espo\Modules\Advanced\Tools\Report\ListType\SubReportParams;
|
||||
use Espo\Modules\Advanced\Tools\Report\SendingService;
|
||||
use Espo\Modules\Advanced\Tools\Report\Service;
|
||||
use Espo\Modules\Advanced\Tools\Report\TargetListSyncService;
|
||||
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use JsonException;
|
||||
use stdClass;
|
||||
|
||||
class Report extends Record
|
||||
{
|
||||
/**
|
||||
* List report or grid sub-report.
|
||||
*
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function actionRunList(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getQueryParam('id');
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$searchParams = $this->injectableFactory
|
||||
->create(SearchParamsFetcher::class)
|
||||
->fetch($request);
|
||||
|
||||
$subReportParams = $this->fetchSubReportParamsFromRequest($request);
|
||||
|
||||
// Passing the user is important.
|
||||
$result = $subReportParams ?
|
||||
$this->getReportService()->runSubReportList($id, $searchParams, $subReportParams, $this->user) :
|
||||
$this->getReportService()->runList($id, $searchParams, $this->user);
|
||||
|
||||
return (object) [
|
||||
'list' => $result->getCollection()->getValueMapList(),
|
||||
'total' => $result->getTotal(),
|
||||
'columns' => $result->getColumns(),
|
||||
'columnsData' => $result->getColumnsData(),
|
||||
];
|
||||
}
|
||||
|
||||
private function fetchSubReportParamsFromRequest(Request $request): ?SubReportParams
|
||||
{
|
||||
if (!$request->hasQueryParam('groupValue')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$groupValue = $request->getQueryParam('groupValue');
|
||||
|
||||
if ($groupValue === '') {
|
||||
$groupValue = null;
|
||||
}
|
||||
|
||||
$groupValue2 = null;
|
||||
|
||||
if ($request->hasQueryParam('groupValue2')) {
|
||||
$groupValue2 = $request->getQueryParam('groupValue2');
|
||||
|
||||
if ($groupValue2 === '') {
|
||||
$groupValue2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
return new SubReportParams(
|
||||
(int) ($request->getQueryParam('groupIndex') ?? 0),
|
||||
$groupValue,
|
||||
$request->hasQueryParam('groupValue2'),
|
||||
$groupValue2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid report.
|
||||
*
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function actionRun(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getQueryParam('id');
|
||||
$where = $request->getQueryParams()['where'] ?? null;
|
||||
|
||||
if ($where === '') {
|
||||
$where = null;
|
||||
}
|
||||
|
||||
$whereItem = null;
|
||||
|
||||
if ($where) {
|
||||
$whereItem = WhereItem::fromRawAndGroup(self::normalizeWhere($where));
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
// Passing the user is important.
|
||||
return $this->getReportService()
|
||||
->runGrid($id, $whereItem, $this->user)
|
||||
->toRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionPopulateTargetList(Request $request): bool
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id ?? null;
|
||||
$targetListId = $data->targetListId ?? null;
|
||||
|
||||
if (!$id || !$targetListId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$this->getTargetListSyncService()->populateTargetList($id, $targetListId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionSyncTargetListWithReports(Request $request): bool
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$targetListId = $data->targetListId;
|
||||
|
||||
if (!$targetListId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$this->getTargetListSyncService()->syncTargetListWithReportsById($targetListId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionExportList(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id ?? null;
|
||||
$orderBy = $data->orderBy ?? null;
|
||||
$order = $data->order ?? 'asc';
|
||||
$where = $data->where ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest("No `id`.");
|
||||
}
|
||||
|
||||
$whereItem = $where ?
|
||||
WhereItem::fromRawAndGroup(self::normalizeWhere($where)) :
|
||||
null;
|
||||
|
||||
$order = strtoupper($order);
|
||||
|
||||
if (!in_array($order, [Order::ASC, Order::DESC])) {
|
||||
$order = null;
|
||||
}
|
||||
|
||||
$searchParams = SearchParams::create()
|
||||
->withOrderBy($orderBy)
|
||||
->withOrder($order);
|
||||
|
||||
if ($whereItem) {
|
||||
$searchParams = $searchParams->withWhere($whereItem);
|
||||
}
|
||||
|
||||
$exportParams = new ExportParams(
|
||||
$data->attributeList ?? null,
|
||||
$data->fieldList ?? null,
|
||||
$data->format ?? null,
|
||||
$data->ids ?? null,
|
||||
($data->params ?? null) ? get_object_vars($data->params) : null,
|
||||
);
|
||||
|
||||
$subReportParams = null;
|
||||
|
||||
if (property_exists($data, 'groupValue')) {
|
||||
$groupValue = $data->groupValue;
|
||||
|
||||
if ($groupValue === '') {
|
||||
$groupValue = null;
|
||||
}
|
||||
|
||||
$groupValue2 = $data->groupValue2 ?? null;
|
||||
|
||||
if ($groupValue2 === '') {
|
||||
$groupValue2 = null;
|
||||
}
|
||||
|
||||
$hasGroupValue2 = property_exists($data, 'groupValue2');
|
||||
|
||||
$subReportParams = new SubReportParams(
|
||||
$data->groupIndex ?? 0,
|
||||
$groupValue,
|
||||
$hasGroupValue2,
|
||||
$groupValue2
|
||||
);
|
||||
}
|
||||
|
||||
$attachmentId = $this->getListExportService()->export(
|
||||
$id,
|
||||
$searchParams,
|
||||
$exportParams,
|
||||
$subReportParams,
|
||||
$this->user
|
||||
);
|
||||
|
||||
return (object) ['id' => $attachmentId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function postActionGetEmailAttributes(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id;
|
||||
$where = $data->where ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$whereItem = $where ?
|
||||
WhereItem::fromRawAndGroup(self::normalizeWhere($where)) :
|
||||
null;
|
||||
|
||||
return (object) $this->injectableFactory
|
||||
->create(SendingService::class)
|
||||
->getEmailAttributes($id, $whereItem, $this->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionExportGridXlsx(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id;
|
||||
$where = $data->where ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$whereItem = $where ?
|
||||
WhereItem::fromRawAndGroup(self::normalizeWhere($where)) :
|
||||
null;
|
||||
|
||||
$attachmentId = $this->getGridExportService()->exportXlsx($id, $whereItem, $this->user);
|
||||
|
||||
return (object) ['id' => $attachmentId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
* @throws Error
|
||||
*/
|
||||
public function postActionExportGridCsv(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id;
|
||||
$where = $data->where ?? null;
|
||||
$column = $data->column ?? null;
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$whereItem = $where ?
|
||||
WhereItem::fromRawAndGroup(self::normalizeWhere($where)) :
|
||||
null;
|
||||
|
||||
$attachmentId = $this->getGridExportService()->exportCsv($id, $whereItem, $column, $this->user);
|
||||
|
||||
return (object) ['id' => $attachmentId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionPrintPdf(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id ?? null;
|
||||
$templateId = $data->templateId ?? null;
|
||||
$where = $data->where ?? null;
|
||||
|
||||
$whereItem = $where ?
|
||||
WhereItem::fromRawAndGroup(self::normalizeWhere($where)) :
|
||||
null;
|
||||
|
||||
if (!$id || !$templateId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$attachmentId = $this->getGridExportService()->exportPdf($id, $whereItem, $templateId, $this->user);
|
||||
|
||||
return (object) ['id' => $attachmentId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
*/
|
||||
private static function normalizeWhere(mixed $where): mixed
|
||||
{
|
||||
try {
|
||||
return Json::decode(Json::encode($where), true);
|
||||
} catch (JsonException) {
|
||||
throw new BadRequest("Bad where.");
|
||||
}
|
||||
}
|
||||
|
||||
private function getReportService(): Service
|
||||
{
|
||||
return $this->injectableFactory->create(Service::class);
|
||||
}
|
||||
|
||||
private function getListExportService(): ListExportService
|
||||
{
|
||||
return $this->injectableFactory->create(ListExportService::class);
|
||||
}
|
||||
|
||||
private function getGridExportService(): GridExportService
|
||||
{
|
||||
return $this->injectableFactory->create(GridExportService::class);
|
||||
}
|
||||
|
||||
private function getTargetListSyncService(): TargetListSyncService
|
||||
{
|
||||
return $this->injectableFactory->create(TargetListSyncService::class);
|
||||
}
|
||||
}
|
||||
24
custom/Espo/Modules/Advanced/Controllers/ReportCategory.php
Normal file
24
custom/Espo/Modules/Advanced/Controllers/ReportCategory.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Templates\Controllers\CategoryTree;
|
||||
|
||||
class ReportCategory extends CategoryTree
|
||||
{}
|
||||
42
custom/Espo/Modules/Advanced/Controllers/ReportFilter.php
Normal file
42
custom/Espo/Modules/Advanced/Controllers/ReportFilter.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Controllers\Record;
|
||||
use Espo\Modules\Advanced\Tools\ReportFilter\Service;
|
||||
|
||||
class ReportFilter extends Record
|
||||
{
|
||||
protected function checkAccess(): bool
|
||||
{
|
||||
return $this->user->isAdmin();
|
||||
}
|
||||
|
||||
public function postActionRebuild(): bool
|
||||
{
|
||||
$this->getFilterService()->rebuild();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFilterService(): Service
|
||||
{
|
||||
return $this->injectableFactory->create(Service::class);
|
||||
}
|
||||
}
|
||||
139
custom/Espo/Modules/Advanced/Controllers/ReportPanel.php
Normal file
139
custom/Espo/Modules/Advanced/Controllers/ReportPanel.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Controllers\Record;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Record\SearchParamsFetcher;
|
||||
use Espo\Modules\Advanced\Tools\Report\ListType\SubReportParams;
|
||||
use Espo\Modules\Advanced\Tools\ReportPanel\Service;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class ReportPanel extends Record
|
||||
{
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function postActionRebuild(): bool
|
||||
{
|
||||
$this->getPanelService()->rebuild();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Error
|
||||
* @throws Forbidden
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function getActionRunList(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getQueryParam('id');
|
||||
|
||||
$parentType = $request->getQueryParam('parentType');
|
||||
$parentId = $request->getQueryParam('parentId');
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$searchParams = $this->injectableFactory
|
||||
->create(SearchParamsFetcher::class)
|
||||
->fetch($request);
|
||||
|
||||
$subReportParams = null;
|
||||
|
||||
if ($request->hasQueryParam('groupValue')) {
|
||||
$groupValue = $request->getQueryParam('groupValue');
|
||||
|
||||
if ($groupValue === '') {
|
||||
$groupValue = null;
|
||||
}
|
||||
|
||||
$groupValue2 = null;
|
||||
|
||||
if ($request->hasQueryParam('groupValue2')) {
|
||||
$groupValue2 = $request->getQueryParam('groupValue2');
|
||||
|
||||
if ($groupValue2 === '') {
|
||||
$groupValue2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
$subReportParams = new SubReportParams(
|
||||
(int) ($request->getQueryParam('groupIndex') ?? 0),
|
||||
$groupValue,
|
||||
$request->hasQueryParam('groupValue2'),
|
||||
$groupValue2
|
||||
);
|
||||
}
|
||||
|
||||
$subReportId = $request->getQueryParam('subReportId');
|
||||
|
||||
$result = $subReportParams ?
|
||||
$this->getPanelService()->runSubReportList(
|
||||
$id,
|
||||
$parentType,
|
||||
$parentId,
|
||||
$searchParams,
|
||||
$subReportParams,
|
||||
$subReportId
|
||||
) :
|
||||
$this->getPanelService()->runList($id, $parentType, $parentId, $searchParams);
|
||||
|
||||
return (object) [
|
||||
'list' => $result->getCollection()->getValueMapList(),
|
||||
'total' => $result->getTotal(),
|
||||
'columns' => $result->getColumns(),
|
||||
'columnsData' => $result->getColumnsData(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function getActionRunGrid(Request $request): stdClass
|
||||
{
|
||||
$id = $request->getQueryParam('id');
|
||||
$parentType = $request->getQueryParam('parentType');
|
||||
$parentId = $request->getQueryParam('parentId');
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
return $this->getPanelService()
|
||||
->runGrid($id, $parentType, $parentId)
|
||||
->toRaw();
|
||||
}
|
||||
|
||||
private function getPanelService(): Service
|
||||
{
|
||||
return $this->injectableFactory->create(Service::class);
|
||||
}
|
||||
}
|
||||
29
custom/Espo/Modules/Advanced/Controllers/Workflow.php
Normal file
29
custom/Espo/Modules/Advanced/Controllers/Workflow.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Controllers\Record;
|
||||
|
||||
class Workflow extends Record
|
||||
{
|
||||
protected function checkAccess(): bool
|
||||
{
|
||||
return $this->user->isAdmin();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Templates\Controllers\CategoryTree;
|
||||
|
||||
class WorkflowCategory extends CategoryTree
|
||||
{}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Controllers\Record;
|
||||
|
||||
class WorkflowLogRecord extends Record
|
||||
{
|
||||
public const ENTITY_TYPE = 'WorkflowLogRecord';
|
||||
}
|
||||
68
custom/Espo/Modules/Advanced/Controllers/WorkflowManual.php
Normal file
68
custom/Espo/Modules/Advanced/Controllers/WorkflowManual.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Modules\Advanced\Tools\Workflow\Service;
|
||||
use stdClass;
|
||||
|
||||
class WorkflowManual
|
||||
{
|
||||
private Service $service;
|
||||
|
||||
public function __construct(Service $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws BadRequest
|
||||
* @throws Forbidden
|
||||
* @throws Error
|
||||
* @throws NotFound
|
||||
*/
|
||||
public function postActionRun(Request $request): stdClass
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
$id = $data->id ?? null;
|
||||
$targetId = $data->targetId ?? null;
|
||||
|
||||
if (!$id || !$targetId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$result = $this->service->runManual($id, $targetId);
|
||||
|
||||
$response = (object) [];
|
||||
|
||||
if ($result->alert) {
|
||||
$response->message = $result->alert->message;
|
||||
$response->type = $result->alert->type;
|
||||
$response->autoClose = $result->alert->autoClose;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
43
custom/Espo/Modules/Advanced/Core/App/Job.php
Normal file
43
custom/Espo/Modules/Advanced/Core/App/Job.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\App;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Espo\Core\Job\JobDataLess;
|
||||
use Espo\Core\Job\JobSchedulerFactory;
|
||||
|
||||
class Job implements JobDataLess
|
||||
{
|
||||
public function __construct(
|
||||
private JobSchedulerFactory $jobSchedulerFactory
|
||||
) {}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$runAt = (new DateTimeImmutable())
|
||||
->modify('+ 1 day')
|
||||
->setTime(rand(0, 5), rand(0, 59));
|
||||
|
||||
$this->jobSchedulerFactory
|
||||
->create()
|
||||
->setClassName(JobRunner::class)
|
||||
->setTime($runAt)
|
||||
->schedule();
|
||||
}
|
||||
}
|
||||
165
custom/Espo/Modules/Advanced/Core/App/JobRunner.php
Normal file
165
custom/Espo/Modules/Advanced/Core/App/JobRunner.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\App;
|
||||
|
||||
use Espo\Core\Job\Job;
|
||||
use Espo\Core\Job\Job\Data;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Entities\Extension;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
class JobRunner implements Job
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var string */
|
||||
private $fieldStatus;
|
||||
|
||||
/** @var string */
|
||||
private $fieldMessage;
|
||||
|
||||
public function __construct(
|
||||
private Config $config,
|
||||
private EntityManager $entityManager
|
||||
) {
|
||||
$this->name = base64_decode('QWR2YW5jZWQgUGFjaw==');
|
||||
$this->fieldStatus = base64_decode('bGljZW5zZVN0YXR1cw==');
|
||||
$this->fieldMessage = base64_decode('bGljZW5zZVN0YXR1c01lc3NhZ2U=');
|
||||
}
|
||||
|
||||
public function run(Data $data): void
|
||||
{
|
||||
/** @var ?Extension $current */
|
||||
$current = $this->entityManager
|
||||
->getRDBRepository(Extension::ENTITY_TYPE)
|
||||
->where([
|
||||
'name' => $this->name,
|
||||
])
|
||||
->order('createdAt', true)
|
||||
->findOne();
|
||||
|
||||
$responseData = $this->validate($this->getData($current));
|
||||
|
||||
$status = $responseData['status'] ?? null;
|
||||
$statusMessage = $responseData['statusMessage'] ?? null;
|
||||
|
||||
if (!$status) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$current->has($this->fieldStatus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$current->get($this->fieldStatus) == $status &&
|
||||
$current->get($this->fieldMessage) == $statusMessage
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current->set([
|
||||
$this->fieldStatus => $status,
|
||||
$this->fieldMessage => $statusMessage,
|
||||
]);
|
||||
|
||||
$this->entityManager->saveEntity($current, [
|
||||
'skipAll' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function getData(?Extension $current): array
|
||||
{
|
||||
$query = SelectBuilder::create()
|
||||
->from(Extension::ENTITY_TYPE)
|
||||
->select(['createdAt'])
|
||||
->withDeleted()
|
||||
->build();
|
||||
|
||||
/** @var ?Extension $first */
|
||||
$first = $this->entityManager
|
||||
->getRDBRepository(Extension::ENTITY_TYPE)
|
||||
->clone($query)
|
||||
->where(['name' => $this->name])
|
||||
->order('createdAt')
|
||||
->findOne();
|
||||
|
||||
return [
|
||||
'id' => base64_decode('MTliYzg2YTY4YTdiYjAxZjQ1OGNiMzkxZDQzYTkyMTI='),
|
||||
'name' => $this->name,
|
||||
'version' => $current?->get('version'),
|
||||
'updatedAt' => $current?->get('createdAt'),
|
||||
'installedAt' => $first?->get('createdAt'),
|
||||
'site' => $this->config->get('siteUrl'),
|
||||
'instanceId' => $this->config->get('instanceId'),
|
||||
'espoVersion' => $this->config->get('version'),
|
||||
'applicationName' => $this->config->get('applicationName'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return ?array<string, mixed>
|
||||
*/
|
||||
private function validate(array $data): ?array
|
||||
{
|
||||
if (!function_exists('curl_version')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
/**
|
||||
* @var string $payload
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
$payload = json_encode($data);
|
||||
|
||||
/** @phpstan-ignore-next-line argument.type */
|
||||
curl_setopt($ch, CURLOPT_URL, base64_decode('aHR0cHM6Ly9zLmVzcG9jcm0uY29tL2xpY2Vuc2Uv'));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($payload),
|
||||
]);
|
||||
|
||||
/** @var string $result */
|
||||
$result = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
return json_decode($result, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\AppParams;
|
||||
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Select\SelectBuilderFactory;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowchart;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
class FlowchartEntityTypeList
|
||||
{
|
||||
private Acl $acl;
|
||||
private EntityManager $entityManager;
|
||||
private SelectBuilderFactory $selectBuilderFactory;
|
||||
|
||||
public function __construct(
|
||||
Acl $acl,
|
||||
EntityManager $entityManager,
|
||||
SelectBuilderFactory $selectBuilderFactory
|
||||
) {
|
||||
$this->acl = $acl;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->selectBuilderFactory = $selectBuilderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
if (!$this->acl->checkScope(BpmnProcess::ENTITY_TYPE, Acl\Table::ACTION_CREATE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!$this->acl->checkScope(BpmnFlowchart::ENTITY_TYPE, Acl\Table::ACTION_READ)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
$query = $this->selectBuilderFactory
|
||||
->create()
|
||||
->from(BpmnFlowchart::ENTITY_TYPE)
|
||||
->withAccessControlFilter()
|
||||
->build();
|
||||
|
||||
$collection = $this->entityManager
|
||||
->getRDBRepository(BpmnFlowchart::ENTITY_TYPE)
|
||||
->clone($query)
|
||||
->select(['targetType'])
|
||||
->group('targetType')
|
||||
->where(['isActive' => true])
|
||||
->find();
|
||||
|
||||
foreach ($collection as $item) {
|
||||
$list[] = $item->get('targetType');
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
2393
custom/Espo/Modules/Advanced/Core/Bpmn/BpmnManager.php
Normal file
2393
custom/Espo/Modules/Advanced/Core/Bpmn/BpmnManager.php
Normal file
File diff suppressed because it is too large
Load Diff
237
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Activity.php
Normal file
237
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Activity.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\ORM\Collection;
|
||||
use Throwable;
|
||||
|
||||
abstract class Activity extends Base
|
||||
{
|
||||
/** @var string[] */
|
||||
protected array $pendingBoundaryTypeList = [
|
||||
'eventIntermediateConditionalBoundary',
|
||||
'eventIntermediateTimerBoundary',
|
||||
'eventIntermediateSignalBoundary',
|
||||
'eventIntermediateMessageBoundary',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function beforeProcess(): void
|
||||
{
|
||||
$this->prepareBoundary();
|
||||
$this->refreshFlowNode();
|
||||
$this->refreshTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function prepareBoundary(): void
|
||||
{
|
||||
$boundaryFlowNodeList = [];
|
||||
|
||||
$attachedElementIdList = $this->getProcess()->getAttachedToFlowNodeElementIdList($this->getFlowNode());
|
||||
|
||||
foreach ($attachedElementIdList as $id) {
|
||||
$item = $this->getProcess()->getElementDataById($id);
|
||||
|
||||
if (!in_array($item->type, $this->pendingBoundaryTypeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$boundaryFlowNode = $this->getManager()->prepareFlow(
|
||||
$this->getTarget(),
|
||||
$this->getProcess(),
|
||||
$id,
|
||||
$this->getFlowNode()->get('id'),
|
||||
$this->getFlowNode()->getElementType()
|
||||
);
|
||||
|
||||
if ($boundaryFlowNode) {
|
||||
$boundaryFlowNodeList[] = $boundaryFlowNode;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($boundaryFlowNodeList as $boundaryFlowNode) {
|
||||
$this->getManager()->processPreparedFlowNode($this->getTarget(), $boundaryFlowNode, $this->getProcess());
|
||||
}
|
||||
}
|
||||
|
||||
public function isProcessable(): bool
|
||||
{
|
||||
return $this->getFlowNode()->getStatus() === BpmnFlowNode::STATUS_CREATED;
|
||||
}
|
||||
|
||||
protected function isInNormalFlow(): bool
|
||||
{
|
||||
return !$this->getFlowNode()->getElementDataItemValue('isForCompensation');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function setFailedWithError(?string $errorCode = null, ?string $errorMessage = null): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_FAILED);
|
||||
$flowNode->set([
|
||||
'processedAt' => date(DateTime::SYSTEM_DATE_TIME_FORMAT),
|
||||
]);
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->getManager()->endProcessWithError($this->getProcess(), $errorCode, $errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function setFailed(): void
|
||||
{
|
||||
$this->rejectPendingBoundaryFlowNodes();
|
||||
|
||||
$errorCode = $this->getFlowNode()->getDataItemValue('errorCode');
|
||||
$errorMessage = $this->getFlowNode()->getDataItemValue('errorMessage');
|
||||
|
||||
$boundaryErrorFlowNode = $this->getManager()
|
||||
->prepareBoundaryErrorFlowNode($this->getFlowNode(), $this->getProcess(), $errorCode);
|
||||
|
||||
if (!$boundaryErrorFlowNode) {
|
||||
$this->setFailedWithError($errorCode, $errorMessage);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$boundaryErrorFlowNode->setDataItemValue('code', $errorCode);
|
||||
$boundaryErrorFlowNode->setDataItemValue('message', $errorMessage);
|
||||
|
||||
$this->getEntityManager()->saveEntity($boundaryErrorFlowNode);
|
||||
|
||||
parent::setFailed();
|
||||
|
||||
$this->getManager()->processPreparedFlowNode($this->getTarget(), $boundaryErrorFlowNode, $this->getProcess());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function setFailedWithException(Throwable $e): void
|
||||
{
|
||||
$errorCode = (string) $e->getCode();
|
||||
|
||||
$this->rejectPendingBoundaryFlowNodes();
|
||||
|
||||
$boundaryErrorFlowNode = $this->getManager()
|
||||
->prepareBoundaryErrorFlowNode($this->getFlowNode(), $this->getProcess(), $errorCode);
|
||||
|
||||
if (!$boundaryErrorFlowNode) {
|
||||
$this->setFailedWithError($errorCode, $e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$boundaryErrorFlowNode->setDataItemValue('code', $errorCode);
|
||||
$boundaryErrorFlowNode->setDataItemValue('message', $e->getMessage());
|
||||
|
||||
$this->getEntityManager()->saveEntity($boundaryErrorFlowNode);
|
||||
|
||||
parent::setFailed();
|
||||
|
||||
$this->getManager()->processPreparedFlowNode($this->getTarget(), $boundaryErrorFlowNode, $this->getProcess());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<BpmnFlowNode>
|
||||
*/
|
||||
protected function getPendingBoundaryFlowNodeList(): Collection
|
||||
{
|
||||
return $this->getEntityManager()
|
||||
->getRDBRepositoryByClass(BpmnFlowNode::class)
|
||||
->where([
|
||||
'elementType' => $this->pendingBoundaryTypeList,
|
||||
'processId' => $this->getProcess()->get('id'),
|
||||
'status' => [
|
||||
BpmnFlowNode::STATUS_CREATED,
|
||||
BpmnFlowNode::STATUS_PENDING,
|
||||
],
|
||||
'previousFlowNodeId' => $this->getFlowNode()->get('id'),
|
||||
])
|
||||
->find();
|
||||
}
|
||||
|
||||
protected function rejectPendingBoundaryFlowNodes(): void
|
||||
{
|
||||
$boundaryNodeList = $this->getPendingBoundaryFlowNodeList();
|
||||
|
||||
foreach ($boundaryNodeList as $boundaryNode) {
|
||||
$boundaryNode->set('status', BpmnFlowNode::STATUS_REJECTED);
|
||||
|
||||
$this->getEntityManager()->saveEntity($boundaryNode);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setRejected(): void
|
||||
{
|
||||
$this->rejectPendingBoundaryFlowNodes();
|
||||
|
||||
parent::setRejected();
|
||||
}
|
||||
|
||||
protected function setProcessed(): void
|
||||
{
|
||||
$this->rejectPendingBoundaryFlowNodes();
|
||||
|
||||
parent::setProcessed();
|
||||
}
|
||||
|
||||
protected function setInterrupted(): void
|
||||
{
|
||||
$this->rejectPendingBoundaryFlowNodes();
|
||||
|
||||
parent::setInterrupted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getReturnVariableList(): array
|
||||
{
|
||||
$newVariableList = [];
|
||||
|
||||
$variableList = $this->getAttributeValue('returnVariableList') ?? [];
|
||||
|
||||
foreach ($variableList as $variable) {
|
||||
if (!$variable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($variable[0] === '$') {
|
||||
$variable = substr($variable, 1);
|
||||
}
|
||||
|
||||
$newVariableList[] = $variable;
|
||||
}
|
||||
|
||||
return $newVariableList;
|
||||
}
|
||||
}
|
||||
611
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Base.php
Normal file
611
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Base.php
Normal file
@@ -0,0 +1,611 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\Error as FormulaError;
|
||||
use Espo\Core\Formula\Manager as FormulaManager;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\WebSocket\Submission;
|
||||
use Espo\Modules\Advanced\Core\Bpmn\BpmnManager;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Container;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
/**
|
||||
* // Do not rename the parameters. Mapped by name.
|
||||
*/
|
||||
public function __construct(
|
||||
protected Container $container,
|
||||
protected BpmnManager $manager,
|
||||
protected Entity $target,
|
||||
protected BpmnFlowNode $flowNode,
|
||||
protected BpmnProcess $process,
|
||||
) {}
|
||||
|
||||
protected function getContainer(): Container
|
||||
{
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
protected function getLog(): Log
|
||||
{
|
||||
return $this->container->getByClass(Log::class);
|
||||
}
|
||||
|
||||
protected function getEntityManager(): EntityManager
|
||||
{
|
||||
return $this->container->getByClass(EntityManager::class);
|
||||
}
|
||||
|
||||
protected function getMetadata(): Metadata
|
||||
{
|
||||
return $this->container->getByClass(Metadata::class);
|
||||
}
|
||||
|
||||
protected function getFormulaManager(): FormulaManager
|
||||
{
|
||||
return $this->getContainer()->getByClass(FormulaManager::class);
|
||||
}
|
||||
|
||||
private function getWebSocketSubmission(): Submission
|
||||
{
|
||||
// Important: The service was not bound prior v9.0.0.
|
||||
/** @var Submission */
|
||||
return $this->getContainer()->get('webSocketSubmission');
|
||||
}
|
||||
|
||||
private function getConfig(): Config
|
||||
{
|
||||
return $this->getContainer()->getByClass(Config::class);
|
||||
}
|
||||
|
||||
protected function getProcess(): BpmnProcess
|
||||
{
|
||||
return $this->process;
|
||||
}
|
||||
|
||||
protected function getFlowNode(): BpmnFlowNode
|
||||
{
|
||||
return $this->flowNode;
|
||||
}
|
||||
|
||||
protected function getTarget(): Entity
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
protected function getManager(): BpmnManager
|
||||
{
|
||||
return $this->manager;
|
||||
}
|
||||
|
||||
protected function refresh(): void
|
||||
{
|
||||
$this->refreshFlowNode();
|
||||
$this->refreshProcess();
|
||||
$this->refreshTarget();
|
||||
}
|
||||
|
||||
protected function refreshFlowNode(): void
|
||||
{
|
||||
if (!$this->flowNode->hasId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getEntityManager()->getEntityById(BpmnFlowNode::ENTITY_TYPE, $this->flowNode->getId());
|
||||
|
||||
if ($flowNode) {
|
||||
$this->flowNode->set($flowNode->getValueMap());
|
||||
$this->flowNode->setAsFetched();
|
||||
}
|
||||
}
|
||||
|
||||
protected function refreshProcess(): void
|
||||
{
|
||||
if (!$this->process->hasId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$process = $this->getEntityManager()->getEntityById(BpmnProcess::ENTITY_TYPE, $this->process->getId());
|
||||
|
||||
if ($process) {
|
||||
$this->process->set($process->getValueMap());
|
||||
$this->process->setAsFetched();
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveProcess(): void
|
||||
{
|
||||
$this->getEntityManager()->saveEntity($this->getProcess(), ['silent' => true]);
|
||||
}
|
||||
|
||||
protected function saveFlowNode(): void
|
||||
{
|
||||
$this->getEntityManager()->saveEntity($this->getFlowNode());
|
||||
|
||||
$this->submitWebSocket();
|
||||
}
|
||||
|
||||
private function submitWebSocket(): void
|
||||
{
|
||||
if (!$this->getConfig()->get('useWebSocket')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->getProcess()->hasId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityType = $this->getProcess()->getEntityType();
|
||||
$id = $this->getProcess()->getId();
|
||||
|
||||
$topic = "recordUpdate.$entityType.$id";
|
||||
|
||||
$this->getWebSocketSubmission()->submit($topic);
|
||||
}
|
||||
|
||||
protected function refreshTarget(): void
|
||||
{
|
||||
if (!$this->target->hasId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target = $this->getEntityManager()->getEntityById($this->target->getEntityType(), $this->target->getId());
|
||||
|
||||
if ($target) {
|
||||
$this->target->set($target->getValueMap());
|
||||
$this->target->setAsFetched();
|
||||
}
|
||||
}
|
||||
|
||||
public function isProcessable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function beforeProcess(): void
|
||||
{}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
abstract public function process(): void;
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
throw new Error("BPM Flow: Can't proceed element ". $flowNode->getElementType() . " " .
|
||||
$flowNode->get('elementId') . " in flowchart " . $flowNode->getFlowchartId() . ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getElementId(): string
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
$elementId = $flowNode->getElementId();
|
||||
|
||||
if (!$elementId) {
|
||||
throw new Error("BPM Flow: No id for element " . $flowNode->getElementType() .
|
||||
" in flowchart " . $flowNode->getFlowchartId() . ".");
|
||||
}
|
||||
|
||||
return $elementId;
|
||||
}
|
||||
|
||||
protected function isInNormalFlow(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hasNextElementId(): bool
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$item = $flowNode->getElementData();
|
||||
$nextElementIdList = $item->nextElementIdList;
|
||||
|
||||
if (!count($nextElementIdList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getNextElementId(): ?string
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
if (!$this->hasNextElementId()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = $flowNode->getElementData();
|
||||
$nextElementIdList = $item->nextElementIdList;
|
||||
|
||||
return $nextElementIdList[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttributeValue(string $name)
|
||||
{
|
||||
$item = $this->getFlowNode()->getElementData();
|
||||
|
||||
if (!property_exists($item, $name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $item->$name;
|
||||
}
|
||||
|
||||
protected function getVariables(): stdClass
|
||||
{
|
||||
return $this->getProcess()->getVariables() ?? (object) [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Revise the need.
|
||||
*/
|
||||
protected function getClonedVariables(): stdClass
|
||||
{
|
||||
return clone $this->getVariables();
|
||||
}
|
||||
|
||||
protected function getVariablesForFormula(): stdClass
|
||||
{
|
||||
$variables = $this->getClonedVariables();
|
||||
|
||||
$variables->__createdEntitiesData = $this->getCreatedEntitiesData();
|
||||
$variables->__processEntity = $this->getProcess();
|
||||
$variables->__targetEntity = $this->getTarget();
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
protected function addCreatedEntityDataToVariables(stdClass $variables): void
|
||||
{
|
||||
$variables->__createdEntitiesData = $this->getCreatedEntitiesData();
|
||||
}
|
||||
|
||||
protected function sanitizeVariables(stdClass $variables): void
|
||||
{
|
||||
unset($variables->__createdEntitiesData);
|
||||
unset($variables->__processEntity);
|
||||
unset($variables->__targetEntity);
|
||||
}
|
||||
|
||||
protected function setProcessed(): void
|
||||
{
|
||||
$this->getFlowNode()->set([
|
||||
'status' => BpmnFlowNode::STATUS_PROCESSED,
|
||||
'processedAt' => date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT)
|
||||
]);
|
||||
$this->saveFlowNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function setInterrupted(): void
|
||||
{
|
||||
$this->getFlowNode()->set([
|
||||
'status' => BpmnFlowNode::STATUS_INTERRUPTED,
|
||||
]);
|
||||
$this->saveFlowNode();
|
||||
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function setFailed(): void
|
||||
{
|
||||
$this->getFlowNode()->set([
|
||||
'status' => BpmnFlowNode::STATUS_FAILED,
|
||||
'processedAt' => date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT),
|
||||
]);
|
||||
$this->saveFlowNode();
|
||||
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function setRejected(): void
|
||||
{
|
||||
$this->getFlowNode()->set([
|
||||
'status' => BpmnFlowNode::STATUS_REJECTED,
|
||||
]);
|
||||
$this->saveFlowNode();
|
||||
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function fail(): void
|
||||
{
|
||||
$this->setFailed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function interrupt(): void
|
||||
{
|
||||
$this->setInterrupted();
|
||||
}
|
||||
|
||||
public function cleanupInterrupted(): void
|
||||
{}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
public function complete(): void
|
||||
{
|
||||
throw new Error("Can't complete " . $this->getFlowNode()->getElementType() . ".");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|false|null $divergentFlowNodeId
|
||||
* @throws Error
|
||||
*/
|
||||
protected function prepareNextFlowNode(
|
||||
?string $nextElementId = null,
|
||||
$divergentFlowNodeId = false
|
||||
): ?BpmnFlowNode {
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
if (!$nextElementId) {
|
||||
if (!$this->isInNormalFlow()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->hasNextElementId()) {
|
||||
$this->endProcessFlow();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$nextElementId = $this->getNextElementId();
|
||||
}
|
||||
|
||||
if ($divergentFlowNodeId === false) {
|
||||
$divergentFlowNodeId = $flowNode->getDivergentFlowNodeId();
|
||||
}
|
||||
|
||||
return $this->getManager()->prepareFlow(
|
||||
$this->getTarget(),
|
||||
$this->getProcess(),
|
||||
$nextElementId,
|
||||
$flowNode->get('id'),
|
||||
$flowNode->getElementType(),
|
||||
$divergentFlowNodeId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|false|null $divergentFlowNodeId
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processNextElement(
|
||||
?string $nextElementId = null,
|
||||
$divergentFlowNodeId = false,
|
||||
bool $dontSetProcessed = false
|
||||
): ?BpmnFlowNode {
|
||||
|
||||
$nextFlowNode = $this->prepareNextFlowNode($nextElementId, $divergentFlowNodeId);
|
||||
|
||||
if (!$dontSetProcessed) {
|
||||
$this->setProcessed();
|
||||
}
|
||||
|
||||
if ($nextFlowNode) {
|
||||
$this->getManager()->processPreparedFlowNode(
|
||||
$this->getTarget(),
|
||||
$nextFlowNode,
|
||||
$this->getProcess()
|
||||
);
|
||||
}
|
||||
|
||||
return $nextFlowNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processPreparedNextFlowNode(BpmnFlowNode $flowNode): void
|
||||
{
|
||||
$this->getManager()->processPreparedFlowNode($this->getTarget(), $flowNode, $this->getProcess());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function endProcessFlow(): void
|
||||
{
|
||||
$this->getManager()->endProcessFlow($this->getFlowNode(), $this->getProcess());
|
||||
}
|
||||
|
||||
protected function getCreatedEntitiesData(): stdClass
|
||||
{
|
||||
$createdEntitiesData = $this->getProcess()->get('createdEntitiesData');
|
||||
|
||||
if (!$createdEntitiesData) {
|
||||
$createdEntitiesData = (object) [];
|
||||
}
|
||||
|
||||
return $createdEntitiesData;
|
||||
}
|
||||
|
||||
protected function getCreatedEntity(string $target): ?Entity
|
||||
{
|
||||
$createdEntitiesData = $this->getCreatedEntitiesData();
|
||||
|
||||
if (str_starts_with($target, 'created:')) {
|
||||
$alias = substr($target, 8);
|
||||
} else {
|
||||
$alias = $target;
|
||||
}
|
||||
|
||||
if (!property_exists($createdEntitiesData, $alias)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($createdEntitiesData->$alias->entityId) || empty($createdEntitiesData->$alias->entityType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entityType = $createdEntitiesData->$alias->entityType;
|
||||
$entityId = $createdEntitiesData->$alias->entityId;
|
||||
|
||||
$entity = $this->getEntityManager()->getEntityById($entityType, $entityId);
|
||||
|
||||
if (!$entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entity instanceof Entity) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaError
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getSpecificTarget(?string $target): ?Entity
|
||||
{
|
||||
$entity = $this->getTarget();
|
||||
|
||||
if (!$target || $target == 'targetEntity') {
|
||||
return $entity;
|
||||
}
|
||||
|
||||
if (str_starts_with($target, 'created:')) {
|
||||
return $this->getCreatedEntity($target);
|
||||
}
|
||||
|
||||
if (str_starts_with($target, 'record:')) {
|
||||
$entityType = substr($target, 7);
|
||||
|
||||
$targetIdExpression = $this->getAttributeValue('targetIdExpression');
|
||||
|
||||
if (!$targetIdExpression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_ends_with($targetIdExpression, ';')) {
|
||||
$targetIdExpression = substr($targetIdExpression, 0, -1);
|
||||
}
|
||||
|
||||
$id = $this->getFormulaManager()->run(
|
||||
$targetIdExpression,
|
||||
$this->getTarget(),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if (!$id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($id)) {
|
||||
throw new Error("BPM: Target-ID evaluated not to string.");
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntityById($entityType, $id);
|
||||
|
||||
if (!$entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entity instanceof Entity) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
if (str_starts_with($target, 'link:')) {
|
||||
$link = substr($target, 5);
|
||||
|
||||
$linkList = explode('.', $link);
|
||||
|
||||
$pointerEntity = $entity;
|
||||
|
||||
$notFound = false;
|
||||
|
||||
foreach ($linkList as $link) {
|
||||
$type = $this->getMetadata()
|
||||
->get(['entityDefs', $pointerEntity->getEntityType(), 'links', $link, 'type']);
|
||||
|
||||
if (empty($type)) {
|
||||
$notFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$pointerEntity = $this->getEntityManager()
|
||||
->getRDBRepository($pointerEntity->getEntityType())
|
||||
->getRelation($pointerEntity, $link)
|
||||
->findOne();
|
||||
|
||||
if (!$pointerEntity instanceof Entity) {
|
||||
$notFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$notFound) {
|
||||
if ($pointerEntity && !$pointerEntity instanceof Entity) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return $pointerEntity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
779
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/CallActivity.php
Normal file
779
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/CallActivity.php
Normal file
@@ -0,0 +1,779 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\Error as FormulaException;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\ObjectUtil;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowchart;
|
||||
use Espo\Modules\Advanced\Core\Bpmn\Utils\Helper;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Throwable;
|
||||
use stdClass;
|
||||
use Traversable;
|
||||
|
||||
class CallActivity extends Activity
|
||||
{
|
||||
protected const MAX_INSTANCE_COUNT = 20;
|
||||
protected const CALLABLE_TYPE_PROCESS = 'Process';
|
||||
|
||||
/**
|
||||
* @throws FormulaException
|
||||
* @throws Error
|
||||
*/
|
||||
public function process(): void
|
||||
{
|
||||
if ($this->isMultiInstance()) {
|
||||
$this->processMultiInstance();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$callableType = $this->getAttributeValue('callableType');
|
||||
|
||||
if (!$callableType) {
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($callableType === self::CALLABLE_TYPE_PROCESS) {
|
||||
$this->processProcess();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaException
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processProcess(): void
|
||||
{
|
||||
$target = $this->getNewTargetEntity();
|
||||
|
||||
if (!$target) {
|
||||
$this->getLog()->notice("BPM Call Activity: Could not get target for sub-process.");
|
||||
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowchartId = $this->getAttributeValue('flowchartId');
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$variables = $this->getPrepareVariables();
|
||||
|
||||
/** @var BpmnProcess $subProcess */
|
||||
$subProcess = $this->getEntityManager()->createEntity(BpmnProcess::ENTITY_TYPE, [
|
||||
'status' => BpmnProcess::STATUS_CREATED,
|
||||
'flowchartId' => $flowchartId,
|
||||
'targetId' => $target->getId(),
|
||||
'targetType' => $target->getEntityType(),
|
||||
'parentProcessId' => $this->getProcess()->getId(),
|
||||
'parentProcessFlowNodeId' => $flowNode->getId(),
|
||||
'rootProcessId' => $this->getProcess()->getRootProcessId(),
|
||||
'assignedUserId' => $this->getProcess()->getAssignedUser()?->getId(),
|
||||
'teamsIds' => $this->getProcess()->getTeams()->getIdList(),
|
||||
'variables' => $variables,
|
||||
], [
|
||||
'skipCreatedBy' => true,
|
||||
'skipModifiedBy' => true,
|
||||
'skipStartProcessFlow' => true,
|
||||
]);
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
$flowNode->setDataItemValue('subProcessId', $subProcess->getId());
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
try {
|
||||
$this->getManager()->startCreatedProcess($subProcess);
|
||||
} catch (Throwable $e) {
|
||||
$message = "BPM Call Activity: Starting sub-process failure, {$subProcess->getId()}. {$e->getMessage()}";
|
||||
|
||||
$this->getLog()->error($message, ['exception' => $e]);
|
||||
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function complete(): void
|
||||
{
|
||||
if (!$this->isInNormalFlow()) {
|
||||
$this->setProcessed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$subProcessId = $this->getFlowNode()->getDataItemValue('subProcessId');
|
||||
|
||||
if (!$subProcessId) {
|
||||
$this->processNextElement();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$subProcess = $this->getEntityManager()->getEntityById(BpmnProcess::ENTITY_TYPE, $subProcessId);
|
||||
|
||||
if (!$subProcess) {
|
||||
$this->processNextElement();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$spCreatedEntitiesData = $subProcess->get('createdEntitiesData') ?? (object) [];
|
||||
$createdEntitiesData = $this->getCreatedEntitiesData();
|
||||
$spVariables = $subProcess->get('variables') ?? (object) [];
|
||||
$variables = $this->getVariables();
|
||||
|
||||
$isUpdated = false;
|
||||
|
||||
foreach (get_object_vars($spCreatedEntitiesData) as $key => $value) {
|
||||
if (!isset($createdEntitiesData->$key)) {
|
||||
$createdEntitiesData->$key = $value;
|
||||
|
||||
$isUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
$variableList = $this->getReturnVariableList();
|
||||
|
||||
if ($this->isMultiInstance()) {
|
||||
$variableList = [];
|
||||
|
||||
$returnCollectionVariable = $this->getReturnCollectionVariable();
|
||||
|
||||
if ($returnCollectionVariable !== null) {
|
||||
$variables->$returnCollectionVariable = $spVariables->outputCollection;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($variableList as $variable) {
|
||||
$variables->$variable = $spVariables->$variable ?? null;
|
||||
}
|
||||
|
||||
if (
|
||||
$isUpdated ||
|
||||
count($variableList) ||
|
||||
$this->isMultiInstance()
|
||||
) {
|
||||
$this->refreshProcess();
|
||||
|
||||
$this->getProcess()->set('createdEntitiesData', $createdEntitiesData);
|
||||
$this->getProcess()->set('variables', $variables);
|
||||
|
||||
$this->getEntityManager()->saveEntity($this->getProcess());
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
}
|
||||
|
||||
protected function getReturnCollectionVariable(): ?string
|
||||
{
|
||||
$variable = $this->getAttributeValue('returnCollectionVariable');
|
||||
|
||||
if (!$variable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($variable[0] === '$') {
|
||||
$variable = substr($variable, 1);
|
||||
}
|
||||
|
||||
return $variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaException
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getNewTargetEntity(): ?Entity
|
||||
{
|
||||
$target = $this->getAttributeValue('target');
|
||||
|
||||
return $this->getSpecificTarget($target);
|
||||
}
|
||||
|
||||
protected function isMultiInstance(): bool
|
||||
{
|
||||
return (bool) $this->getAttributeValue('isMultiInstance');
|
||||
}
|
||||
|
||||
protected function isSequential(): bool
|
||||
{
|
||||
return (bool) $this->getAttributeValue('isSequential');
|
||||
}
|
||||
|
||||
protected function getLoopCollectionExpression(): ?string
|
||||
{
|
||||
$expression = $this->getAttributeValue('loopCollectionExpression');
|
||||
|
||||
if (!$expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expression = trim($expression, " \t\n\r");
|
||||
|
||||
if (str_ends_with($expression, ';')) {
|
||||
$expression = substr($expression, 0, -1);
|
||||
}
|
||||
|
||||
return $expression;
|
||||
}
|
||||
|
||||
protected function getConfig(): Config
|
||||
{
|
||||
return $this->getContainer()->getByClass(Config::class);
|
||||
}
|
||||
|
||||
protected function getMaxInstanceCount(): int
|
||||
{
|
||||
return $this->getConfig()->get('bpmnSubProcessInstanceMaxCount', self::MAX_INSTANCE_COUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaException
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processMultiInstance(): void
|
||||
{
|
||||
$loopCollectionExpression = $this->getLoopCollectionExpression();
|
||||
|
||||
if (!$loopCollectionExpression) {
|
||||
throw new Error("BPM Sub-Process: No loop-collection-expression.");
|
||||
}
|
||||
|
||||
$loopCollection = $this->getFormulaManager()->run(
|
||||
$loopCollectionExpression,
|
||||
$this->getTarget(),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if (!is_iterable($loopCollection)) {
|
||||
throw new Error("BPM Sub-Process: Loop-collection-expression evaluated to a non-iterable value.");
|
||||
}
|
||||
|
||||
if ($loopCollection instanceof Traversable) {
|
||||
$loopCollection = iterator_to_array($loopCollection);
|
||||
}
|
||||
|
||||
$maxCount = $this->getMaxInstanceCount();
|
||||
|
||||
$returnVariableList = $this->getReturnVariableList();
|
||||
|
||||
$outputCollection = [];
|
||||
|
||||
for ($i = 0; $i < count($loopCollection); $i++) {
|
||||
$outputItem = (object) [];
|
||||
|
||||
foreach ($returnVariableList as $variable) {
|
||||
$outputItem->$variable = null;
|
||||
}
|
||||
|
||||
$outputCollection[] = $outputItem;
|
||||
}
|
||||
|
||||
if ($maxCount < count($loopCollection)) {
|
||||
$loopCollection = array_slice($loopCollection, 0, $maxCount);
|
||||
}
|
||||
|
||||
$count = count($loopCollection);
|
||||
|
||||
$flowchart = $this->createMultiInstanceFlowchart($count);
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
$variables = $this->getClonedVariables();
|
||||
|
||||
$this->refreshProcess();
|
||||
|
||||
$variables->inputCollection = $loopCollection;
|
||||
$variables->outputCollection = $outputCollection;
|
||||
|
||||
/** @var BpmnProcess $subProcess */
|
||||
$subProcess = $this->getEntityManager()->createEntity(BpmnProcess::ENTITY_TYPE, [
|
||||
'status' => BpmnFlowNode::STATUS_CREATED,
|
||||
'targetId' => $this->getTarget()->getId(),
|
||||
'targetType' => $this->getTarget()->getEntityType(),
|
||||
'parentProcessId' => $this->getProcess()->getId(),
|
||||
'parentProcessFlowNodeId' => $flowNode->getId(),
|
||||
'rootProcessId' => $this->getProcess()->getRootProcessId(),
|
||||
'assignedUserId' => $this->getProcess()->getAssignedUser()?->getId(),
|
||||
'teamsIds' => $this->getProcess()->getTeams()->getIdList(),
|
||||
'variables' => $variables,
|
||||
'createdEntitiesData' => clone $this->getCreatedEntitiesData(),
|
||||
],
|
||||
[
|
||||
'skipCreatedBy' => true,
|
||||
'skipModifiedBy' => true,
|
||||
'skipStartProcessFlow' => true,
|
||||
]);
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
$flowNode->setDataItemValue('subProcessId', $subProcess->getId());
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
try {
|
||||
$this->getManager()->startCreatedProcess($subProcess, $flowchart);
|
||||
} catch (Throwable $e) {
|
||||
$message = "BPM Sub-Process: Starting sub-process failure, {$subProcess->getId()}. {$e->getMessage()}";
|
||||
|
||||
$this->getLog()->error($message, ['exception' => $e]);
|
||||
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function createMultiInstanceFlowchart(int $count): BpmnFlowchart
|
||||
{
|
||||
/** @var BpmnFlowchart $flowchart */
|
||||
$flowchart = $this->getEntityManager()->getNewEntity(BpmnFlowchart::ENTITY_TYPE);
|
||||
|
||||
$dataList = $this->isSequential() ?
|
||||
$this->generateSequentialMultiInstanceDataList($count) :
|
||||
$this->generateParallelMultiInstanceDataList($count);
|
||||
|
||||
$eData = Helper::getElementsDataFromFlowchartData((object) [
|
||||
'list' => $dataList,
|
||||
]);
|
||||
|
||||
$name = $this->isSequential() ?
|
||||
'Sequential Multi-Instance' :
|
||||
'Parallel Multi-Instance';
|
||||
|
||||
$flowchart->set([
|
||||
'targetType' => $this->getTarget()->getEntityType(),
|
||||
'data' => (object) [
|
||||
'createdEntitiesData' => clone $this->getCreatedEntitiesData(),
|
||||
'list' => $dataList,
|
||||
],
|
||||
'elementsDataHash' => $eData['elementsDataHash'],
|
||||
'teamsIds' => $this->getProcess()->getLinkMultipleIdList('teams'),
|
||||
'assignedUserId' => $this->getProcess()->get('assignedUserId'),
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
return $flowchart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
*/
|
||||
protected function generateParallelMultiInstanceDataList(int $count): array
|
||||
{
|
||||
$dataList = [];
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$dataList = array_merge($dataList, $this->generateMultiInstanceIteration($i));
|
||||
}
|
||||
|
||||
return array_merge($dataList, $this->generateMultiInstanceCompensation($count, $dataList));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
*/
|
||||
protected function generateSequentialMultiInstanceDataList(int $count): array
|
||||
{
|
||||
$dataList = [];
|
||||
$groupList = [];
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$groupList[] = $this->generateMultiInstanceIteration($i);
|
||||
}
|
||||
|
||||
foreach ($groupList as $i => $itemList) {
|
||||
$dataList = array_merge($dataList, $itemList);
|
||||
|
||||
if ($i == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$previousItemList = $groupList[$i - 1];
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'flow',
|
||||
'id' => self::generateElementId(),
|
||||
'startId' => $previousItemList[2]->id,
|
||||
'endId' => $itemList[0]->id,
|
||||
'startDirection' => 'r',
|
||||
];
|
||||
}
|
||||
|
||||
return array_merge($dataList, $this->generateMultiInstanceCompensation(0, $dataList));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
*/
|
||||
protected function generateMultiInstanceIteration(int $loopCounter): array
|
||||
{
|
||||
$dataList = [];
|
||||
|
||||
$x = 100;
|
||||
$y = ($loopCounter + 1) * 130;
|
||||
|
||||
if ($this->isSequential()) {
|
||||
$x = $x + ($loopCounter * 400);
|
||||
$y = 50;
|
||||
}
|
||||
|
||||
$initElement = (object) [
|
||||
'type' => 'taskScript',
|
||||
'id' => self::generateElementId(),
|
||||
'formula' =>
|
||||
"\$loopCounter = $loopCounter;\n" .
|
||||
"\$inputItem = array\\at(\$inputCollection, $loopCounter);\n",
|
||||
'center' => (object) [
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
],
|
||||
'text' => $loopCounter . ' init',
|
||||
];
|
||||
|
||||
$subProcessElement = $this->generateSubProcessMultiInstance($loopCounter, $x, $y);
|
||||
|
||||
$endScript = "\$outputItem = array\\at(\$outputCollection, $loopCounter);\n";
|
||||
|
||||
foreach ($this->getReturnVariableList() as $variable) {
|
||||
$endScript .= "object\set(\$outputItem, '$variable', \$$variable);\n";
|
||||
}
|
||||
|
||||
$endElement = (object) [
|
||||
'type' => 'taskScript',
|
||||
'id' => self::generateElementId(),
|
||||
'formula' => $endScript,
|
||||
'center' => (object) [
|
||||
'x' => $x + 250,
|
||||
'y' => $y,
|
||||
],
|
||||
'text' => $loopCounter . ' out',
|
||||
];
|
||||
|
||||
$dataList[] = $initElement;
|
||||
$dataList[] = $subProcessElement;
|
||||
$dataList[] = $endElement;
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'flow',
|
||||
'id' => self::generateElementId(),
|
||||
'startId' => $initElement->id,
|
||||
'endId' => $subProcessElement->id,
|
||||
'startDirection' => 'r',
|
||||
];
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'flow',
|
||||
'id' => self::generateElementId(),
|
||||
'startId' => $subProcessElement->id,
|
||||
'endId' => $endElement->id,
|
||||
'startDirection' => 'r',
|
||||
];
|
||||
|
||||
foreach ($this->generateBoundaryMultiInstance($subProcessElement) as $item) {
|
||||
$dataList[] = $item;
|
||||
}
|
||||
|
||||
return $dataList;
|
||||
}
|
||||
|
||||
protected function generateSubProcessMultiInstance(int $loopCounter, int $x, int $y): stdClass
|
||||
{
|
||||
return (object) [
|
||||
'type' => $this->getAttributeValue('type'),
|
||||
'id' => self::generateElementId(),
|
||||
'center' => (object) [
|
||||
'x' => $x + 125,
|
||||
'y' => $y,
|
||||
],
|
||||
'callableType' => $this->getAttributeValue('callableType'),
|
||||
'flowchartId' => $this->getAttributeValue('flowchartId'),
|
||||
'flowchartName' => $this->getAttributeValue('flowchartName'),
|
||||
'returnVariableList' => $this->getAttributeValue('returnVariableList'),
|
||||
'target' => $this->getAttributeValue('target'),
|
||||
'targetType' => $this->getAttributeValue('targetType'),
|
||||
'targetIdExpression' => $this->getAttributeValue('targetIdExpression'),
|
||||
'isMultiInstance' => false,
|
||||
'isSequential' => false,
|
||||
'loopCollectionExpression' => null,
|
||||
'text' => (string) $loopCounter,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
*/
|
||||
protected function generateBoundaryMultiInstance(stdClass $element): array
|
||||
{
|
||||
$dataList = [];
|
||||
|
||||
$attachedElementIdList = array_filter(
|
||||
$this->getProcess()->getAttachedToFlowNodeElementIdList($this->getFlowNode()),
|
||||
function (string $id): bool {
|
||||
$data = $this->getProcess()->getElementDataById($id);
|
||||
|
||||
return in_array(
|
||||
$data->type,
|
||||
[
|
||||
'eventIntermediateErrorBoundary',
|
||||
'eventIntermediateEscalationBoundary',
|
||||
'eventIntermediateCompensationBoundary',
|
||||
]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$compensationId = array_values(array_filter(
|
||||
$this->getProcess()->getElementIdList(),
|
||||
function (string $id): bool {
|
||||
$data = $this->getProcess()->getElementDataById($id);
|
||||
|
||||
return ($data->isForCompensation ?? null) === true;
|
||||
}
|
||||
))[0] ?? null;
|
||||
|
||||
foreach ($attachedElementIdList as $i => $id) {
|
||||
$boundaryElementId = self::generateElementId();
|
||||
$throwElementId = self::generateElementId();
|
||||
|
||||
$originalData = $this->getProcess()->getElementDataById($id);
|
||||
|
||||
$o1 = (object) [
|
||||
'type' => $originalData->type,
|
||||
'id' => $boundaryElementId,
|
||||
'attachedToId' => $element->id,
|
||||
'cancelActivity' => $originalData->cancelActivity ?? false,
|
||||
'center' => (object) [
|
||||
'x' => $element->center->x - 20 + $i * 25,
|
||||
'y' => $element->center->y - 35,
|
||||
],
|
||||
'attachPosition' => $originalData->attachPosition,
|
||||
];
|
||||
|
||||
if ($originalData->type === 'eventIntermediateCompensationBoundary') {
|
||||
if (!$compensationId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dataList[] = $o1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$o2 = (object) [
|
||||
'type' => 'eventEndError',
|
||||
'id' => $throwElementId,
|
||||
'errorCode' => $originalData->errorCode ?? null,
|
||||
'center' => (object) [
|
||||
'x' => $element->center->x - 20 + $i * 25 + 80,
|
||||
'y' => $element->center->y - 35 - 25,
|
||||
],
|
||||
];
|
||||
|
||||
if ($originalData->type === 'eventIntermediateErrorBoundary') {
|
||||
$o2->type = 'eventEndError';
|
||||
$o1->errorCode = $originalData->errorCode ?? null;
|
||||
$o2->errorCode = $originalData->errorCode ?? null;
|
||||
$o1->cancelActivity = true;
|
||||
}
|
||||
else if ($originalData->type === 'eventIntermediateEscalationBoundary') {
|
||||
$o2->type = 'eventEndEscalation';
|
||||
$o1->escalationCode = $originalData->escalationCode ?? null;
|
||||
$o2->escalationCode = $originalData->escalationCode ?? null;
|
||||
}
|
||||
|
||||
$dataList[] = $o1;
|
||||
$dataList[] = $o2;
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'flow',
|
||||
'id' => self::generateElementId(),
|
||||
'startId' => $boundaryElementId,
|
||||
'endId' => $throwElementId,
|
||||
'startDirection' => 'r',
|
||||
];
|
||||
}
|
||||
|
||||
return $dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param stdClass[] $currentDataList
|
||||
* @return stdClass[]
|
||||
*/
|
||||
private function generateMultiInstanceCompensation(int $loopCounter, array $currentDataList): array
|
||||
{
|
||||
$x = 150;
|
||||
$y = ($loopCounter + 1) * 100 + 100;
|
||||
|
||||
$internalDataList = $this->generateMultiInstanceCompensationSubProcessDataList();
|
||||
|
||||
$dataList = [];
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'eventSubProcess',
|
||||
'id' => self::generateElementId(),
|
||||
'center' => (object) [
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
],
|
||||
'height' => 100,
|
||||
'width' => 205,
|
||||
'target' => null,
|
||||
'isExpanded' => true,
|
||||
'dataList' => $internalDataList,
|
||||
'eventStartData' => clone $internalDataList[0],
|
||||
];
|
||||
|
||||
$activity = $this->generateMultiInstanceBoundaryCompensationActivity();
|
||||
|
||||
if ($activity) {
|
||||
$activity->center = (object) [
|
||||
'x' => 350,
|
||||
'y' => $y,
|
||||
];
|
||||
|
||||
$dataList[] = $activity;
|
||||
|
||||
foreach ($currentDataList as $item) {
|
||||
if ($item->type === 'eventIntermediateCompensationBoundary') {
|
||||
$dataList[] = (object) [
|
||||
'type' => 'flow',
|
||||
'id' => self::generateElementId(),
|
||||
'startId' => $item->id,
|
||||
'endId' => $activity->id,
|
||||
'startDirection' => 'd',
|
||||
'isAssociation' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
*/
|
||||
private function generateMultiInstanceCompensationSubProcessDataList(): array
|
||||
{
|
||||
$x = 50;
|
||||
$y = 35;
|
||||
|
||||
$dataList = [];
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'eventStartCompensation',
|
||||
'id' => self::generateElementId(),
|
||||
'center' => (object) [
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
],
|
||||
];
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'eventEndCompensation',
|
||||
'id' => self::generateElementId(),
|
||||
'center' => (object) [
|
||||
'x' => $x + 100,
|
||||
'y' => $y,
|
||||
],
|
||||
'activityId' => null,
|
||||
];
|
||||
|
||||
$dataList[] = (object) [
|
||||
'type' => 'flow',
|
||||
'id' => self::generateElementId(),
|
||||
'startId' => $dataList[0]->id,
|
||||
'endId' => $dataList[1]->id,
|
||||
'startDirection' => 'r',
|
||||
];
|
||||
|
||||
return $dataList;
|
||||
}
|
||||
|
||||
private function generateMultiInstanceBoundaryCompensationActivity(): ?stdClass
|
||||
{
|
||||
/** @var string[] $attachedElementIdList */
|
||||
$attachedElementIdList = array_values(array_filter(
|
||||
$this->getProcess()->getAttachedToFlowNodeElementIdList($this->getFlowNode()),
|
||||
function (string $id): bool {
|
||||
$data = $this->getProcess()->getElementDataById($id);
|
||||
|
||||
return $data->type === 'eventIntermediateCompensationBoundary';
|
||||
}
|
||||
));
|
||||
|
||||
if ($attachedElementIdList === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$boundaryId = $attachedElementIdList[0];
|
||||
|
||||
$compensationId = $this->getProcess()->getElementNextIdList($boundaryId)[0] ?? null;
|
||||
|
||||
if (!$compensationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = $this->getProcess()->getElementDataById($compensationId);
|
||||
|
||||
if (!$item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = ObjectUtil::clone($item);
|
||||
$item->id = $this->generateElementId();
|
||||
|
||||
if ($item->type === 'subProcess') {
|
||||
$item->isExpanded = false;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected static function generateElementId(): string
|
||||
{
|
||||
return Util::generateId();
|
||||
}
|
||||
|
||||
protected function getPrepareVariables(): stdClass
|
||||
{
|
||||
$variables = $this->getClonedVariables();
|
||||
|
||||
unset($variables->__caughtErrorCode);
|
||||
unset($variables->__caughtErrorMessage);
|
||||
|
||||
return $variables;
|
||||
}
|
||||
}
|
||||
49
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Event.php
Normal file
49
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Event.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
abstract class Event extends Base
|
||||
{
|
||||
protected function rejectConcurrentPendingFlows(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
if ($flowNode->getPreviousFlowNodeElementType() === 'gatewayEventBased') {
|
||||
/** @var iterable<BpmnFlowNode> $concurrentFlowNodeList */
|
||||
$concurrentFlowNodeList = $this->getEntityManager()
|
||||
->getRDBRepository(BpmnFlowNode::ENTITY_TYPE)
|
||||
->where([
|
||||
'previousFlowNodeElementType' => 'gatewayEventBased',
|
||||
'previousFlowNodeId' => $flowNode->getPreviousFlowNodeId(),
|
||||
'processId' => $flowNode->getProcessId(),
|
||||
'id!=' => $flowNode->get('id'),
|
||||
'status' => BpmnFlowNode::STATUS_PENDING,
|
||||
])
|
||||
->find();
|
||||
|
||||
foreach ($concurrentFlowNodeList as $concurrentFlowNode) {
|
||||
$concurrentFlowNode->setStatus(BpmnFlowNode::STATUS_REJECTED);
|
||||
|
||||
$this->getEntityManager()->saveEntity($concurrentFlowNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/EventEnd.php
Normal file
31
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/EventEnd.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventEnd extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
|
||||
class EventEndCompensation extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
/** @var ?string $activityId */
|
||||
$activityId = $this->getAttributeValue('activityId');
|
||||
|
||||
$process = $this->getProcess();
|
||||
|
||||
if (
|
||||
$this->getProcess()->getParentProcessFlowNodeId() &&
|
||||
$this->getProcess()->getParentProcessId()
|
||||
) {
|
||||
/** @var ?BpmnFlowNode $parentNode */
|
||||
$parentNode = $this->getEntityManager()
|
||||
->getEntityById(BpmnFlowNode::ENTITY_TYPE, $this->getProcess()->getParentProcessFlowNodeId());
|
||||
|
||||
if (!$parentNode) {
|
||||
throw new Error("No parent node.");
|
||||
}
|
||||
|
||||
if ($parentNode->getElementType() === 'eventSubProcess') {
|
||||
/** @var ?BpmnProcess $process */
|
||||
$process = $this->getEntityManager()
|
||||
->getEntityById(BpmnProcess::ENTITY_TYPE, $this->getProcess()->getParentProcessId());
|
||||
|
||||
if (!$process) {
|
||||
throw new Error("No parent process.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compensationFlowNodeIdList = $this->getManager()->compensate($process, $activityId);
|
||||
|
||||
$this->getFlowNode()->setDataItemValue('compensationFlowNodeIdList', $compensationFlowNodeIdList);
|
||||
$this->saveFlowNode();
|
||||
|
||||
$this->refreshProcess();
|
||||
$this->refreshTarget();
|
||||
|
||||
if ($compensationFlowNodeIdList === [] || $this->isCompensated()) {
|
||||
$this->setProcessed();
|
||||
$this->processAfterProcessed();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getFlowNode()->set('status', BpmnFlowNode::STATUS_PENDING);
|
||||
$this->saveFlowNode();
|
||||
}
|
||||
|
||||
protected function processAfterProcessed(): void
|
||||
{
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
if (!$this->isCompensated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setProcessed();
|
||||
$this->processAfterProcessed();
|
||||
}
|
||||
|
||||
private function isCompensated(): bool
|
||||
{
|
||||
/** @var string[] $compensationFlowNodeIdList */
|
||||
$compensationFlowNodeIdList = $this->getFlowNode()->getDataItemValue('compensationFlowNodeIdList') ?? [];
|
||||
|
||||
$flowNodes = $this->getEntityManager()
|
||||
->getRDBRepositoryByClass(BpmnFlowNode::class)
|
||||
->where(['id' => $compensationFlowNodeIdList])
|
||||
->find();
|
||||
|
||||
foreach ($flowNodes as $flowNode) {
|
||||
if ($flowNode->getStatus() === BpmnFlowNode::STATUS_PROCESSED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventEndError extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
$this->getManager()->endProcessWithError($this->getProcess(), $this->getAttributeValue('errorCode'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventEndEscalation extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
|
||||
$this->getManager()->escalate($this->getProcess(), $this->getAttributeValue('escalationCode'));
|
||||
|
||||
$this->refreshProcess();
|
||||
$this->refreshTarget();
|
||||
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventEndSignal extends EventSignal
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
|
||||
$signal = $this->getSignal();
|
||||
|
||||
if ($signal) {
|
||||
if (mb_substr($signal, 0, 1) !== '@') {
|
||||
$this->getManager()->broadcastSignal($signal);
|
||||
}
|
||||
} else {
|
||||
$this->getLog()->warning("BPM: eventEndSignal, no signal");
|
||||
}
|
||||
|
||||
$this->refreshProcess();
|
||||
$this->refreshTarget();
|
||||
|
||||
$this->endProcessFlow();
|
||||
|
||||
if ($signal) {
|
||||
if (mb_substr($signal, 0, 1) !== '@') {
|
||||
$this->getSignalManager()->trigger($signal);
|
||||
} else {
|
||||
$this->getSignalManager()->trigger($signal, $this->getTarget());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventEndTerminate extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
$this->getManager()->endProcess($this->getProcess(), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventIntermediateBoundary extends Event
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventIntermediateCompensationThrow extends EventEndCompensation
|
||||
{
|
||||
protected function processAfterProcessed(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventIntermediateConditionalBoundary extends EventIntermediateConditionalCatch
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$result = $this->getConditionManager()->check(
|
||||
$this->getTarget(),
|
||||
$this->getAttributeValue('conditionsAll'),
|
||||
$this->getAttributeValue('conditionsAny'),
|
||||
$this->getAttributeValue('conditionsFormula'),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$cancel = $this->getAttributeValue('cancelActivity');
|
||||
|
||||
if (!$cancel) {
|
||||
$this->createOppositeNode();
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
if ($cancel) {
|
||||
$this->getManager()->cancelActivityByBoundaryEvent($this->getFlowNode());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_PENDING);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$result = $this->getConditionManager()->check(
|
||||
$this->getTarget(),
|
||||
$this->getAttributeValue('conditionsAll'),
|
||||
$this->getAttributeValue('conditionsAny'),
|
||||
$this->getAttributeValue('conditionsFormula'),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($this->getFlowNode()->getDataItemValue('isOpposite')) {
|
||||
if (!$result) {
|
||||
$this->setProcessed();
|
||||
$this->createOppositeNode(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$cancel = $this->getAttributeValue('cancelActivity');
|
||||
|
||||
if (!$cancel) {
|
||||
$this->createOppositeNode();
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
if ($cancel) {
|
||||
$this->getManager()->cancelActivityByBoundaryEvent($this->getFlowNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createOppositeNode(bool $isNegative = false): void
|
||||
{
|
||||
/** @var BpmnFlowNode $flowNode */
|
||||
$flowNode = $this->getEntityManager()->getNewEntity(BpmnFlowNode::ENTITY_TYPE);
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_PENDING);
|
||||
|
||||
$flowNode->set([
|
||||
'elementId' => $this->getFlowNode()->getElementId(),
|
||||
'elementType' => $this->getFlowNode()->getElementType(),
|
||||
'elementData' => $this->getFlowNode()->getElementData(),
|
||||
'data' => [
|
||||
'isOpposite' => !$isNegative,
|
||||
],
|
||||
'flowchartId' => $this->getProcess()->getFlowchartId(),
|
||||
'processId' => $this->getProcess()->getId(),
|
||||
'previousFlowNodeElementType' => $this->getFlowNode()->getPreviousFlowNodeElementType(),
|
||||
'previousFlowNodeId' => $this->getFlowNode()->getPreviousFlowNodeId(),
|
||||
'divergentFlowNodeId' => $this->getFlowNode()->getDivergentFlowNodeId(),
|
||||
'targetType' => $this->getFlowNode()->getTargetType(),
|
||||
'targetId' => $this->getFlowNode()->getTargetId(),
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Modules\Advanced\Core\Bpmn\Utils\ConditionManager;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class EventIntermediateConditionalCatch extends Event
|
||||
{
|
||||
/** @var string */
|
||||
protected $pendingStatus = BpmnFlowNode::STATUS_PENDING;
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Exceptions\Error
|
||||
*/
|
||||
public function process(): void
|
||||
{
|
||||
$target = $this->getConditionsTarget();
|
||||
|
||||
if (!$target) {
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->getConditionManager()->check(
|
||||
$target,
|
||||
$this->getAttributeValue('conditionsAll'),
|
||||
$this->getAttributeValue('conditionsAny'),
|
||||
$this->getAttributeValue('conditionsFormula'),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$this->rejectConcurrentPendingFlows();
|
||||
$this->processNextElement();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->set([
|
||||
'status' => $this->pendingStatus,
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Exceptions\Error
|
||||
*/
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$target = $this->getConditionsTarget();
|
||||
|
||||
if (!$target) {
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->getConditionManager()->check(
|
||||
$target,
|
||||
$this->getAttributeValue('conditionsAll'),
|
||||
$this->getAttributeValue('conditionsAny'),
|
||||
$this->getAttributeValue('conditionsFormula'),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$this->rejectConcurrentPendingFlows();
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getConditionsTarget(): ?Entity
|
||||
{
|
||||
return $this->getTarget();
|
||||
}
|
||||
|
||||
protected function getConditionManager(): ConditionManager
|
||||
{
|
||||
$conditionManager = $this->getContainer()
|
||||
->getByClass(InjectableFactory::class)
|
||||
->create(ConditionManager::class);
|
||||
|
||||
$conditionManager->setCreatedEntitiesData($this->getCreatedEntitiesData());
|
||||
|
||||
return $conditionManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventIntermediateErrorBoundary extends EventIntermediateBoundary
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->storeErrorVariables();
|
||||
$this->processNextElement();
|
||||
}
|
||||
|
||||
private function storeErrorVariables(): void
|
||||
{
|
||||
$variables = $this->getVariables();
|
||||
|
||||
$variables->__caughtErrorCode = $this->getFlowNode()->getDataItemValue('code');
|
||||
$variables->__caughtErrorMessage = $this->getFlowNode()->getDataItemValue('message');
|
||||
|
||||
$this->getProcess()->setVariables($variables);
|
||||
$this->saveProcess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventIntermediateEscalationBoundary extends Event
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
if ($this->getAttributeValue('cancelActivity')) {
|
||||
$this->getManager()->cancelActivityByBoundaryEvent($this->getFlowNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventIntermediateEscalationThrow extends Event
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->getManager()->escalate($this->getProcess(), $this->getAttributeValue('escalationCode'));
|
||||
|
||||
$this->refreshProcess();
|
||||
$this->refreshTarget();
|
||||
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventIntermediateMessageBoundary extends EventIntermediateMessageCatch
|
||||
{
|
||||
protected function proceedPendingFinal(): void
|
||||
{
|
||||
$cancel = $this->getAttributeValue('cancelActivity');
|
||||
|
||||
if (!$cancel) {
|
||||
$this->createCopy();
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
if ($cancel) {
|
||||
$this->getManager()->cancelActivityByBoundaryEvent($this->getFlowNode());
|
||||
}
|
||||
}
|
||||
|
||||
protected function createCopy(): void
|
||||
{
|
||||
/** @var BpmnFlowNode $flowNode */
|
||||
$flowNode = $this->getEntityManager()->getNewEntity(BpmnFlowNode::ENTITY_TYPE);
|
||||
|
||||
$flowNode->set([
|
||||
'status' => BpmnFlowNode::STATUS_PENDING,
|
||||
'elementId' => $this->getFlowNode()->getElementId(),
|
||||
'elementType' => $this->getFlowNode()->getElementType(),
|
||||
'elementData' => $this->getFlowNode()->getElementData(),
|
||||
'data' => (object) [],
|
||||
'flowchartId' => $this->getProcess()->getFlowchartId(),
|
||||
'processId' => $this->getProcess()->get('id'),
|
||||
'previousFlowNodeElementType' => $this->getFlowNode()->getPreviousFlowNodeElementType(),
|
||||
'previousFlowNodeId' => $this->getFlowNode()->getPreviousFlowNodeId(),
|
||||
'divergentFlowNodeId' => $this->getFlowNode()->getDivergentFlowNodeId(),
|
||||
'targetType' => $this->getFlowNode()->getTargetType(),
|
||||
'targetId' => $this->getFlowNode()->getTargetId(),
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\Error as FormulaError;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Entities\Email;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
class EventIntermediateMessageCatch extends Event
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_PENDING);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaError
|
||||
* @throws Error
|
||||
*/
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$repliedToAliasId = $this->getAttributeValue('repliedTo');
|
||||
$messageType = $this->getAttributeValue('messageType') ?? 'Email';
|
||||
$relatedTo = $this->getAttributeValue('relatedTo');
|
||||
|
||||
$conditionsFormula = $this->getAttributeValue('conditionsFormula');
|
||||
$conditionsFormula = trim($conditionsFormula, " \t\n\r");
|
||||
|
||||
if (strlen($conditionsFormula) && str_ends_with($conditionsFormula, ';')) {
|
||||
$conditionsFormula = substr($conditionsFormula, 0, -1);
|
||||
}
|
||||
|
||||
$target = $this->getTarget();
|
||||
|
||||
$createdEntitiesData = $this->getCreatedEntitiesData();
|
||||
|
||||
$repliedToId = null;
|
||||
|
||||
if ($repliedToAliasId) {
|
||||
if (!isset($createdEntitiesData->$repliedToAliasId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repliedToId = $createdEntitiesData->$repliedToAliasId->entityId ?? null;
|
||||
$repliedToType = $createdEntitiesData->$repliedToAliasId->entityType ?? null;
|
||||
|
||||
if (!$repliedToId || $messageType !== $repliedToType) {
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
if ($messageType === 'Email') {
|
||||
$from = $flowNode->getDataItemValue('checkedAt') ?? $flowNode->get('createdAt');
|
||||
|
||||
$whereClause = [
|
||||
'createdAt>=' => $from,
|
||||
'status' => Email::STATUS_ARCHIVED,
|
||||
'dateSent>=' => $flowNode->get('createdAt'),
|
||||
[
|
||||
'OR' => [
|
||||
'sentById' => null,
|
||||
'sentBy.type' => 'portal', // @todo Change to const.
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
if ($repliedToId) {
|
||||
$whereClause['repliedId'] = $repliedToId;
|
||||
|
||||
} else if ($relatedTo) {
|
||||
$relatedTarget = $this->getSpecificTarget($relatedTo);
|
||||
|
||||
if (!$relatedTarget) {
|
||||
$this->updateCheckedAt();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($relatedTarget->getEntityType() === 'Account') {
|
||||
$whereClause['accountId'] = $relatedTarget->getId();
|
||||
} else {
|
||||
$whereClause['parentId'] = $relatedTarget->getId();
|
||||
$whereClause['parentType'] = $relatedTarget->getEntityType();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$repliedToId && !$relatedTo) {
|
||||
if ($target->getEntityType() === 'Contact' && $target->get('accountId')) {
|
||||
$whereClause[] = [
|
||||
'OR' => [
|
||||
[
|
||||
'parentType' => 'Contact',
|
||||
'parentId' => $target->getId(),
|
||||
],
|
||||
[
|
||||
'parentType' => 'Account',
|
||||
'parentId' => $target->get('accountId'),
|
||||
],
|
||||
]
|
||||
];
|
||||
}
|
||||
else if ($target->getEntityType() === 'Account') {
|
||||
$whereClause['accountId'] = $target->getId();
|
||||
}
|
||||
else {
|
||||
$whereClause['parentId'] = $target->getId();
|
||||
$whereClause['parentType'] = $target->getEntityType();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->getContainer()->get('config');
|
||||
|
||||
$limit = $config->get('bpmnMessageCatchLimit', 50);
|
||||
|
||||
$emailList = $this->getEntityManager()
|
||||
->getRDBRepository(Email::ENTITY_TYPE)
|
||||
->leftJoin('sentBy')
|
||||
->where($whereClause)
|
||||
->limit(0, $limit)
|
||||
->find();
|
||||
|
||||
if (!count($emailList)) {
|
||||
$this->updateCheckedAt();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($conditionsFormula) {
|
||||
$isFound = false;
|
||||
|
||||
foreach ($emailList as $email) {
|
||||
$formulaResult = $this->getFormulaManager()
|
||||
->run($conditionsFormula, $email, $this->getVariablesForFormula());
|
||||
|
||||
if ($formulaResult) {
|
||||
$isFound = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isFound) {
|
||||
$this->updateCheckedAt();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->proceedPendingFinal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function proceedPendingFinal(): void
|
||||
{
|
||||
$this->rejectConcurrentPendingFlows();
|
||||
$this->processNextElement();
|
||||
}
|
||||
|
||||
protected function updateCheckedAt(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setDataItemValue('checkedAt', date(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT));
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventIntermediateSignalBoundary extends EventSignal
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$signal = $this->getSignal();
|
||||
|
||||
if (!$signal) {
|
||||
$this->fail();
|
||||
|
||||
$this->getLog()->warning("BPM: No signal for sub-process EventIntermediateSignalBoundary");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_PENDING);
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->getSignalManager()->subscribe($signal, $flowNode->getId());
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$cancel = $this->getAttributeValue('cancelActivity');
|
||||
|
||||
if (!$cancel) {
|
||||
$this->createCopy();
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
if ($cancel) {
|
||||
$this->getManager()->cancelActivityByBoundaryEvent($this->getFlowNode());
|
||||
}
|
||||
}
|
||||
|
||||
protected function createCopy(): void
|
||||
{
|
||||
$data = $this->getFlowNode()->getData();
|
||||
|
||||
$data = clone $data;
|
||||
|
||||
/** @var BpmnFlowNode $flowNode */
|
||||
$flowNode = $this->getEntityManager()->getNewEntity(BpmnFlowNode::ENTITY_TYPE);
|
||||
|
||||
$flowNode->set([
|
||||
'status' => BpmnFlowNode::STATUS_PENDING,
|
||||
'elementId' => $this->getFlowNode()->getElementId(),
|
||||
'elementType' => $this->getFlowNode()->getElementType(),
|
||||
'elementData' => $this->getFlowNode()->getElementData(),
|
||||
'data' => $data,
|
||||
'flowchartId' => $this->getProcess()->getFlowchartId(),
|
||||
'processId' => $this->getProcess()->getId(),
|
||||
'previousFlowNodeElementType' => $this->getFlowNode()->getPreviousFlowNodeElementType(),
|
||||
'previousFlowNodeId' => $this->getFlowNode()->getPreviousFlowNodeId(),
|
||||
'divergentFlowNodeId' => $this->getFlowNode()->getDivergentFlowNodeId(),
|
||||
'targetType' => $this->getFlowNode()->getTargetType(),
|
||||
'targetId' => $this->getFlowNode()->getTargetId(),
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->getSignalManager()->subscribe($this->getSignal(), $flowNode->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
class EventIntermediateSignalCatch extends EventSignal
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$signal = $this->getSignal();
|
||||
|
||||
if (!$signal) {
|
||||
$this->fail();
|
||||
|
||||
$this->getLog()->warning("BPM: No signal for EventIntermediateSignalCatch");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_PENDING);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->getSignalManager()->subscribe($signal, $flowNode->getId());
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->rejectConcurrentPendingFlows();
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventIntermediateSignalThrow extends EventSignal
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$nextFlowNode = $this->prepareNextFlowNode();
|
||||
|
||||
$this->setProcessed();
|
||||
|
||||
$signal = $this->getSignal();
|
||||
|
||||
if ($signal) {
|
||||
if (mb_substr($signal, 0, 1) !== '@') {
|
||||
$this->getManager()->broadcastSignal($signal);
|
||||
}
|
||||
} else {
|
||||
$this->getLog()->warning("BPM: eventIntermediateSignalThrow, no signal");
|
||||
}
|
||||
|
||||
$this->refreshProcess();
|
||||
$this->refreshTarget();
|
||||
|
||||
if ($nextFlowNode) {
|
||||
$this->processPreparedNextFlowNode($nextFlowNode);
|
||||
}
|
||||
|
||||
if ($signal) {
|
||||
if (mb_substr($signal, 0, 1) !== '@') {
|
||||
$this->getSignalManager()->trigger($signal);
|
||||
} else {
|
||||
$this->getSignalManager()->trigger($signal, $this->getTarget());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventIntermediateTimerBoundary extends EventIntermediateTimerCatch
|
||||
{
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
if ($this->getAttributeValue('cancelActivity')) {
|
||||
$this->getManager()->cancelActivityByBoundaryEvent($this->getFlowNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\Utils\DateTime as DateTimeUtil;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Exception;
|
||||
use DateTime;
|
||||
|
||||
class EventIntermediateTimerCatch extends Event
|
||||
{
|
||||
/** @var string */
|
||||
protected $pendingStatus = BpmnFlowNode::STATUS_PENDING;
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$timerBase = $this->getAttributeValue('timerBase');
|
||||
|
||||
if (!$timerBase || $timerBase === 'moment') {
|
||||
$dt = new DateTime();
|
||||
|
||||
$this->shiftDateTime($dt);
|
||||
} else if ($timerBase === 'formula') {
|
||||
$timerFormula = $this->getAttributeValue('timerFormula');
|
||||
|
||||
$formulaManager = $this->getFormulaManager();
|
||||
|
||||
if (!$timerFormula) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error('Bpmn Flow: EventIntermediateTimer error.');
|
||||
}
|
||||
|
||||
$value = $formulaManager->run($timerFormula, $this->getTarget(), $this->getVariablesForFormula());
|
||||
|
||||
if (!$value || !is_string($value)) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value);
|
||||
} catch (Exception) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error('Bpmn Flow: EventIntermediateTimer error.');
|
||||
}
|
||||
}
|
||||
else if (str_starts_with($timerBase, 'field:')) {
|
||||
$field = substr($timerBase, 6);
|
||||
$entity = $this->getTarget();
|
||||
|
||||
if (strpos($field, '.') > 0) {
|
||||
[$link, $field] = explode('.', $field);
|
||||
|
||||
$target = $this->getTarget();
|
||||
|
||||
$entity = $this->getEntityManager()
|
||||
->getRDBRepository($target->getEntityType())
|
||||
->getRelation($target, $link)
|
||||
->findOne();
|
||||
|
||||
if (!$entity) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error("Bpmn Flow: EventIntermediateTimer. Related entity doesn't exist.");
|
||||
}
|
||||
}
|
||||
|
||||
$value = $entity->get($field);
|
||||
|
||||
if (!$value || !is_string($value)) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error('Bpmn Flow: EventIntermediateTimer.');
|
||||
}
|
||||
|
||||
try {
|
||||
$dt = new DateTime($value);
|
||||
}
|
||||
catch (Exception) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error('Bpmn Flow: EventIntermediateTimer error.');
|
||||
}
|
||||
|
||||
$this->shiftDateTime($dt);
|
||||
}
|
||||
else {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error('Bpmn Flow: EventIntermediateTimer error.');
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->set([
|
||||
'status' => $this->pendingStatus,
|
||||
'proceedAt' => $dt->format(DateTimeUtil::SYSTEM_DATE_TIME_FORMAT)
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function shiftDateTime(DateTime $dt): void
|
||||
{
|
||||
$timerShiftOperator = $this->getAttributeValue('timerShiftOperator');
|
||||
$timerShift = $this->getAttributeValue('timerShift');
|
||||
$timerShiftUnits = $this->getAttributeValue('timerShiftUnits');
|
||||
|
||||
if (!in_array($timerShiftUnits, ['minutes', 'hours', 'days', 'months', 'seconds'])) {
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error("Bpmn Flow: Bad shift in ". $flowNode->get('elementType') . " " .
|
||||
$flowNode->get('elementId') . " in flowchart " . $flowNode->get('flowchartId') . ".");
|
||||
}
|
||||
|
||||
if ($timerShift) {
|
||||
$modifyString = $timerShift . ' ' . $timerShiftUnits;
|
||||
|
||||
if ($timerShiftOperator === 'minus') {
|
||||
$modifyString = '-' . $modifyString;
|
||||
}
|
||||
|
||||
try {
|
||||
$dt->modify($modifyString);
|
||||
/** @phpstan-ignore-next-line */
|
||||
} catch (Exception) {
|
||||
$this->setFailed();
|
||||
|
||||
throw new Error('Bpmn Flow: EventIntermediateTimer error.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$this->getFlowNode()->set('status', BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
$this->saveFlowNode();
|
||||
|
||||
$this->rejectConcurrentPendingFlows();
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Core\Bpmn\Utils\Helper;
|
||||
use Espo\Modules\Advanced\Core\SignalManager;
|
||||
|
||||
abstract class EventSignal extends Event
|
||||
{
|
||||
protected function getSignalManager(): SignalManager
|
||||
{
|
||||
return $this->getContainer()->getByClass(SignalManager::class);
|
||||
}
|
||||
|
||||
protected function getSignal(): ?string
|
||||
{
|
||||
$name = $this->getAttributeValue('signal');
|
||||
|
||||
if (!$name || !is_string($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Helper::applyPlaceholders($name, $this->getTarget(), $this->getVariables());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventStart extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventStartCompensation extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventStartConditional extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class EventStartConditionalEventSubProcess extends EventIntermediateConditionalCatch
|
||||
{
|
||||
/** @var string */
|
||||
protected $pendingStatus = BpmnFlowNode::STATUS_STANDBY;
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws \Espo\Core\Exceptions\Error
|
||||
*/
|
||||
protected function getConditionsTarget(): ?Entity
|
||||
{
|
||||
return $this->getSpecificTarget($this->getFlowNode()->getDataItemValue('subProcessTarget'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|bool|null $divergentFlowNodeId
|
||||
*/
|
||||
protected function processNextElement(
|
||||
?string $nextElementId = null,
|
||||
$divergentFlowNodeId = false,
|
||||
bool $dontSetProcessed = false
|
||||
): ?BpmnFlowNode {
|
||||
|
||||
return parent::processNextElement($this->getFlowNode()->getDataItemValue('subProcessElementId'));
|
||||
}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$target = $this->getConditionsTarget();
|
||||
|
||||
if (!$target) {
|
||||
$this->fail();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $this->getConditionManager()->check(
|
||||
$target,
|
||||
$this->getAttributeValue('conditionsAll'),
|
||||
$this->getAttributeValue('conditionsAny'),
|
||||
$this->getAttributeValue('conditionsFormula'),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$subProcessIsInterrupting = $this->getFlowNode()->getDataItemValue('subProcessIsInterrupting');
|
||||
|
||||
if (!$subProcessIsInterrupting) {
|
||||
$this->createOppositeNode();
|
||||
}
|
||||
|
||||
if ($subProcessIsInterrupting) {
|
||||
$this->getManager()->interruptProcessByEventSubProcess($this->getProcess(), $this->getFlowNode());
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getFlowNode()->set(['status' => $this->pendingStatus]);
|
||||
$this->saveFlowNode();
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$result = $this->getConditionManager()->check(
|
||||
$this->getTarget(),
|
||||
$this->getAttributeValue('conditionsAll'),
|
||||
$this->getAttributeValue('conditionsAny'),
|
||||
$this->getAttributeValue('conditionsFormula'),
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($this->getFlowNode()->getDataItemValue('isOpposite')) {
|
||||
if (!$result) {
|
||||
$this->setProcessed();
|
||||
$this->createOppositeNode(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($result) {
|
||||
$subProcessIsInterrupting = $this->getFlowNode()->getDataItemValue('subProcessIsInterrupting');
|
||||
|
||||
if (!$subProcessIsInterrupting) {
|
||||
$this->createOppositeNode();
|
||||
}
|
||||
|
||||
if ($subProcessIsInterrupting) {
|
||||
$this->getManager()->interruptProcessByEventSubProcess($this->getProcess(), $this->getFlowNode());
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
|
||||
protected function createOppositeNode(bool $isNegative = false): void
|
||||
{
|
||||
$data = $this->getFlowNode()->get('data') ?? (object) [];
|
||||
$data = clone $data;
|
||||
$data->isOpposite = !$isNegative;
|
||||
|
||||
$flowNode = $this->getEntityManager()->getEntity(BpmnFlowNode::ENTITY_TYPE);
|
||||
|
||||
$flowNode->set([
|
||||
'status' => BpmnFlowNode::STATUS_STANDBY,
|
||||
'elementType' => $this->getFlowNode()->get('elementType'),
|
||||
'elementData' => $this->getFlowNode()->get('elementData'),
|
||||
'data' => $data,
|
||||
'flowchartId' => $this->getProcess()->get('flowchartId'),
|
||||
'processId' => $this->getProcess()->get('id'),
|
||||
'targetType' => $this->getFlowNode()->get('targetType'),
|
||||
'targetId' => $this->getFlowNode()->get('targetId'),
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
class EventStartError extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->writeErrorData();
|
||||
$this->storeErrorVariables();
|
||||
$this->processNextElement();
|
||||
}
|
||||
|
||||
private function writeErrorData(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$parentFlowNodeId = $this->getProcess()->getParentProcessFlowNodeId();
|
||||
|
||||
if (!$parentFlowNodeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var ?BpmnFlowNode $parentFlowNode */
|
||||
$parentFlowNode = $this->getEntityManager()->getEntityById(BpmnFlowNode::ENTITY_TYPE, $parentFlowNodeId);
|
||||
|
||||
if (!$parentFlowNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
$code = $parentFlowNode->getDataItemValue('caughtErrorCode');
|
||||
$message = $parentFlowNode->getDataItemValue('caughtErrorMessage');
|
||||
|
||||
$flowNode->setDataItemValue('code', $code);
|
||||
$flowNode->setDataItemValue('message', $message);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
}
|
||||
|
||||
private function storeErrorVariables(): void
|
||||
{
|
||||
$variables = $this->getVariables();
|
||||
|
||||
$variables->__caughtErrorCode = $this->getFlowNode()->getDataItemValue('code');
|
||||
$variables->__caughtErrorMessage = $this->getFlowNode()->getDataItemValue('message');
|
||||
|
||||
$this->getProcess()->setVariables($variables);
|
||||
$this->saveProcess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventStartEscalation extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventStartSignal extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Core\Bpmn\Utils\Helper;
|
||||
use Espo\Modules\Advanced\Core\SignalManager;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventStartSignalEventSubProcess extends Event
|
||||
{
|
||||
/**
|
||||
* @param string|bool|null $divergentFlowNodeId
|
||||
*/
|
||||
protected function processNextElement(
|
||||
?string $nextElementId = null,
|
||||
$divergentFlowNodeId = false,
|
||||
bool $dontSetProcessed = false
|
||||
): ?BpmnFlowNode {
|
||||
|
||||
return parent::processNextElement($this->getFlowNode()->getDataItemValue('subProcessElementId'));
|
||||
}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$signal = $this->getSignal();
|
||||
|
||||
if (!$signal) {
|
||||
$this->fail();
|
||||
|
||||
$this->getLog()->warning("BPM: No signal for sub-process start event.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
$flowNode->set([
|
||||
'status' => BpmnFlowNode::STATUS_STANDBY,
|
||||
]);
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->getSignalManager()->subscribe($signal, $flowNode->get('id'));
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$subProcessIsInterrupting = $this->getFlowNode()->getDataItemValue('subProcessIsInterrupting');
|
||||
|
||||
if (!$subProcessIsInterrupting) {
|
||||
$this->createCopy();
|
||||
}
|
||||
|
||||
if ($subProcessIsInterrupting) {
|
||||
$this->getManager()->interruptProcessByEventSubProcess($this->getProcess(), $this->getFlowNode());
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
}
|
||||
|
||||
protected function createCopy(): void
|
||||
{
|
||||
$data = $this->getFlowNode()->get('data') ?? (object) [];
|
||||
$data = clone $data;
|
||||
|
||||
$flowNode = $this->getEntityManager()->getEntity(BpmnFlowNode::ENTITY_TYPE);
|
||||
|
||||
$flowNode->set([
|
||||
'status' => BpmnFlowNode::STATUS_STANDBY,
|
||||
'elementType' => $this->getFlowNode()->getElementType(),
|
||||
'elementData' => $this->getFlowNode()->get('elementData'),
|
||||
'data' => $data,
|
||||
'flowchartId' => $this->getProcess()->getFlowchartId(),
|
||||
'processId' => $this->getProcess()->get('id'),
|
||||
'targetType' => $this->getFlowNode()->getTargetType(),
|
||||
'targetId' => $this->getFlowNode()->getTargetId(),
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$this->getSignalManager()->subscribe($this->getSignal(), $flowNode->get('id'));
|
||||
}
|
||||
|
||||
protected function getSignal(): ?string
|
||||
{
|
||||
$subProcessStartData = $this->getFlowNode()->getDataItemValue('subProcessStartData') ?? (object) [];
|
||||
|
||||
$name = $subProcessStartData->signal ?? null;
|
||||
|
||||
if (!$name || !is_string($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Helper::applyPlaceholders($name, $this->getTarget(), $this->getVariables());
|
||||
}
|
||||
|
||||
protected function getSignalManager(): SignalManager
|
||||
{
|
||||
return $this->getContainer()->getByClass(SignalManager::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
class EventStartTimer extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Formula\Exceptions\Error as FormulaError;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class EventStartTimerEventSubProcess extends EventIntermediateTimerCatch
|
||||
{
|
||||
/** @var string */
|
||||
protected $pendingStatus = BpmnFlowNode::STATUS_STANDBY;
|
||||
|
||||
/**
|
||||
* @throws FormulaError
|
||||
* @throws Error
|
||||
*/
|
||||
protected function getConditionsTarget(): ?Entity
|
||||
{
|
||||
return $this->getSpecificTarget($this->getFlowNode()->getDataItemValue('subProcessTarget'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|bool|null $divergentFlowNodeId
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processNextElement(
|
||||
?string $nextElementId = null,
|
||||
$divergentFlowNodeId = false,
|
||||
bool $dontSetProcessed = false
|
||||
): ?BpmnFlowNode {
|
||||
|
||||
return parent::processNextElement($this->getFlowNode()->getDataItemValue('subProcessElementId'));
|
||||
}
|
||||
|
||||
public function proceedPending(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$subProcessIsInterrupting = $this->getFlowNode()->getDataItemValue('subProcessIsInterrupting');
|
||||
|
||||
if (!$subProcessIsInterrupting) {
|
||||
$standbyFlowNode = $this->getManager()->prepareStandbyFlow(
|
||||
$this->getTarget(),
|
||||
$this->getProcess(),
|
||||
$this->getFlowNode()->getDataItemValue('subProcessElementId')
|
||||
);
|
||||
|
||||
if ($standbyFlowNode) {
|
||||
$this->getManager()->processPreparedFlowNode(
|
||||
$this->getTarget(),
|
||||
$standbyFlowNode,
|
||||
$this->getProcess()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($subProcessIsInterrupting) {
|
||||
$this->getManager()->interruptProcessByEventSubProcess($this->getProcess(), $this->getFlowNode());
|
||||
}
|
||||
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
|
||||
class EventSubProcess extends SubProcess
|
||||
{
|
||||
protected function getSubProcessStartElementId(): ?string
|
||||
{
|
||||
$eventStartData = $this->getAttributeValue('eventStartData') ?? (object) [];
|
||||
|
||||
return $eventStartData->id ?? null;
|
||||
}
|
||||
|
||||
public function complete(): void
|
||||
{
|
||||
$this->setProcessed();
|
||||
|
||||
if ($this->getProcess()->getStatus() === BpmnProcess::STATUS_STARTED) {
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
}
|
||||
|
||||
protected function isMultiInstance(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
126
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Gateway.php
Normal file
126
custom/Espo/Modules/Advanced/Core/Bpmn/Elements/Gateway.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
abstract class Gateway extends Base
|
||||
{
|
||||
public function process(): void
|
||||
{
|
||||
if ($this->isDivergent()) {
|
||||
$this->processDivergent();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isConvergent()) {
|
||||
$this->processConvergent();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setFailed();
|
||||
}
|
||||
|
||||
abstract protected function processDivergent(): void;
|
||||
|
||||
abstract protected function processConvergent(): void;
|
||||
|
||||
protected function isDivergent(): bool
|
||||
{
|
||||
$nextElementIdList = $this->getAttributeValue('nextElementIdList') ?? [];
|
||||
|
||||
return !$this->isConvergent() && count($nextElementIdList);
|
||||
}
|
||||
|
||||
protected function isConvergent(): bool
|
||||
{
|
||||
$previousElementIdList = $this->getAttributeValue('previousElementIdList') ?? [];
|
||||
|
||||
return is_array($previousElementIdList) && count($previousElementIdList) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $divergentElementId
|
||||
* @param string $forkElementId
|
||||
* @param string $currentElementId
|
||||
* @param string[] $metElementIdList
|
||||
*/
|
||||
private function checkElementsBelongSingleFlowRecursive(
|
||||
$divergentElementId,
|
||||
$forkElementId,
|
||||
$currentElementId,
|
||||
bool &$result,
|
||||
&$metElementIdList = null
|
||||
): void {
|
||||
|
||||
if ($divergentElementId === $currentElementId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($forkElementId === $currentElementId) {
|
||||
$result = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$metElementIdList) {
|
||||
$metElementIdList = [];
|
||||
}
|
||||
|
||||
$flowchartElementsDataHash = $this->getProcess()->get('flowchartElementsDataHash');
|
||||
|
||||
$elementData = $flowchartElementsDataHash->$currentElementId;
|
||||
|
||||
if (!isset($elementData->previousElementIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($elementData->previousElementIdList as $elementId) {
|
||||
if (in_array($elementId, $metElementIdList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->checkElementsBelongSingleFlowRecursive(
|
||||
$divergentElementId,
|
||||
$forkElementId,
|
||||
$elementId,
|
||||
$result,
|
||||
$metElementIdList
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $divergentElementId
|
||||
* @param string $forkElementId
|
||||
* @param string $elementId
|
||||
*/
|
||||
protected function checkElementsBelongSingleFlow(
|
||||
$divergentElementId,
|
||||
$forkElementId,
|
||||
$elementId
|
||||
): bool {
|
||||
|
||||
$result = false;
|
||||
|
||||
$this->checkElementsBelongSingleFlowRecursive($divergentElementId, $forkElementId, $elementId, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class GatewayEventBased extends Gateway
|
||||
{
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processDivergent(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$item = $flowNode->getElementData();
|
||||
$nextElementIdList = $item->nextElementIdList ?? [];
|
||||
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
foreach ($nextElementIdList as $nextElementId) {
|
||||
$nextFlowNode = $this->processNextElement($nextElementId, false, true);
|
||||
|
||||
if ($nextFlowNode->getStatus() === BpmnFlowNode::STATUS_PROCESSED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setProcessed();
|
||||
|
||||
$this->getManager()->tryToEndProcess($this->getProcess());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Error
|
||||
*/
|
||||
protected function processConvergent(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error as FormulaError;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Modules\Advanced\Core\Bpmn\Utils\ConditionManager;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class GatewayExclusive extends Gateway
|
||||
{
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws FormulaError
|
||||
*/
|
||||
protected function processDivergent(): void
|
||||
{
|
||||
$conditionManager = $this->getConditionManager();
|
||||
|
||||
$flowList = $this->getAttributeValue('flowList');
|
||||
|
||||
if (!is_array($flowList)) {
|
||||
$flowList = [];
|
||||
}
|
||||
|
||||
$defaultNextElementId = $this->getAttributeValue('defaultNextElementId');
|
||||
$nextElementId = null;
|
||||
|
||||
foreach ($flowList as $flowData) {
|
||||
$conditionsAll = $flowData->conditionsAll ?? null;
|
||||
$conditionsAny = $flowData->conditionsAny ?? null;
|
||||
$conditionsFormula = $flowData->conditionsFormula ?? null;
|
||||
|
||||
$result = $conditionManager->check(
|
||||
$this->getTarget(),
|
||||
$conditionsAll,
|
||||
$conditionsAny,
|
||||
$conditionsFormula,
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$nextElementId = $flowData->elementId;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nextElementId && $defaultNextElementId) {
|
||||
$nextElementId = $defaultNextElementId;
|
||||
}
|
||||
|
||||
if ($nextElementId) {
|
||||
$this->processNextElement($nextElementId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaError
|
||||
*/
|
||||
protected function processConvergent(): void
|
||||
{
|
||||
$this->processNextElement();
|
||||
}
|
||||
|
||||
protected function getConditionManager(): ConditionManager
|
||||
{
|
||||
$conditionManager = $this->getContainer()
|
||||
->getByClass(InjectableFactory::class)
|
||||
->create(ConditionManager::class);
|
||||
|
||||
$conditionManager->setCreatedEntitiesData($this->getCreatedEntitiesData());
|
||||
|
||||
return $conditionManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
/***********************************************************************************
|
||||
* The contents of this file are subject to the Extension License Agreement
|
||||
* ("Agreement") which can be viewed at
|
||||
* https://www.espocrm.com/extension-license-agreement/.
|
||||
* By copying, installing downloading, or using this file, You have unconditionally
|
||||
* agreed to the terms and conditions of the Agreement, and You may not use this
|
||||
* file except in compliance with the Agreement. Under the terms of the Agreement,
|
||||
* You shall not license, sublicense, sell, resell, rent, lease, lend, distribute,
|
||||
* redistribute, market, publish, commercialize, or otherwise transfer rights or
|
||||
* usage to the software or any modified version or derivative work of the software
|
||||
* created by or for you.
|
||||
*
|
||||
* Copyright (C) 2015-2025 EspoCRM, Inc.
|
||||
*
|
||||
* License ID: 19bc86a68a7bb01f458cb391d43a9212
|
||||
************************************************************************************/
|
||||
|
||||
namespace Espo\Modules\Advanced\Core\Bpmn\Elements;
|
||||
|
||||
use Espo\Core\Exceptions\Error as FormulaError;
|
||||
use Espo\Core\Formula\Exceptions\Error;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Modules\Advanced\Core\Bpmn\Utils\ConditionManager;
|
||||
use Espo\Modules\Advanced\Entities\BpmnFlowNode;
|
||||
use Espo\Modules\Advanced\Entities\BpmnProcess;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class GatewayInclusive extends Gateway
|
||||
{
|
||||
/**
|
||||
* @throws Error
|
||||
* @throws FormulaError
|
||||
*/
|
||||
protected function processDivergent(): void
|
||||
{
|
||||
$conditionManager = $this->getConditionManager();
|
||||
|
||||
$flowList = $this->getAttributeValue('flowList');
|
||||
|
||||
if (!is_array($flowList)) {
|
||||
$flowList = [];
|
||||
}
|
||||
|
||||
$defaultNextElementId = $this->getAttributeValue('defaultNextElementId');
|
||||
|
||||
$nextElementIdList = [];
|
||||
|
||||
foreach ($flowList as $flowData) {
|
||||
$conditionsAll = $flowData->conditionsAll ?? null;
|
||||
$conditionsAny = $flowData->conditionsAny ?? null;
|
||||
$conditionsFormula = $flowData->conditionsFormula ?? null;
|
||||
|
||||
$result = $conditionManager->check(
|
||||
$this->getTarget(),
|
||||
$conditionsAll,
|
||||
$conditionsAny,
|
||||
$conditionsFormula,
|
||||
$this->getVariablesForFormula()
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$nextElementIdList[] = $flowData->elementId;
|
||||
}
|
||||
}
|
||||
|
||||
//$isDefaultFlow = false;
|
||||
|
||||
if (!count($nextElementIdList) && $defaultNextElementId) {
|
||||
//$isDefaultFlow = true;
|
||||
|
||||
$nextElementIdList[] = $defaultNextElementId;
|
||||
}
|
||||
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$nextDivergentFlowNodeId = $flowNode->getId();
|
||||
|
||||
if (count($nextElementIdList)) {
|
||||
$flowNode->setStatus(BpmnFlowNode::STATUS_IN_PROCESS);
|
||||
$this->getEntityManager()->saveEntity($flowNode);
|
||||
|
||||
$nextFlowNodeList = [];
|
||||
|
||||
foreach ($nextElementIdList as $nextElementId) {
|
||||
$nextFlowNode = $this->prepareNextFlowNode($nextElementId, $nextDivergentFlowNodeId);
|
||||
|
||||
if ($nextFlowNode) {
|
||||
$nextFlowNodeList[] = $nextFlowNode;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setProcessed();
|
||||
|
||||
foreach ($nextFlowNodeList as $nextFlowNode) {
|
||||
if ($this->getProcess()->getStatus() !== BpmnProcess::STATUS_STARTED) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->getManager()->processPreparedFlowNode($this->getTarget(), $nextFlowNode, $this->getProcess());
|
||||
}
|
||||
|
||||
$this->getManager()->tryToEndProcess($this->getProcess());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->endProcessFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FormulaError
|
||||
*/
|
||||
protected function processConvergent(): void
|
||||
{
|
||||
$flowNode = $this->getFlowNode();
|
||||
|
||||
$item = $flowNode->getElementData();
|
||||
$previousElementIdList = $item->previousElementIdList;
|
||||
|
||||
$nextDivergentFlowNodeId = null;
|
||||
$divergentFlowNode = null;
|
||||
|
||||
$convergingFlowCount = 1;
|
||||
|
||||
if ($flowNode->getDivergentFlowNodeId()) {
|
||||
/** @var ?BpmnFlowNode $divergentFlowNode */
|
||||
$divergentFlowNode = $this->getEntityManager()
|
||||
->getEntityById(BpmnFlowNode::ENTITY_TYPE, $flowNode->getDivergentFlowNodeId());
|
||||
|
||||
if ($divergentFlowNode) {
|
||||
$nextDivergentFlowNodeId = $divergentFlowNode->getDivergentFlowNodeId();
|
||||
|
||||
/** @var iterable<BpmnFlowNode> $forkFlowNodeList */
|
||||
$forkFlowNodeList = $this->getEntityManager()
|
||||
->getRDBRepository(BpmnFlowNode::ENTITY_TYPE)
|
||||
->where([
|
||||
'processId' => $flowNode->getProcessId(),
|
||||
'previousFlowNodeId' => $divergentFlowNode->getId(),
|
||||
])
|
||||
->find();
|
||||
|
||||
$convergingFlowCount = 0;
|
||||
|
||||
foreach ($previousElementIdList as $previousElementId) {
|
||||
$isActual = false;
|
||||
|
||||
foreach ($forkFlowNodeList as $forkFlowNode) {
|
||||
if (
|
||||
$this->checkElementsBelongSingleFlow(
|
||||
$divergentFlowNode->getElementId(),
|
||||
$forkFlowNode->getElementId(),
|
||||
$previousElementId
|
||||
)
|
||||
) {
|
||||
$isActual = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isActual) {
|
||||
$convergingFlowCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$concurrentFlowNodeList = $this->getEntityManager()
|
||||
->getRDBRepository(BpmnFlowNode::ENTITY_TYPE)
|
||||
->where([
|
||||
'elementId' => $flowNode->getElementId(),
|
||||
'processId' => $flowNode->getProcessId(),
|
||||
'divergentFlowNodeId' => $flowNode->getDivergentFlowNodeId(),
|
||||
])
|
||||
->find();
|
||||
|
||||
$concurrentCount = count(iterator_to_array($concurrentFlowNodeList));
|
||||
|
||||
if ($concurrentCount < $convergingFlowCount) {
|
||||
$this->setRejected();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isBalancingDivergent = true;
|
||||
|
||||
if ($divergentFlowNode) {
|
||||
$divergentElementData = $divergentFlowNode->getElementData();
|
||||
|
||||
if (isset($divergentElementData->nextElementIdList)) {
|
||||
foreach ($divergentElementData->nextElementIdList as $forkId) {
|
||||
if (
|
||||
!$this->checkElementsBelongSingleFlow(
|
||||
$divergentFlowNode->getElementId(),
|
||||
$forkId,
|
||||
$flowNode->getElementId()
|
||||
)
|
||||
) {
|
||||
$isBalancingDivergent = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isBalancingDivergent) {
|
||||
if ($divergentFlowNode) {
|
||||
$nextDivergentFlowNodeId = $divergentFlowNode->getDivergentFlowNodeId();
|
||||
}
|
||||
|
||||
$this->processNextElement(null, $nextDivergentFlowNodeId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$this->processNextElement(null, false);
|
||||
}
|
||||
|
||||
protected function getConditionManager(): ConditionManager
|
||||
{
|
||||
$conditionManager = $this->getContainer()
|
||||
->getByClass(InjectableFactory::class)
|
||||
->create(ConditionManager::class);
|
||||
|
||||
$conditionManager->setCreatedEntitiesData($this->getCreatedEntitiesData());
|
||||
|
||||
return $conditionManager;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user