diff --git a/application/Espo/Classes/Cleanup/Audit.php b/application/Espo/Classes/Cleanup/Audit.php index e97a1db0ab..fa547f992a 100644 --- a/application/Espo/Classes/Cleanup/Audit.php +++ b/application/Espo/Classes/Cleanup/Audit.php @@ -71,7 +71,7 @@ class Audit implements Cleanup ->where([ 'parentType' => $entityType, 'createdAt<' => $this->getBefore()->toString(), - 'type' => [Note::TYPE_UPDATE, Note::TYPE_STATUS], + 'type' => [Note::TYPE_UPDATE], ]) ->build(); diff --git a/application/Espo/Classes/Select/Note/PrimaryFilters/Updates.php b/application/Espo/Classes/Select/Note/PrimaryFilters/Updates.php index 107c68f105..262afedb37 100644 --- a/application/Espo/Classes/Select/Note/PrimaryFilters/Updates.php +++ b/application/Espo/Classes/Select/Note/PrimaryFilters/Updates.php @@ -38,10 +38,7 @@ class Updates implements Filter public function apply(QueryBuilder $queryBuilder): void { $queryBuilder->where([ - 'type' => [ - Note::TYPE_UPDATE, - Note::TYPE_STATUS, - ], + 'type' => Note::TYPE_UPDATE, ]); } } diff --git a/application/Espo/Core/Upgrades/Migrations/V9_2/AfterUpgrade.php b/application/Espo/Core/Upgrades/Migrations/V9_2/AfterUpgrade.php new file mode 100644 index 0000000000..7a3b94c690 --- /dev/null +++ b/application/Espo/Core/Upgrades/Migrations/V9_2/AfterUpgrade.php @@ -0,0 +1,60 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Upgrades\Migrations\V9_2; + +use Espo\Core\Upgrades\Migration\Script; +use Espo\Entities\Note; +use Espo\ORM\EntityManager; +use Espo\ORM\Query\UpdateBuilder; + +class AfterUpgrade implements Script +{ + public function __construct( + private EntityManager $entityManager, + ) {} + + public function run(): void + { + $this->updateNotes(); + } + + private function updateNotes(): void + { + $update = UpdateBuilder::create() + ->in(Note::ENTITY_TYPE) + ->set([ + 'type' => Note::TYPE_UPDATE, + ]) + ->where(['type' => 'Status']) + ->build(); + + $this->entityManager->getQueryExecutor()->execute($update); + } +} diff --git a/application/Espo/Entities/Note.php b/application/Espo/Entities/Note.php index ecfd895c1c..72e6a6fdc9 100644 --- a/application/Espo/Entities/Note.php +++ b/application/Espo/Entities/Note.php @@ -52,6 +52,9 @@ class Note extends Entity public const TYPE_POST = 'Post'; public const TYPE_UPDATE = 'Update'; + /** + * @deprecated As of v9.2.0 + */ public const TYPE_STATUS = 'Status'; public const TYPE_CREATE = 'Create'; public const TYPE_CREATE_RELATED = 'CreateRelated'; diff --git a/application/Espo/Resources/metadata/entityDefs/Note.json b/application/Espo/Resources/metadata/entityDefs/Note.json index f71ad88210..50e9b890be 100644 --- a/application/Espo/Resources/metadata/entityDefs/Note.json +++ b/application/Espo/Resources/metadata/entityDefs/Note.json @@ -30,7 +30,6 @@ "Create", "CreateRelated", "Update", - "Status", "Assign", "Relate", "Unrelate", diff --git a/application/Espo/Tools/EmailNotification/Processor.php b/application/Espo/Tools/EmailNotification/Processor.php index bee79096a1..e3cc1da7ef 100644 --- a/application/Espo/Tools/EmailNotification/Processor.php +++ b/application/Espo/Tools/EmailNotification/Processor.php @@ -656,8 +656,8 @@ class Processor $noteData = $note->getData(); - $value = $noteData->statusValue ?? null; - $field = $noteData->statusField ?? null; + $value = $noteData->value ?? null; + $field = $this->metadata->get("scopes.$parentType.statusField"); if ($value === null || !$field || !is_string($field)) { return; diff --git a/application/Espo/Tools/Stream/NoteAccessControl.php b/application/Espo/Tools/Stream/NoteAccessControl.php index 52529a1c3f..593dcc3f5d 100644 --- a/application/Espo/Tools/Stream/NoteAccessControl.php +++ b/application/Espo/Tools/Stream/NoteAccessControl.php @@ -29,6 +29,7 @@ namespace Espo\Tools\Stream; +use Espo\Core\Utils\Metadata; use Espo\Entities\Note; use Espo\Entities\User; use Espo\Core\Utils\Acl\UserAclManagerProvider; @@ -37,6 +38,7 @@ class NoteAccessControl { public function __construct( private UserAclManagerProvider $userAclManagerProvider, + private Metadata $metadata, ) {} public function apply(Note $note, User $user): void @@ -54,9 +56,9 @@ class NoteAccessControl ->get($user) ->getScopeForbiddenFieldList($user, $note->getParentType()); - $forbiddenAttributeList = $this->userAclManagerProvider - ->get($user) - ->getScopeForbiddenAttributeList($user, $note->getParentType()); + $aclManager = $this->userAclManagerProvider->get($user); + + $forbiddenAttributeList = $aclManager->getScopeForbiddenAttributeList($user, $note->getParentType()); $data->fields = array_values(array_diff($fields, $forbiddenFieldList)); @@ -65,34 +67,13 @@ class NoteAccessControl unset($data->attributes->became->$attribute); } - $statusField = $data->statusField ?? null; + $statusField = $this->metadata->get("scopes.{$note->getParentType()}.statusField"); if ( $statusField && - !$this->userAclManagerProvider->get($user) - ->checkField($user, $note->getParentType(), $statusField) + !$aclManager->checkField($user, $note->getParentType(), $statusField) ) { - unset($data->statusField); - unset($data->statusValue); - unset($data->statusStyle); - } - - $note->setData($data); - } - - // Legacy as of v9.2.0. - if ($note->getType() === Note::TYPE_STATUS && $note->getParentType()) { - $forbiddenFieldList = $this->userAclManagerProvider - ->get($user) - ->getScopeForbiddenFieldList($user, $note->getParentType()); - - $data = $note->getData(); - - $field = $data->field ?? null; - - if (in_array($field, $forbiddenFieldList)) { - $data->value = null; - $data->style = null; + unset($data->value); } $note->setData($data); @@ -109,7 +90,7 @@ class NoteAccessControl if (in_array($field, $forbiddenFieldList)) { $data->statusValue = null; - $data->statusStyle = null; + $data->statusStyle = null; // Legacy. } $note->setData($data); diff --git a/application/Espo/Tools/Stream/RecordService.php b/application/Espo/Tools/Stream/RecordService.php index 9f9ada553e..e68679aa07 100644 --- a/application/Espo/Tools/Stream/RecordService.php +++ b/application/Espo/Tools/Stream/RecordService.php @@ -35,7 +35,6 @@ use Espo\Core\Exceptions\NotFound; use Espo\Core\Name\Field; use Espo\Core\Select\SearchParams; use Espo\Core\Select\SelectBuilderFactory; -use Espo\Core\Utils\Metadata; use Espo\Entities\Attachment; use Espo\ORM\Collection; use Espo\ORM\Entity; @@ -64,7 +63,6 @@ class RecordService private NoteAccessControl $noteAccessControl, private Helper $helper, private QueryHelper $queryHelper, - private Metadata $metadata, private NoteHelper $noteHelper, private MassNotePreparator $massNotePreparator, private SelectBuilderFactory $selectBuilderFactory, @@ -149,7 +147,6 @@ class RecordService $this->applyPortalAccess($builder, $where); $this->applyAccess($builder, $id, $scope, $where); $this->applyIgnore($where); - $this->applyStatusIgnore($scope, $where); $builder->where($where); @@ -382,24 +379,6 @@ class RecordService ]; } - /** - * @param array $where - */ - private function applyStatusIgnore(string $scope, array &$where): void - { - $field = $this->metadata->get("scopes.$scope.statusField"); - - if (!$field) { - return; - } - - if ($this->acl->checkField($scope, $field)) { - return; - } - - $where[] = ['type!=' => Note::TYPE_STATUS]; - } - private function prepareNote(Note $note, string $scope, string $id): void { if ( @@ -537,7 +516,6 @@ class RecordService $this->applyPortalAccess($builder, $where); $this->applyAccess($builder, $id, $scope, $where); $this->applyIgnore($where); - $this->applyStatusIgnore($scope, $where); $builder->where($where); diff --git a/application/Espo/Tools/Stream/Service.php b/application/Espo/Tools/Stream/Service.php index c7142404c2..23a17a8cc1 100644 --- a/application/Espo/Tools/Stream/Service.php +++ b/application/Espo/Tools/Stream/Service.php @@ -78,24 +78,6 @@ use stdClass; class Service { - /** @var ?array */ - private $statusStyles = null; - /** @var ?array */ - private $statusFields = null; - /** @var string[] */ - private $successDefaultStyleList = [ - 'Held', - 'Closed Won', - 'Closed', - 'Completed', - 'Complete', - 'Sold', - ]; - /** @var string[] */ - private $dangerDefaultStyleList = [ - 'Closed Lost', - ]; - private const FIELD_ASSIGNED_USERS = Field::ASSIGNED_USERS; /** @@ -126,47 +108,9 @@ class Service private SystemUser $systemUser ) {} - /** - * @return array - */ - private function getStatusStyles(): array - { - if (empty($this->statusStyles)) { - $this->statusStyles = $this->metadata->get('entityDefs.Note.statusStyles', []); - } - - return $this->statusStyles; - } - private function getStatusField(string $entityType): ?string { - return $this->getStatusFields()[$entityType] ?? null; - } - - /** - * @return array - */ - private function getStatusFields(): array - { - if ($this->statusFields === null) { - $this->statusFields = []; - - /** @var array> $scopes */ - $scopes = $this->metadata->get('scopes', []); - - foreach ($scopes as $scope => $data) { - /** @var ?string $statusField */ - $statusField = $data['statusField'] ?? null; - - if (!$statusField) { - continue; - } - - $this->statusFields[$scope] = $statusField; - } - } - - return $this->statusFields; + return $this->metadata->get("scopes.$entityType.statusField"); } public function checkIsFollowed(Entity $entity, ?string $userId = null): bool @@ -680,7 +624,6 @@ class Service if ($value) { $data['statusValue'] = $value; $data['statusField'] = $field; - $data['statusStyle'] = $this->getStatusStyle($entityType, $field, $value); } } @@ -703,34 +646,6 @@ class Service } } - /** - * @param mixed $value - */ - private function getStatusStyle(string $entityType, string $field, $value): string - { - $style = $this->metadata->get(['entityDefs', $entityType, 'fields', $field, 'style', $value]); - - if ($style) { - return $style; - } - - $statusStyles = $this->getStatusStyles(); - - if (isset($statusStyles[$entityType][$value])) { - return $statusStyles[$entityType][$value]; - } - - if (in_array($value, $this->successDefaultStyleList)) { - return 'success'; - } - - if (in_array($value, $this->dangerDefaultStyleList)) { - return 'danger'; - } - - return 'default'; - } - /** * @param array $options */ @@ -858,44 +773,11 @@ class Service /** * @param array $options - * @deprecated As of v9.2.0. The update note carries the status information now. + * @deprecated As of v9.2.0. The Update type note carries the status information now. * @todo Remove in v9.3.0. */ public function noteStatus(Entity $entity, string $field, array $options = []): void - { - $note = $this->getNewNote(); - - $note->setType(Note::TYPE_STATUS); - $note->setParent(LinkParent::createFromEntity($entity)); - - $this->setSuperParent($entity, $note, true); - - $entityType = $entity->getEntityType(); - - $value = $entity->get($field); - - $style = $this->getStatusStyle($entityType, $field, $value); - - $note->set('data', [ - 'field' => $field, - 'value' => $value, - 'style' => $style, - ]); - - $noteOptions = []; - - if (!empty($options[SaveOption::CREATED_BY_ID])) { - $noteOptions[SaveOption::CREATED_BY_ID] = $options[SaveOption::CREATED_BY_ID]; - } - - if (!empty($options[SaveOption::MODIFIED_BY_ID])) { - $noteOptions[SaveOption::CREATED_BY_ID] = $options[SaveOption::MODIFIED_BY_ID]; - } - - $noteOptions[SaveContext::NAME] = SaveContext::obtainFromRawOptions($options)?->createDerived(); - - $this->entityManager->saveEntity($note, $noteOptions); - } + {} /** * @return array< @@ -1431,7 +1313,7 @@ class Service */ private function getStatusUpdateDate(Entity $entity): ?array { - $statusField = $this->metadata->get("scopes.{$entity->getEntityType()}.statusField"); + $statusField = $this->getStatusField($entity->getEntityType()); $statusData = null; if ( @@ -1439,12 +1321,8 @@ class Service $entity->isAttributeChanged($statusField) && $entity->get($statusField) ) { - $style = $this->getStatusStyle($entity->getEntityType(), $statusField, $entity->get($statusField)); - $statusData = [ - 'statusValue' => $entity->get($statusField), - 'statusField' => $statusField, - 'statusStyle' => $style, + 'value' => $entity->get($statusField), ]; } diff --git a/client/src/views/stream/notes/create.js b/client/src/views/stream/notes/create.js index 747c78e79b..d064f2e984 100644 --- a/client/src/views/stream/notes/create.js +++ b/client/src/views/stream/notes/create.js @@ -71,13 +71,18 @@ class CreateNoteStreamView extends NoteStreamView { this.setupUsersData(); - if (data.statusField) { - const statusField = this.statusField = data.statusField; + const parentType = this.model.attributes.parentType; + + if (data.statusValue != null) { + const statusField = this.statusField = this.getMetadata().get(`scopes.${parentType}.statusField`) ?? ''; const statusValue = data.statusValue; - this.statusStyle = data.statusStyle || 'default'; + this.statusStyle = this.getMetadata() + .get(`entityDefs.${parentType}.fields.${statusField}.style.${statusValue}`) || + 'default'; + this.statusText = this.getLanguage() - .translateOption(statusValue, statusField, this.model.attributes.parentType); + .translateOption(statusValue, statusField, parentType); } } diff --git a/client/src/views/stream/notes/status.js b/client/src/views/stream/notes/status.js index 48785575a6..e823257cc5 100644 --- a/client/src/views/stream/notes/status.js +++ b/client/src/views/stream/notes/status.js @@ -56,13 +56,19 @@ class StatusNoteStreamView extends NoteStreamView { setup() { const data = this.model.get('data'); - const field = data.field; + const parentType = this.model.attributes.parentType; + + const field = this.getMetadata().get(`scopes.${parentType}.statusField`) ?? ''; const value = data.value; this.style = data.style || 'default'; - this.statusText = this.getLanguage().translateOption(value, field, this.model.get('parentType')); + this.statusText = this.getLanguage().translateOption(value, field, parentType); - let fieldLabel = this.translate(field, 'fields', this.model.get('parentType')); + this.statusStyle = this.getMetadata() + .get(`entityDefs.${parentType}.fields.${field}.style.${value}`) || + 'default'; + + let fieldLabel = this.translate(field, 'fields', parentType); if (!this.isToUpperCaseStringItems()) { fieldLabel = fieldLabel.toLowerCase(); diff --git a/client/src/views/stream/notes/update.js b/client/src/views/stream/notes/update.js index a974dd065a..3c0bec8f88 100644 --- a/client/src/views/stream/notes/update.js +++ b/client/src/views/stream/notes/update.js @@ -85,23 +85,26 @@ class UpdateNoteStreamView extends NoteStreamView { /** @type {Record} */ const data = this.model.attributes.data; - if (data.statusField) { - const statusField = this.statusField = data.statusField; - const statusValue = data.statusValue; + const parentType = this.model.attributes.parentType; + + if (data.value != null) { + const statusField = this.statusField = this.getMetadata().get(`scopes.${parentType}.statusField`) ?? ''; + const statusValue = data.value; + + this.statusStyle = this.getMetadata() + .get(`entityDefs.${parentType}.fields.${statusField}.style.${statusValue}`) || + 'default'; - this.statusStyle = data.statusStyle || 'default'; this.statusText = this.getLanguage() .translateOption(statusValue, statusField, this.model.attributes.parentType); } this.wait(true); - this.getModelFactory().create(this.model.get('parentType'), model => { + this.getModelFactory().create(parentType, model => { const modelWas = model; const modelBecame = model.clone(); - - data.attributes = data.attributes || {}; modelWas.set(data.attributes.was); @@ -109,7 +112,7 @@ class UpdateNoteStreamView extends NoteStreamView { this.fieldDataList = []; - const fields = this.fieldList = data.fields; + const fields = this.fieldList = data.fields ?? []; fields.forEach(field => { const type = model.getFieldType(field) || 'base';