send invitations impr

This commit is contained in:
Yuri Kuznetsov
2022-11-23 12:00:48 +02:00
parent b7ff603772
commit 3076b6c8ea
21 changed files with 528 additions and 127 deletions
@@ -107,6 +107,7 @@ class Invitations
'inviteeId' => $invitee->getId(),
'inviteeType' => $invitee->getEntityType(),
'link' => $link,
'dateStart' => $entity->get('dateStart'),
]);
if ($entity->get('dateEnd')) {
@@ -31,22 +31,27 @@ namespace Espo\Modules\Crm\Controllers;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\Record;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\Api\Request;
use Espo\Core\Mail\Exceptions\SendingError;
use Espo\Core\Utils\Json;
use Espo\Modules\Crm\Entities\Call as CallEntity;
use Espo\Modules\Crm\Tools\Meeting\InvitationService;
use Espo\Modules\Crm\Tools\Meeting\Invitee;
use Espo\Modules\Crm\Tools\Meeting\Service;
use stdClass;
class Call extends Record
{
/**
* @throws BadRequest
* @throws Forbidden
* @throws ForbiddenSilent
* @throws Error
* @throws SendingError
* @throws NotFound
*/
public function postActionSendInvitations(Request $request): bool
@@ -57,13 +62,52 @@ class Call extends Record
throw new BadRequest();
}
$invitees = $this->fetchInvitees($request);
$resultList = $this->injectableFactory
->create(InvitationService::class)
->send(CallEntity::ENTITY_TYPE, $id);
->send(CallEntity::ENTITY_TYPE, $id, $invitees);
return $resultList !== 0;
}
/**
* @param Request $request
* @return ?Invitee[]
* @throws BadRequest
*/
private function fetchInvitees(Request $request): ?array
{
$targets = $request->getParsedBody()->targets ?? null;
if ($targets === null) {
return null;
}
if (!is_array($targets)) {
throw new BadRequest();
}
$invitees = [];
foreach ($targets as $target) {
if (!$target instanceof stdClass) {
throw new BadRequest();
}
$targetEntityType = $target->entityType ?? null;
$targetId = $target->id ?? null;
if (!is_string($targetEntityType) || !is_string($targetId)) {
throw new BadRequest();
}
$invitees[] = new Invitee($targetEntityType, $targetId);
}
return $invitees;
}
/**
* @throws BadRequest
* @throws Forbidden
@@ -31,6 +31,7 @@ namespace Espo\Modules\Crm\Controllers;
use Espo\Core\Api\Response;
use Espo\Core\Controllers\Record;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\ForbiddenSilent;
@@ -38,17 +39,21 @@ use Espo\Core\Exceptions\NotFound;
use Espo\Core\Api\Request;
use Espo\Core\Mail\Exceptions\SendingError;
use Espo\Core\Utils\Json;
use Espo\Modules\Crm\Entities\Meeting as MeetingEntity;
use Espo\Modules\Crm\Tools\Meeting\InvitationService;
use Espo\Modules\Crm\Tools\Meeting\Invitee;
use Espo\Modules\Crm\Tools\Meeting\Service;
use stdClass;
class Meeting extends Record
{
/**
* @throws BadRequest
* @throws Forbidden
* @throws ForbiddenSilent
* @throws Error
* @throws SendingError
* @throws NotFound
*/
public function postActionSendInvitations(Request $request): bool
@@ -59,13 +64,52 @@ class Meeting extends Record
throw new BadRequest();
}
$invitees = $this->fetchInvitees($request);
$resultList = $this->injectableFactory
->create(InvitationService::class)
->send(MeetingEntity::ENTITY_TYPE, $id);
->send(MeetingEntity::ENTITY_TYPE, $id, $invitees);
return $resultList !== 0;
}
/**
* @param Request $request
* @return ?Invitee[]
* @throws BadRequest
*/
private function fetchInvitees(Request $request): ?array
{
$targets = $request->getParsedBody()->targets ?? null;
if ($targets === null) {
return null;
}
if (!is_array($targets)) {
throw new BadRequest();
}
$invitees = [];
foreach ($targets as $target) {
if (!$target instanceof stdClass) {
throw new BadRequest();
}
$targetEntityType = $target->entityType ?? null;
$targetId = $target->id ?? null;
if (!is_string($targetEntityType) || !is_string($targetId)) {
throw new BadRequest();
}
$invitees[] = new Invitee($targetEntityType, $targetId);
}
return $invitees;
}
/**
* @throws BadRequest
* @throws Forbidden
@@ -34,6 +34,7 @@ use Espo\Core\Api\Response;
use Espo\Core\EntryPoint\EntryPoint;
use Espo\Core\EntryPoint\Traits\NoAuth;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\NotFound;
use Espo\Core\HookManager;
use Espo\Core\ORM\EntityManager;
@@ -67,6 +68,7 @@ class EventConfirmation implements EntryPoint
/**
* @throws BadRequest
* @throws NotFound
* @throws Error
*/
public function run(Request $request, Response $response): void
{
@@ -100,6 +102,7 @@ class EventConfirmation implements EntryPoint
$inviteeType = $data->inviteeType ?? null;
$inviteeId = $data->inviteeId ?? null;
$link = $data->link ?? null;
$sentDateStart = $data->dateStart ?? null;
$toProcess =
$eventType &&
@@ -109,7 +112,11 @@ class EventConfirmation implements EntryPoint
$link;
if (!$toProcess) {
throw new BadRequest();
throw new Error();
}
if ($sentDateStart !== null && !is_string($sentDateStart)) {
throw new Error();
}
$event = $this->entityManager->getEntityById($eventType, $eventId);
@@ -187,6 +194,8 @@ class EventConfirmation implements EntryPoint
$actionData['translatedStatus'] = $this->language->translateOption($status, 'acceptanceStatus', $eventType);
$actionData['style'] = $this->metadata
->get(['entityDefs', $eventType, 'fields', 'acceptanceStatus', 'style', $status]);
$actionData['sentDateStart'] = $sentDateStart;
$actionData['statusTranslation'] = $this->language->get([Meeting::ENTITY_TYPE, 'options', 'acceptanceStatus']);
$this->actionRenderer->write(
$response,
@@ -53,7 +53,7 @@
"todays": "Today's"
},
"messages": {
"sendInvitationsConfirmation": "Send invitation emails to attendees?",
"sendInvitationsToSelectedAttendees": "Invitation emails will be sent to the selected attendees.",
"selectAcceptanceStatus": "Set your acceptance status.",
"nothingHasBeenSent": "Nothing were sent"
}
@@ -6,8 +6,10 @@
{{/if}}
{{/if}}
<p>
<a href="{{acceptLink}}">Annehmen</a>, <a href="{{declineLink}}">Decline</a>, <a href="{{tentativeLink}}">mit Vorbehalt</a>
<a href="{{acceptLink}}" style="font-size: 1.2em">Annehmen</a> &middot;
<a href="{{tentativeLink}}" style="font-size: 1.2em">mit Vorbehalt</a> &middot;
<a href="{{declineLink}}" style="font-size: 1.2em">Decline</a>
</p>
{{#if isUser}}
<p><a href="{{recordUrl}}">Eintrag öffnen</a></p>
{{/if}}
{{/if}}
@@ -6,8 +6,10 @@
{{/if}}
{{/if}}
<p>
<a href="{{acceptLink}}">Accept</a>, <a href="{{declineLink}}">Decline</a>, <a href="{{tentativeLink}}">Tentative</a>
<a href="{{acceptLink}}" style="font-size: 1.2em">Accept</a> &middot;
<a href="{{tentativeLink}}" style="font-size: 1.2em">Tentative</a> &middot;
<a href="{{declineLink}}" style="font-size: 1.2em">Decline</a>
</p>
{{#if isUser}}
<p><a href="{{recordUrl}}">View record</a></p>
{{/if}}
{{/if}}
@@ -6,7 +6,9 @@
{{/if}}
{{/if}}
<p>
<a href="{{acceptLink}}">Aceptar</a>, <a href="{{declineLink}}">Declinar</a>, <a href="{{tentativeLink}}">Provisional</a>
<a href="{{acceptLink}}" style="font-size: 1.2em">Aceptar</a> &middot;
<a href="{{tentativeLink}}" style="font-size: 1.2em">Provisional</a> &middot;
<a href="{{declineLink}}" style="font-size: 1.2em">Declinar</a>
</p>
{{#if isUser}}
<p><a href="{{recordUrl}}">Ver registro</a></p>
@@ -6,8 +6,10 @@
{{/if}}
{{/if}}
<p>
<a href="{{acceptLink}}">Accepter</a>, <a href="{{declineLink}}">Décliner</a>, <a href="{{tentativeLink}}">Tentative</a>
<a href="{{acceptLink}}" style="font-size: 1.2em">Accepter</a> &middot;
<a href="{{tentativeLink}}" style="font-size: 1.2em">Tentative</a> &middot;
<a href="{{declineLink}}" style="font-size: 1.2em">Décliner</a>
</p>
{{#if isUser}}
<p><a href="{{recordUrl}}">Voir la fiche</a></p>
{{/if}}
{{/if}}
@@ -6,8 +6,10 @@
{{/if}}
{{/if}}
<p>
<a href="{{acceptLink}}">Прийняти</a>, <a href="{{declineLink}}">Відхилити</a>, <a href="{{tentativeLink}}">Не впевнений</a>
<a href="{{acceptLink}}" style="font-size: 1.2em">Прийняти</a> &middot;
<a href="{{tentativeLink}}" style="font-size: 1.2em">Не впевнений</a> &middot;
<a href="{{declineLink}}" style="font-size: 1.2em">Відхилити</a>
</p>
{{#if isUser}}
<p><a href="{{recordUrl}}">Відкрити запис</a></p>
{{/if}}
{{/if}}
@@ -41,10 +41,7 @@ use Espo\Core\Record\ServiceContainer as RecordServiceContainer;
use Espo\Core\Utils\Config;
use Espo\Entities\User;
use Espo\Modules\Crm\Business\Event\Invitations;
use Espo\Modules\Crm\Entities\Contact;
use Espo\Modules\Crm\Entities\Lead;
use Espo\Modules\Crm\Entities\Meeting;
use Espo\ORM\Collection;
use Espo\ORM\Entity;
use Espo\ORM\EntityManager;
use Espo\Tools\Email\SendService;
@@ -81,13 +78,14 @@ class InvitationService
/**
* Send invitations for a meeting (or call). Checks access. Uses user's SMTP if available.
*
* @param ?Invitee[] $targets
* @return Entity[] Entities an invitation was sent to.
* @throws NotFound
* @throws Forbidden
* @throws Error
* @throws SendingError
*/
public function send(string $entityType, string $id): array
public function send(string $entityType, string $id, ?array $targets = null): array
{
$entity = $this->recordServiceContainer
->get($entityType)
@@ -101,53 +99,50 @@ class InvitationService
throw new Forbidden("No edit access.");
}
$linkList = [
'users',
'contacts',
'leads',
];
$sender = $this->getSender();
$sentAddressList = [];
$resultEntityList = [];
foreach ($this->getUsers($entity) as $user) {
$emailAddress = $user->getEmailAddress();
foreach ($linkList as $link) {
$builder = $this->entityManager
->getRDBRepository($entityType)
->getRelation($entity, $link);
if ($emailAddress) {
$sender->sendInvitation($entity, $user, 'users');
$sentAddressList[] = $emailAddress;
$resultEntityList[] = $user;
if ($targets === null) {
$builder->where([
'@relation.status=' => Meeting::ATTENDEE_STATUS_NONE,
]);
}
}
/** @var Collection<Contact> $contacts */
$contacts = $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'contacts')
->find();
$collection = $builder->find();
/** @var Collection<Lead> $leads */
$leads = $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'leads')
->find();
foreach ($collection as $attendee) {
if ($targets && !self::isInTargets($attendee, $targets)) {
continue;
}
foreach ($contacts as $contact) {
$emailAddress = $contact->getEmailAddress();
$emailAddress = $attendee->get('emailAddress');
if ($emailAddress && !in_array($emailAddress, $sentAddressList)) {
$sender->sendInvitation($entity, $contact, 'contacts');
if (!$emailAddress || in_array($emailAddress, $sentAddressList)) {
continue;
}
$sender->sendInvitation($entity, $attendee, $link);
$sentAddressList[] = $emailAddress;
$resultEntityList[] = $contact;
}
}
$resultEntityList[] = $attendee;
foreach ($leads as $lead) {
$emailAddress = $lead->getEmailAddress();
if ($emailAddress && !in_array($emailAddress, $sentAddressList)) {
$sender->sendInvitation($entity, $lead, 'leads');
$sentAddressList[] = $emailAddress;
$resultEntityList[] = $lead;
$this->entityManager
->getRDBRepository($entityType)
->getRelation($entity, $link)
->updateColumns($attendee, ['status' => Meeting::ATTENDEE_STATUS_NONE]);
}
}
@@ -155,26 +150,20 @@ class InvitationService
}
/**
* @return Collection<User>
* @param Invitee[] $targets
*/
private function getUsers(Entity $entity): Collection
private static function isInTargets(Entity $entity, array $targets): bool
{
/** @var Collection<User> */
return $this->entityManager
->getRDBRepository($entity->getEntityType())
->getRelation($entity, 'users')
->where([
'OR' => [
[
'id=' => $this->user->getId(),
'@relation.status!=' => Meeting::ATTENDEE_STATUS_ACCEPTED,
],
[
'id!=' => $this->user->getId(),
]
]
])
->find();
foreach ($targets as $target) {
if (
$entity->getEntityType() === $target->getEntityType() &&
$entity->getId() === $target->getId()
) {
return true;
}
}
return false;
}
private function getSender(): Invitations
@@ -0,0 +1,19 @@
<?php
namespace Espo\Modules\Crm\Tools\Meeting;
class Invitee
{
public function __construct(private string $entityType, private string $id)
{}
public function getEntityType(): string
{
return $this->entityType;
}
public function getId(): string
{
return $this->id;
}
}
@@ -2,10 +2,30 @@
<div class="block-center-md">
<div class="panel panel-default">
<div class="panel-body">
<h4>{{actionData.translatedEntityType}}: {{actionData.eventName}}</h4>
<p class="margin-top-2x">
<h4 class="margin-bottom-2x">{{actionData.translatedEntityType}}: {{actionData.eventName}}</h4>
<div class="margin-bottom-2x">
{{#if dateStartChanged}}
<div style="text-decoration: line-through;">{{sentDateStart}}</div>
{{/if}}
<div>{{dateStart}}</div>
</div>
<div>
<span class="label label-{{style}} label-md">{{actionData.translatedStatus}}</span>
</p>
&nbsp;<div class="btn-group">
<a role="button" class="dropdown-toggle text-soft" data-toggle="dropdown">
<span class="fas fa-ellipsis-h"></span>
</a>
<ul class="dropdown-menu">
{{#each actionDataList}}
<li>
<a {{#if link}}href="{{link}}"{{/if}}>{{label}}
{{#if active}}<span class="fas fa-check pull-right"></span>{{/if}}
</a>
</li>
{{/each}}
</ul>
</div>
</div>
</div>
</div>
</div>
@@ -26,19 +26,61 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('crm:views/event-confirmation/confirmation', ['view'], function (Dep) {
define('crm:views/event-confirmation/confirmation', ['view', 'lib!moment'], function (Dep, moment) {
return Dep.extend({
template: 'crm:event-confirmation/confirmation',
data: function () {
let style = this.options.actionData.style || 'default';
let style = this.actionData.style || 'default';
return {
actionData: this.options.actionData,
actionData: this.actionData,
style: style,
dateStart: this.actionData.dateStart ?
this.convertDateTime(this.actionData.dateStart) : null,
sentDateStart: this.actionData.sentDateStart ?
this.convertDateTime(this.actionData.sentDateStart) : null,
dateStartChanged: this.actionData.sentDateStart &&
this.actionData.dateStart !== this.actionData.sentDateStart,
actionDataList: this.getActionDataList(),
};
},
setup: function () {
this.actionData = this.options.actionData;
},
getActionDataList: function () {
let actionMap = {
'Accepted': 'accept',
'Declined': 'decline',
'Tentative': 'tentative',
};
let statusList = ['Accepted', 'Tentative', 'Declined'];
let url = window.location.href.replace('action=' + actionMap[this.actionData.status], 'action={action}');
return statusList.map(item => {
let active = item === this.actionData.status;
return {
active: active,
link: active ? '' : url.replace('{action}', actionMap[item]),
label: this.actionData.statusTranslation[item],
};
});
},
convertDateTime: function (value) {
let timezone = this.getConfig().get('timeZone');
let m = this.getDateTime().toMoment(value)
.tz(timezone);
return m.format(this.getDateTime().getDateTimeFormat()) + ' ' +
m.format('Z z');
},
});
});
+21 -23
View File
@@ -128,7 +128,7 @@ define('crm:views/meeting/detail', ['views/detail', 'lib!moment'], function (Dep
if (!contactIdList.length && !leadIdList.length && !userIdList.length) {
show = false;
}
else if (
/*else if (
!contactIdList.length &&
!leadIdList.length &&
userIdList.length === 1 &&
@@ -136,6 +136,17 @@ define('crm:views/meeting/detail', ['views/detail', 'lib!moment'], function (Dep
this.model.getLinkMultipleColumn('users', 'status', this.getUser().id) === 'Accepted'
) {
show = false;
}*/
}
if (show) {
let dateEnd = this.model.get('dateEnd');
if (
dateEnd &&
this.getDateTime().toMoment(dateEnd).isBefore(moment.now())
) {
show = false;
}
}
@@ -164,30 +175,17 @@ define('crm:views/meeting/detail', ['views/detail', 'lib!moment'], function (Dep
},
actionSendInvitations: function () {
this.confirm({
message: this.translate('sendInvitationsConfirmation', 'messages', 'Meeting'),
})
.then(() => {
this.disableMenuItem('sendInvitations');
this.notify('Sending...');
Espo.Ui.notify(' ... ');
Espo.Ajax
.postRequest(this.model.entityType + '/action/sendInvitations', {
id: this.model.id,
})
.then(result => {
if (result) {
this.notify('Sent', 'success');
} else {
Espo.Ui.warning(this.translate('nothingHasBeenSent', 'messages', 'Meeting'));
}
this.createView('dialog', 'crm:views/meeting/modals/send-invitations', {
model: this.model,
}).then(view => {
Espo.Ui.notify(false);
this.enableMenuItem('sendInvitations');
})
.catch(() => {
this.enableMenuItem('sendInvitations');
});
});
view.render();
this.listenToOnce(view, 'sent', () => this.model.fetch());
});
},
actionSetAcceptanceStatus: function () {
@@ -0,0 +1,198 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* 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 General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('crm:views/meeting/modals/send-invitations', ['views/modal', 'collection'], function (Dep, Collection) {
/**
* @module crm_views/meeting/modals/send-invitations
*/
/**
* @class
* @name Class
* @extends module:views/modal.Class
* @memberOf module:crm_views/meeting/modals/send-invitations
*/
return Dep.extend(/** @lends module:crm_views/meeting/modals/send-invitations.Class# */{
backdrop: true,
templateContent: `
<div class="margin-bottom">
<p>{{message}}</p>
</div>
<div class="list-container">{{{list}}}</div>
`,
data: function () {
return {
message: this.translate('sendInvitationsToSelectedAttendees', 'messages', 'Meeting'),
};
},
setup: function () {
Dep.prototype.setup.call(this);
this.shortcutKeys = {};
this.shortcutKeys['Control+Enter'] = e => {
if (!this.hasAvailableActionItem('send')) {
return;
}
e.preventDefault();
this.actionSend();
};
this.$header = $('<span>').append(
$('<span>')
.text(this.translate(this.model.entityType, 'scopeNames')),
' <span class="chevron-right"></span> ',
$('<span>')
.text(this.model.get('name')),
' <span class="chevron-right"></span> ',
$('<span>')
.text(this.translate('Send Invitations', 'labels', 'Meeting'))
);
this.addButton({
label: 'Send',
name: 'send',
style: 'danger',
disabled: true,
});
this.addButton({
label: 'Cancel',
name: 'cancel',
});
this.collection = new Collection();
this.collection.url = this.model.entityType + `/${this.model.id}/attendees`;
this.wait(
this.collection.fetch()
.then(() => {
Espo.Utils.clone(this.collection.models).forEach(model => {
model.entityType = model.get('_scope');
if (!model.get('emailAddress')) {
this.collection.remove(model.id);
}
});
return this.createView('list', 'views/record/list', {
selector: '.list-container',
collection: this.collection,
rowActionsDisabled: true,
massActionsDisabled: true,
checkAllResultDisabled: true,
selectable: true,
buttonsDisabled: true,
listLayout: [
{
name: 'name',
customLabel: this.translate('name', 'fields'),
notSortable: true,
},
{
name: 'acceptanceStatus',
width: 40,
customLabel: this.translate('acceptanceStatus', 'fields', 'Meeting'),
notSortable: true,
view: 'views/fields/enum',
params: {
options: this.model.getFieldParam('acceptanceStatus', 'options'),
style: this.model.getFieldParam('acceptanceStatus', 'style'),
},
},
],
})
})
.then(view => {
this.collection.models
.filter(model => {
let status = model.get('acceptanceStatus');
return !status || status === 'None';
})
.forEach(model => {
this.getListView().checkRecord(model.id);
});
this.listenTo(view, 'check', () => this.controlSendButton());
this.controlSendButton();
})
);
},
controlSendButton: function () {
this.getListView().checkedList.length ?
this.enableButton('send') :
this.disableButton('send');
},
/**
* @return {module:views/record/list.Class}
*/
getListView: function () {
return this.getView('list');
},
actionSend: function () {
this.disableButton('send');
Espo.Ui.notify(' ... ');
let targets = this.getListView().checkedList.map(id => {
return {
entityType: this.collection.get(id).entityType,
id: id,
};
});
Espo.Ajax
.postRequest(this.model.entityType + '/action/sendInvitations', {
id: this.model.id,
targets: targets,
})
.then(result => {
result ?
Espo.Ui.success(this.translate('Sent')) :
Espo.Ui.warning(this.translate('nothingHasBeenSent', 'messages', 'Meeting'));
this.trigger('sent');
this.close();
})
.catch(() => {
this.enableButton('send');
});
},
});
});
+3 -1
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('collection', [], function () {
define('collection', ['model'], function (Model) {
/**
* On sync with backend.
@@ -204,6 +204,8 @@ define('collection', [], function () {
this.defaultOrderBy = this.orderBy;
this.data = {};
this.model = options.model || Model;
},
/**
+4 -1
View File
@@ -39,7 +39,10 @@ define('multi-collection', ['collection'], function (Collection) {
return Collection.extend(/** @lends module:multi-collection.Class# */{
/**
* @private
* A model seed map.
*
* @public
* @type {Object.<string, module:model.Class>}
*/
seeds: null,
+1 -1
View File
@@ -115,7 +115,7 @@ function (Dep, /** module:ui/multi-select*/MultiSelect, /** module:ui/select*/Se
);
}
this.styleMap = this.model.getFieldParam(this.name, 'style') || {};
this.styleMap = this.params.style || this.model.getFieldParam(this.name, 'style') || {};
this.setupOptions();
+33 -25
View File
@@ -108,7 +108,7 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
/**
* A scope. Set automatically.
*
* @type {string|null}
* @type {?string}
*/
scope: null,
@@ -1913,13 +1913,13 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
this.checkAllResultMassActionList = checkAllResultMassActionList;
let metadataCheckkAllMassActionList = (
let metadataCheckAllMassActionList = (
this.getMetadata().get(['clientDefs', this.scope, 'checkAllResultMassActionList']) || []
).concat(
this.getMetadata().get(['clientDefs', 'Global', 'checkAllResultMassActionList']) || []
);
metadataCheckkAllMassActionList.forEach(item => {
metadataCheckAllMassActionList.forEach(item => {
if (this.collection.url !== this.entityType) {
return;
}
@@ -1941,7 +1941,7 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
});
metadataMassActionList
.concat(metadataCheckkAllMassActionList)
.concat(metadataCheckAllMassActionList)
.forEach(action => {
let defs = this.getMetadata()
.get(['clientDefs', this.scope, 'massActionDefs', action]) || {};
@@ -2216,7 +2216,9 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
_loadListLayout: function (callback) {
this.layoutLoadCallbackList.push(callback);
if (this.layoutIsBeingLoaded) return;
if (this.layoutIsBeingLoaded) {
return;
}
this.layoutIsBeingLoaded = true;
@@ -2257,7 +2259,7 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
return;
}
this._loadListLayout((listLayout) => {
this._loadListLayout(listLayout => {
this.listLayout = listLayout;
let attributeList = this.fetchAttributeListFromLayout();
@@ -2433,6 +2435,16 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
item.options.defs.align = col.align;
}
if (col.options) {
for (let optionName in col.options) {
if (typeof item.options[optionName] !== 'undefined') {
continue;
}
item.options[optionName] = col.options[optionName];
}
}
layout.push(item);
}
if (this.rowActionsView && !this.rowActionsDisabled) {
@@ -2452,20 +2464,17 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
checkRecord: function (id, $target, isSilent) {
$target = $target || this.$el.find('.record-checkbox[data-id="' + id + '"]');
if (!$target.length) {
return;
if ($target.length) {
$target.get(0).checked = true;
$target.closest('tr').addClass('active');
}
$target.get(0).checked = true;
var index = this.checkedList.indexOf(id);
let index = this.checkedList.indexOf(id);
if (index === -1) {
this.checkedList.push(id);
}
$target.closest('tr').addClass('active');
this.handleAfterCheck(isSilent);
},
@@ -2481,18 +2490,15 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
if ($target.get(0)) {
$target.get(0).checked = false;
$target.closest('tr').removeClass('active');
}
var index = this.checkedList.indexOf(id);
let index = this.checkedList.indexOf(id);
if (index !== -1) {
this.checkedList.splice(index, 1);
}
if ($target.get(0)) {
$target.closest('tr').removeClass('active');
}
this.handleAfterCheck(isSilent);
},
@@ -2587,17 +2593,19 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
* @protected
*/
getInternalLayout: function (callback, model) {
if (this.scope === null || this.rowHasOwnLayout) {
if (
(this.scope === null || this.rowHasOwnLayout) &&
!Array.isArray(this.listLayout)
) {
if (!model) {
callback(null);
return;
}
else {
this.getInternalLayoutForModel(callback, model);
return;
}
this.getInternalLayoutForModel(callback, model);
return;
}
if (this._internalLayout !== null) {
@@ -2614,7 +2622,7 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
return;
}
this._loadListLayout((listLayout) => {
this._loadListLayout(listLayout => {
this.listLayout = listLayout;
this._internalLayout = this._convertLayout(listLayout);
@@ -2652,7 +2660,7 @@ function (Dep, MassActionHelper, ExportHelper, RecordModal) {
this.rowList.push(key);
this.getInternalLayout((internalLayout) => {
this.getInternalLayout(internalLayout => {
internalLayout = Espo.Utils.cloneDeep(internalLayout);
this.prepareInternalLayout(internalLayout, model);
+14
View File
@@ -224,6 +224,20 @@ a.text-muted {
}
}
a.text-soft {
&:hover,
&:focus {
color: var(--gray-soft);
}
&:hover {
> .fas,
> .far {
color: var(--gray-soft);
}
}
}
.text-primary {
.text-emphasis-variant-espo(var(--state-primary-text), var(--brand-primary-10));
}