notify about reactions if not followed

This commit is contained in:
Yuri Kuznetsov
2025-09-30 12:21:40 +03:00
parent 107571a544
commit ec5bc2f6f2
7 changed files with 67 additions and 46 deletions

View File

@@ -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",

View File

@@ -142,7 +142,7 @@
],
[
{"name": "reactionNotifications"},
false
{"name": "reactionNotificationsNotFollowed"}
]
]
}

View File

@@ -30,7 +30,7 @@
[{"name": "receiveStreamEmailNotifications"}, false],
[
{"name": "reactionNotifications"},
false
{"name": "reactionNotificationsNotFollowed"}
]
]
}

View File

@@ -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",

View File

@@ -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"
}
]
}
}
}
}

View File

@@ -41,6 +41,9 @@ use Espo\Tools\Stream\Service;
class NotificationService
{
/** @var array<string, ?Preferences> */
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];
}
}

View File

@@ -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