From ec5bc2f6f22c2ed28e2ef06c55647fbfc77e6468 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 30 Sep 2025 12:21:40 +0300 Subject: [PATCH] notify about reactions if not followed --- .../Resources/i18n/en_US/Preferences.json | 1 + .../Resources/layouts/Preferences/detail.json | 2 +- .../layouts/Preferences/detailPortal.json | 2 +- .../metadata/entityDefs/Preferences.json | 4 ++ .../metadata/logicDefs/Preferences.json | 30 ++++++++++++ .../UserReaction/NotificationService.php | 27 ++++++++++- client/src/views/preferences/record/edit.js | 47 ++----------------- 7 files changed, 67 insertions(+), 46 deletions(-) diff --git a/application/Espo/Resources/i18n/en_US/Preferences.json b/application/Espo/Resources/i18n/en_US/Preferences.json index 8cdf5115d8..491ea2f192 100644 --- a/application/Espo/Resources/i18n/en_US/Preferences.json +++ b/application/Espo/Resources/i18n/en_US/Preferences.json @@ -16,6 +16,7 @@ "assignmentNotificationsIgnoreEntityTypeList": "In-app assignment notifications", "assignmentEmailNotificationsIgnoreEntityTypeList": "Email assignment notifications", "reactionNotifications": "In-app notifications about reactions", + "reactionNotificationsNotFollowed": "Notifications about reactions for non-followed records", "autoFollowEntityTypeList": "Global Auto-Follow", "signature": "Email Signature", "dashboardTabList": "Tab List", diff --git a/application/Espo/Resources/layouts/Preferences/detail.json b/application/Espo/Resources/layouts/Preferences/detail.json index 928c70b4ca..fd61a16efe 100644 --- a/application/Espo/Resources/layouts/Preferences/detail.json +++ b/application/Espo/Resources/layouts/Preferences/detail.json @@ -142,7 +142,7 @@ ], [ {"name": "reactionNotifications"}, - false + {"name": "reactionNotificationsNotFollowed"} ] ] } diff --git a/application/Espo/Resources/layouts/Preferences/detailPortal.json b/application/Espo/Resources/layouts/Preferences/detailPortal.json index 0d31f28a1d..09986b64d7 100644 --- a/application/Espo/Resources/layouts/Preferences/detailPortal.json +++ b/application/Espo/Resources/layouts/Preferences/detailPortal.json @@ -30,7 +30,7 @@ [{"name": "receiveStreamEmailNotifications"}, false], [ {"name": "reactionNotifications"}, - false + {"name": "reactionNotificationsNotFollowed"} ] ] } diff --git a/application/Espo/Resources/metadata/entityDefs/Preferences.json b/application/Espo/Resources/metadata/entityDefs/Preferences.json index b331253c3c..56bf560e1b 100644 --- a/application/Espo/Resources/metadata/entityDefs/Preferences.json +++ b/application/Espo/Resources/metadata/entityDefs/Preferences.json @@ -111,6 +111,10 @@ "type": "bool", "default": true }, + "reactionNotificationsNotFollowed": { + "type": "bool", + "default": false + }, "autoFollowEntityTypeList": { "type": "multiEnum", "view": "views/preferences/fields/auto-follow-entity-type-list", diff --git a/application/Espo/Resources/metadata/logicDefs/Preferences.json b/application/Espo/Resources/metadata/logicDefs/Preferences.json index 7f532297bd..fde7f141e5 100644 --- a/application/Espo/Resources/metadata/logicDefs/Preferences.json +++ b/application/Espo/Resources/metadata/logicDefs/Preferences.json @@ -1,5 +1,25 @@ { "fields": { + "tabList": { + "visible": { + "conditionGroup": [ + { + "type": "isTrue", + "attribute": "useCustomTabList" + } + ] + } + }, + "addCustomTabs": { + "visible": { + "conditionGroup": [ + { + "type": "isTrue", + "attribute": "useCustomTabList" + } + ] + } + }, "assignmentEmailNotificationsIgnoreEntityTypeList": { "visible": { "conditionGroup": [ @@ -9,6 +29,16 @@ } ] } + }, + "reactionNotificationsNotFollowed": { + "visible": { + "conditionGroup": [ + { + "type": "isTrue", + "attribute": "reactionNotifications" + } + ] + } } } } diff --git a/application/Espo/Tools/UserReaction/NotificationService.php b/application/Espo/Tools/UserReaction/NotificationService.php index 98c0c2f746..df11a2abc1 100644 --- a/application/Espo/Tools/UserReaction/NotificationService.php +++ b/application/Espo/Tools/UserReaction/NotificationService.php @@ -41,6 +41,9 @@ use Espo\Tools\Stream\Service; class NotificationService { + /** @var array */ + private array $preferencesMap = []; + public function __construct( private EntityManager $entityManager, private User $user, @@ -57,7 +60,11 @@ class NotificationService $parent = $note->getParent(); - if ($parent && !$this->streamService->checkIsFollowed($parent, $note->getCreatedById())) { + if ( + $parent && + !$this->isEnabledForUserForNotFollowed($recipientId) && + !$this->streamService->checkIsFollowed($parent, $note->getCreatedById()) + ) { return; } @@ -90,11 +97,18 @@ class NotificationService private function isEnabledForUser(string $recipientId): bool { - $recipientPreferences = $this->entityManager->getRepositoryByClass(Preferences::class)->getById($recipientId); + $recipientPreferences = $this->getPreferences($recipientId); return $recipientPreferences && $recipientPreferences->get('reactionNotifications'); } + private function isEnabledForUserForNotFollowed(string $recipientId): bool + { + $recipientPreferences = $this->getPreferences($recipientId); + + return $recipientPreferences && $recipientPreferences->get('reactionNotificationsNotFollowed'); + } + public function removeNoteUnread(Note $note, User $user, ?string $type = null): void { $notifications = $this->entityManager @@ -116,4 +130,13 @@ class NotificationService $this->entityManager->removeEntity($notification); } } + + private function getPreferences(string $id): ?Preferences + { + if (!array_key_exists($id, $this->preferencesMap)) { + $this->preferencesMap[$id] = $this->entityManager->getRepositoryByClass(Preferences::class)->getById($id); + } + + return $this->preferencesMap[$id]; + } } diff --git a/client/src/views/preferences/record/edit.js b/client/src/views/preferences/record/edit.js index 62e1757cbb..34440de8dd 100644 --- a/client/src/views/preferences/record/edit.js +++ b/client/src/views/preferences/record/edit.js @@ -45,32 +45,9 @@ class PreferencesEditRecordView extends EditRecordView { } ] - dynamicLogicDefs = { - fields: { - 'tabList': { - visible: { - conditionGroup: [ - { - type: 'isTrue', - attribute: 'useCustomTabList', - } - ] - } - }, - 'addCustomTabs': { - visible: { - conditionGroup: [ - { - type: 'isTrue', - attribute: 'useCustomTabList', - } - ] - } - }, - }, - } - setup() { + this.dynamicLogicDefs = Espo.Utils.cloneDeep(this.getMetadata().get(`logicDefs.Preferences`)); + super.setup(); const model = /** @type {import('models/preferences').default} */this.model; @@ -123,20 +100,14 @@ class PreferencesEditRecordView extends EditRecordView { let hideNotificationPanel = true; if (!this.getConfig().get('assignmentEmailNotifications') || model.isPortal()) { - this.hideField('receiveAssignmentEmailNotifications'); - this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList'); + this.hideField('receiveAssignmentEmailNotifications', true); + this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList', true); } else { hideNotificationPanel = false; - - this.controlAssignmentEmailNotificationsVisibility(); - - this.listenTo(this.model, 'change:receiveAssignmentEmailNotifications', () => { - this.controlAssignmentEmailNotificationsVisibility(); - }); } if ((this.getConfig().get('assignmentEmailNotificationsEntityList') || []).length === 0) { - this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList'); + this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList', true); } if ( @@ -204,14 +175,6 @@ class PreferencesEditRecordView extends EditRecordView { } } - controlAssignmentEmailNotificationsVisibility() { - if (this.model.get('receiveAssignmentEmailNotifications')) { - this.showField('assignmentEmailNotificationsIgnoreEntityTypeList'); - } else { - this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList'); - } - } - actionReset() { this.confirm(this.translate('resetPreferencesConfirmation', 'messages'), () => { Espo.Ajax