From cb87133fae4e24b285d392eb751f78ee7f255c7a Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 7 Aug 2025 16:15:04 +0300 Subject: [PATCH] calendar date scope support --- .../metadata/clientDefs/Calendar.json | 1 - .../Crm/Resources/metadata/scopes/Task.json | 1 + .../Modules/Crm/Tools/Calendar/Service.php | 24 +++++-- .../crm/src/views/calendar/calendar.js | 57 ++++++++++++++--- .../crm/src/views/calendar/timeline.js | 64 +++++++++++++++---- schema/metadata/scopes.json | 4 ++ 6 files changed, 123 insertions(+), 28 deletions(-) diff --git a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Calendar.json b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Calendar.json index 56d18d9d28..af776d470e 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Calendar.json +++ b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Calendar.json @@ -5,7 +5,6 @@ "Task": "#70c173" }, "scopeList": ["Meeting", "Call", "Task"], - "allDayScopeList": ["Task"], "modeList": ["month", "agendaWeek", "timeline", "agendaDay"], "sharedViewModeList": ["basicWeek", "month", "basicDay"], "additionalColorList": ["#AB78AD", "#CC9B45"], diff --git a/application/Espo/Modules/Crm/Resources/metadata/scopes/Task.json b/application/Espo/Modules/Crm/Resources/metadata/scopes/Task.json index 5d3e88fb50..f115041e43 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/scopes/Task.json +++ b/application/Espo/Modules/Crm/Resources/metadata/scopes/Task.json @@ -14,6 +14,7 @@ "importable": true, "notifications": true, "calendar": true, + "calendarOneDay": true, "object": true, "statusField": "status", "stream": true, diff --git a/application/Espo/Modules/Crm/Tools/Calendar/Service.php b/application/Espo/Modules/Crm/Tools/Calendar/Service.php index 30856cb9dd..4063e6c668 100644 --- a/application/Espo/Modules/Crm/Tools/Calendar/Service.php +++ b/application/Espo/Modules/Crm/Tools/Calendar/Service.php @@ -61,6 +61,7 @@ use Espo\ORM\Query\Part\Condition as Cond; use Espo\ORM\Query\Part\Expression as Expr; use Espo\ORM\Query\Part\Where\OrGroup; use Espo\ORM\Query\Select; +use Espo\ORM\Type\AttributeType; use Espo\ORM\Type\RelationType; use Espo\Tools\WorkingTime\Calendar as WorkingCalendar; use Espo\Tools\WorkingTime\CalendarFactory as WorkingCalendarFactory; @@ -274,15 +275,30 @@ class Service $seed = $this->entityManager->getNewEntity($scope); + if ($seed->getAttributeType('dateStart') === AttributeType::DATE) { + $dateStartSelect = ['null', 'dateStart']; + $dateEndSelect = ['null', 'dateEnd']; + $dateStartDateSelect = ['dateStart', 'dateStartDate']; + $dateEndDateSelect = ['dateEnd', 'dateEndDate']; + + } else { + $dateStartSelect = ['dateStart', 'dateStart']; + $dateEndSelect = ['dateEnd', 'dateEnd']; + $dateStartDateSelect = ($seed->hasAttribute('dateStartDate') ? + ['dateStartDate', 'dateStartDate'] : ['null', 'dateStartDate']); + $dateEndDateSelect = ($seed->hasAttribute('dateEndDate') ? + ['dateEndDate', 'dateEndDate'] : ['null', 'dateEndDate']); + } + $select = [ ['"' . $scope . '"', 'scope'], 'id', 'name', - ['dateStart', 'dateStart'], - ['dateEnd', 'dateEnd'], + $dateStartSelect, + $dateEndSelect, ($seed->hasAttribute('status') ? ['status', 'status'] : ['null', 'status']), - ($seed->hasAttribute('dateStartDate') ? ['dateStartDate', 'dateStartDate'] : ['null', 'dateStartDate']), - ($seed->hasAttribute('dateEndDate') ? ['dateEndDate', 'dateEndDate'] : ['null', 'dateEndDate']), + $dateStartDateSelect, + $dateEndDateSelect, ($seed->hasAttribute('parentType') ? ['parentType', 'parentType'] : ['null', 'parentType']), ($seed->hasAttribute('parentId') ? ['parentId', 'parentId'] : ['null', 'parentId']), Field::CREATED_AT, diff --git a/client/modules/crm/src/views/calendar/calendar.js b/client/modules/crm/src/views/calendar/calendar.js index eed91a6898..336a04bd28 100644 --- a/client/modules/crm/src/views/calendar/calendar.js +++ b/client/modules/crm/src/views/calendar/calendar.js @@ -51,8 +51,31 @@ class CalendarView extends View { eventAttributes = [] colors = {} - allDayScopeList = ['Task'] + + /** + * @private + * @type {string[]} + */ + allDayScopeList + + /** + * @private + * @type {string[]} + */ scopeList = ['Meeting', 'Call', 'Task'] + + /** + * @private + * @type {string[]} + */ + onlyDateScopeList + + /** + * @private + * @type {string[]} + */ + enabledScopeList + header = true modeList = [] fullCalendarModeList = [ @@ -204,11 +227,19 @@ class CalendarView extends View { this.modeList = this.getMetadata() .get('clientDefs.Calendar.modeList') || this.modeList; - this.scopeList = this.getConfig() - .get('calendarEntityList') || Espo.Utils.clone(this.scopeList); + this.scopeList = this.getConfig().get('calendarEntityList') || Espo.Utils.clone(this.scopeList); - this.allDayScopeList = this.getMetadata() - .get('clientDefs.Calendar.allDayScopeList') || this.allDayScopeList; + this.allDayScopeList = this.getMetadata().get('clientDefs.Calendar.allDayScopeList') ?? []; + + this.scopeList.forEach(scope => { + if (this.getMetadata().get(`scopes.${scope}.calendarOneDay`) && !this.allDayScopeList.includes(scope)) { + this.allDayScopeList.push(scope); + } + }); + + this.onlyDateScopeList = this.scopeList.filter(scope => { + return this.getMetadata().get(`entityDefs.${scope}.fields.dateStart.type`) === 'date'; + }); this.slotDuration = this.options.slotDuration || this.getPreferences().get('calendarSlotDuration') || @@ -553,13 +584,13 @@ class CalendarView extends View { let start; let end; - if (o.dateStart) { + if (o.dateStart || o.dateStartDate) { start = !o.dateStartDate ? this.getDateTime().toMoment(o.dateStart) : this.dateToMoment(o.dateStartDate); } - if (o.dateEnd) { + if (o.dateEnd || o.dateEndDate) { end = !o.dateEndDate ? this.getDateTime().toMoment(o.dateEnd) : this.dateToMoment(o.dateEndDate); @@ -737,7 +768,7 @@ class CalendarView extends View { event.allDay = true; event.allDayCopy = event.allDay; - if (!afterDrop) { + if (!afterDrop && end) { end.add(1, 'days') } @@ -989,6 +1020,12 @@ class CalendarView extends View { const scope = event.extendedProps.scope; + if (this.onlyDateScopeList.includes(scope)) { + info.revert(); + + return; + } + if (!event.allDay && event.extendedProps.allDayCopy) { info.revert(); @@ -1249,11 +1286,13 @@ class CalendarView extends View { attributes.assignedUserName = this.options.userName || this.options.userId; } + const scopeList = this.enabledScopeList.filter(it => !this.onlyDateScopeList.includes(it)); + Espo.Ui.notifyWait(); const view = await this.createView('dialog', 'crm:views/calendar/modals/edit', { attributes: attributes, - enabledScopeList: this.enabledScopeList, + enabledScopeList: scopeList, scopeList: this.scopeList, allDay: values.allDay, dateStartDate: values.dateStartDate, diff --git a/client/modules/crm/src/views/calendar/timeline.js b/client/modules/crm/src/views/calendar/timeline.js index 5d416c6c12..dfd8846d79 100644 --- a/client/modules/crm/src/views/calendar/timeline.js +++ b/client/modules/crm/src/views/calendar/timeline.js @@ -42,8 +42,33 @@ class TimelineView extends View { eventAttributes = [] colors = {} - scopeList = [] + + /** + * @private + * @type {string[]} + */ + allDayScopeList + + /** + * @private + * @type {string[]} + */ + scopeList = ['Meeting', 'Call', 'Task'] + + /** + * @private + * @type {string[]} + */ + enabledScopeList + + /** + * @private + * @type {string[]} + */ + onlyDateScopeList + header = true + modeList = [] defaultMode = 'timeline' maxRange = 120 @@ -166,14 +191,19 @@ class TimelineView extends View { this.$container = this.options.$container; - this.colors = Espo.Utils - .clone(this.getMetadata().get('clientDefs.Calendar.colors') || this.colors || {}); - this.modeList = this.getMetadata() - .get('clientDefs.Calendar.modeList') || this.modeList || []; - this.scopeList = this.getConfig() - .get('calendarEntityList') || Espo.Utils.clone(this.scopeList) || []; - this.allDayScopeList = this.getMetadata() - .get('clientDefs.Calendar.allDayScopeList') || this.allDayScopeList || []; + this.colors = Espo.Utils.clone(this.getMetadata().get('clientDefs.Calendar.colors') || this.colors || {}); + + this.modeList = this.getMetadata().get('clientDefs.Calendar.modeList') || this.modeList || []; + + this.scopeList = this.getConfig().get('calendarEntityList') || Espo.Utils.clone(this.scopeList); + + this.allDayScopeList = this.getMetadata().get('clientDefs.Calendar.allDayScopeList') ?? []; + + this.scopeList.forEach(scope => { + if (this.getMetadata().get(`scopes.${scope}.calendarOneDay`) && !this.allDayScopeList.includes(scope)) { + this.allDayScopeList.push(scope); + } + }); this.colors = {...this.colors, ...this.getHelper().themeManager.getParam('calendarColors')}; @@ -211,6 +241,10 @@ class TimelineView extends View { } }); + this.onlyDateScopeList = this.scopeList.filter(scope => { + return this.getMetadata().get(`entityDefs.${scope}.fields.dateStart.type`) === 'date'; + }); + if (this.options.calendarType) { this.calendarType = this.options.calendarType; } else { @@ -403,7 +437,7 @@ class TimelineView extends View { event[attr] = o[attr]; }); - if (o.dateStart) { + if (o.dateStart || o.dateStartDate) { if (!o.dateStartDate) { event.start = this.getDateTime().toMoment(o.dateStart); } else { @@ -411,7 +445,7 @@ class TimelineView extends View { } } - if (o.dateEnd) { + if (o.dateEnd || o.dateEndDate) { if (!o.dateEndDate) { event.end = this.getDateTime().toMoment(o.dateEnd); } else { @@ -419,7 +453,7 @@ class TimelineView extends View { } } - if (o.dateStartDate && !~this.allDayScopeList.indexOf(o.scope)) { + if (o.dateStartDate && !this.allDayScopeList.includes(o.scope) && event.end) { event.end = event.end.clone().add(1, 'days'); } @@ -427,7 +461,7 @@ class TimelineView extends View { return event; } - if (~this.allDayScopeList.indexOf(o.scope)) { + if (this.allDayScopeList.includes(o.scope)) { event.type = 'box'; if (event.end) { @@ -697,11 +731,13 @@ class TimelineView extends View { attributes.assignedUserName = userName || userId; } + const scopeList = this.enabledScopeList.filter(it => !this.onlyDateScopeList.includes(it)); + Espo.Ui.notifyWait(); const view = await this.createView('dialog', 'crm:views/calendar/modals/edit', { attributes: attributes, - enabledScopeList: this.enabledScopeList, + enabledScopeList: scopeList, scopeList: this.scopeList }); diff --git a/schema/metadata/scopes.json b/schema/metadata/scopes.json index d7057be846..9887e5ab9a 100644 --- a/schema/metadata/scopes.json +++ b/schema/metadata/scopes.json @@ -249,6 +249,10 @@ "description": "Whether the entity can be displayed on the Calendar.", "type": "boolean" }, + "calendarOneDay": { + "description": "Display as one-day events in the calendar.", + "type": "boolean" + }, "activity": { "description": "Whether the entity can be displayed on the Activities panel.", "type": "boolean"