preserve duration

This commit is contained in:
Yuri Kuznetsov
2022-05-12 13:39:53 +03:00
parent 487da37bea
commit da032f3518
15 changed files with 302 additions and 47 deletions
@@ -0,0 +1,123 @@
<?php
/************************************************************************
* 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.
************************************************************************/
namespace Espo\Classes\RecordHooks\Event;
use Espo\Core\Record\Hook\UpdateHook;
use Espo\Core\Record\UpdateParams;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Core\Field\DateTime;
use Espo\Core\Field\Date;
use Espo\ORM\Entity;
use Espo\ORM\Defs as OrmDefs;
/**
* @implements UpdateHook<CoreEntity>
*/
class BeforeUpdatePreserveDuration implements UpdateHook
{
private OrmDefs $ormDefs;
private const DATE_START = 'dateStart';
private const DATE_END = 'dateEnd';
public function __construct(OrmDefs $ormDefs)
{
$this->ormDefs = $ormDefs;
}
public function process(Entity $entity, UpdateParams $params): void
{
/** @var CoreEntity $entity */
if (!$entity->isAttributeChanged('dateStart')) {
return;
}
if ($entity->isAttributeWritten('dateEnd')) {
return;
}
$preserveDurationDisabled = $this->ormDefs
->getEntity($entity->getEntityType())
->getField('dateEnd')
->getParam('preserveDurationDisabled');
if ($preserveDurationDisabled) {
return;
}
$this->processDateTime($entity);
$this->processDate($entity);
}
private function processDateTime(Entity $entity): void
{
$dateStartFetchedString = $entity->getFetched('dateStart');
$dateStartString = $entity->get('dateStart');
$dateEndString = $entity->get('dateEnd');
if (!$dateStartFetchedString || !$dateStartString || !$dateEndString) {
return;
}
$dateStartFetched = DateTime::fromString($dateStartFetchedString);
$dateStart = DateTime::fromString($dateStartString);
$dateEnd = DateTime::fromString($dateEndString);
$diff = $dateStartFetched->diff($dateEnd);
$dateEndModified = $dateStart->add($diff);
$entity->set('dateEnd', $dateEndModified->getString());
}
private function processDate(Entity $entity): void
{
$dateStartFetchedString = $entity->getFetched('dateStartDate');
$dateStartString = $entity->get('dateStartDate');
$dateEndString = $entity->get('dateEndDate');
if (!$dateStartFetchedString || !$dateStartString || !$dateEndString) {
return;
}
$dateStartFetched = Date::fromString($dateStartFetchedString);
$dateStart = Date::fromString($dateStartString);
$dateEnd = Date::fromString($dateEndString);
$diff = $dateStartFetched->diff($dateEnd);
$dateEndModified = $dateStart->add($diff);
$entity->set('dateEndDate', $dateEndModified->getString());
}
}
@@ -40,7 +40,6 @@ use Espo\Core\{
use DateTime;
use DateTimeZone;
use Exception;
/**
* @extends Database<\Espo\Core\ORM\Entity>
@@ -58,11 +57,6 @@ class Event extends Database implements
*/
protected $reminderSkippingStatusList = ['Held', 'Not Held'];
/**
* @var bool
*/
protected $preserveDuration = true;
/**
* @param array<string,mixed> $options
* @return void
@@ -109,31 +103,6 @@ class Event extends Database implements
}
}
if (
!$entity->isNew() &&
$this->preserveDuration &&
$entity->isAttributeChanged('dateStart') &&
$entity->get('dateStart') &&
!$entity->isAttributeChanged('dateEnd')
) {
$dateEndPrevious = $entity->getFetched('dateEnd');
$dateStartPrevious = $entity->getFetched('dateStart');
if ($dateStartPrevious && $dateEndPrevious) {
$dtStart = new DateTime($dateStartPrevious);
$dtEnd = new DateTime($dateEndPrevious);
$dt = new DateTime($entity->get('dateStart'));
$duration = ($dtEnd->getTimestamp() - $dtStart->getTimestamp());
$dt->modify('+' . $duration . ' seconds');
$dateEnd = $dt->format('Y-m-d H:i:s');
$entity->set('dateEnd', $dateEnd);
}
}
parent::beforeSave($entity, $options);
}
@@ -40,6 +40,10 @@
"name":"todays"
}
],
"forcePatchAttributeDependencyMap": {
"dateEnd": ["dateStart"],
"dateEndDate": ["dateStartDate"]
},
"dynamicLogic":{
"fields":{
"duration": {
@@ -4,5 +4,9 @@
],
"saverClassNameList": [
"Espo\\Core\\FieldProcessing\\Reminder\\Saver"
],
"beforeUpdateHookClassNameList": [
"__APPEND__",
"Espo\\Classes\\RecordHooks\\Event\\BeforeUpdatePreserveDuration"
]
}
@@ -37,8 +37,6 @@ class Task extends EventRepository
{
protected $reminderSkippingStatusList = ['Completed', 'Canceled'];
protected $preserveDuration = false;
protected function beforeSave(Entity $entity, array $options = [])
{
if ($entity->isAttributeChanged('status')) {
@@ -91,6 +91,10 @@
"activitiesCreate": true,
"historyCreate": true
},
"forcePatchAttributeDependencyMap": {
"dateEnd": ["dateStart"],
"dateEndDate": ["dateStartDate"]
},
"dynamicLogic":{
"fields":{
"reminders": {
@@ -96,6 +96,10 @@
"activitiesCreate": true,
"historyCreate": true
},
"forcePatchAttributeDependencyMap": {
"dateEnd": ["dateStart"],
"dateEndDate": ["dateStartDate"]
},
"dynamicLogic":{
"fields":{
"reminders": {
@@ -5,5 +5,9 @@
],
"saverClassNameList": [
"Espo\\Core\\FieldProcessing\\Reminder\\Saver"
],
"beforeUpdateHookClassNameList": [
"__APPEND__",
"Espo\\Classes\\RecordHooks\\Event\\BeforeUpdatePreserveDuration"
]
}
@@ -5,5 +5,9 @@
"saverClassNameList": [
"Espo\\Core\\FieldProcessing\\Reminder\\Saver",
"Espo\\Modules\\Crm\\Classes\\FieldProcessing\\Meeting\\SourceEmailSaver"
],
"beforeUpdateHookClassNameList": [
"__APPEND__",
"Espo\\Classes\\RecordHooks\\Event\\BeforeUpdatePreserveDuration"
]
}
+17
View File
@@ -100,6 +100,11 @@ class BaseEntity implements Entity
private ?ValueAccessor $valueAccessor = null;
/**
* @var array<string,bool>
*/
private array $writtenMap = [];
/**
* @param array{
* attributes?: array<string,array<string,mixed>>,
@@ -490,6 +495,8 @@ class BaseEntity implements Entity
}
$this->setInContainer($attribute, $preparedValue);
$this->writtenMap[$attribute] = true;
}
/**
@@ -897,6 +904,14 @@ class BaseEntity implements Entity
);
}
/**
* Whether an attribute was written (since syncing with DB) regardless being changed.
*/
public function isAttributeWritten(string $name): bool
{
return $this->writtenMap[$name] ?? false;
}
/**
* @param mixed $v1
* @param mixed $v2
@@ -1028,6 +1043,8 @@ class BaseEntity implements Entity
foreach ($this->fetchedValuesContainer as $attribute => $value) {
$this->setFetched($attribute, $value);
}
$this->writtenMap = [];
}
/**
+35 -13
View File
@@ -658,36 +658,58 @@ define(
var headers = options.headers || {};
var model = this.model;
this.lastSaveCancelReason = null;
this.beforeBeforeSave();
var data = this.fetch();
var model = this.model;
let fetchedAttributes = this.fetch();
var initialAttributes = this.attributes;
var beforeSaveAttributes = this.model.getClonedAttributes();
data = _.extend(Espo.Utils.cloneDeep(beforeSaveAttributes), data);
let attributes = _.extend(
Espo.Utils.cloneDeep(beforeSaveAttributes),
fetchedAttributes
);
var setAttributes = false;
let setAttributes = {};
if (model.isNew()) {
setAttributes = data;
setAttributes = attributes;
}
else {
for (var name in data) {
if (_.isEqual(initialAttributes[name], data[name])) {
if (!model.isNew()) {
for (let attr in attributes) {
if (_.isEqual(initialAttributes[attr], attributes[attr])) {
continue;
}
(setAttributes || (setAttributes = {}))[name] = data[name];
setAttributes[attr] = attributes[attr];
}
let forcePatchAttributeDependencyMap = this.forcePatchAttributeDependencyMap || {};
for (let attr in forcePatchAttributeDependencyMap) {
if (attr in setAttributes) {
continue;
}
if (!(attr in fetchedAttributes)) {
continue;
}
let depAttributeList = forcePatchAttributeDependencyMap[attr];
let treatAsChanged = !! depAttributeList.find(attr => attr in setAttributes);
if (treatAsChanged) {
setAttributes[attr] = attributes[attr];
}
}
}
if (!setAttributes) {
if (Object.keys(setAttributes).length === 0) {
if (!options.skipNotModifiedWarning) {
this.afterNotModified();
}
+3
View File
@@ -1236,6 +1236,9 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.getHelper().processSetupHandlers(this, this.setupHandlerType);
this.initInlideEditDynamicWithLogicInteroperability();
this.forcePatchAttributeDependencyMap = this.getMetadata()
.get(['clientDefs', this.scope, 'forcePatchAttributeDependencyMap']) || {};
},
setupBeforeFinal: function () {
+20 -1
View File
@@ -75,7 +75,7 @@ class EntityTest extends \PHPUnit\Framework\TestCase
}
/**
* @return \Espo\ORM\Entity
* @return \Espo\ORM\BaseEntity
*/
protected function createEntity(string $entityType, ?string $className = null)
{
@@ -380,4 +380,23 @@ class EntityTest extends \PHPUnit\Framework\TestCase
$entity->getId();
}
public function testIsAttributeWritten1(): void
{
$entity = $this->createEntity('Test');
$entity->set([
'int' => '1',
'object' => (object) [],
]);
$entity->setAsFetched();
$this->assertEquals(false, $entity->isAttributeWritten('int'));
$entity->set('int', '1');
$this->assertEquals(true, $entity->isAttributeWritten('int'));
$this->assertEquals(false, $entity->isAttributeWritten('object'));
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"manifest": {
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 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.
************************************************************************/
use Espo\Core\Container;
use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\File\Manager as FileManager;
class AfterUpgrade
{
public function run(Container $container): void
{
$this->updateEventMetadata($container->get('metadata'), $container->get('fileManager'));
}
private function updateEventMetadata(Metadata $metadata, FileManager $fileManager): void
{
$defs = $metadata->get(['scopes']);
$toSave = false;
foreach ($defs as $entityType => $item) {
$isCustom = $item['isCustom'] ?? false;
$type = $item['type'] ?? false;
if (!$isCustom || $type !== 'Event') {
continue;
}
$toSave = true;
$metadata->set('recordDefs', $entityType, [
'beforeUpdateHookClassNameList' => [
"__APPEND__",
"Espo\\Classes\\RecordHooks\\Event\\BeforeUpdatePreserveDuration"
]
]);
$metadata->set('clientDefs', $entityType, [
'forcePatchAttributeDependencyMap' => [
"dateEnd" => ["dateStart"],
"dateEndDate" => ["dateStartDate"]
]
]);
}
if ($toSave) {
$metadata->save();
}
}
}