diff --git a/application/Espo/Tools/WorkingTime/CalendarFactory.php b/application/Espo/Tools/WorkingTime/CalendarFactory.php index 8232513c6a..fbcef47704 100644 --- a/application/Espo/Tools/WorkingTime/CalendarFactory.php +++ b/application/Espo/Tools/WorkingTime/CalendarFactory.php @@ -33,15 +33,12 @@ use Espo\Core\Binding\BindingContainerBuilder; use Espo\Core\InjectableFactory; use Espo\Entities\Team; use Espo\Entities\User; +use Espo\Entities\WorkingTimeCalendar; class CalendarFactory { - private InjectableFactory $injectableFactory; - - public function __construct(InjectableFactory $injectableFactory) - { - $this->injectableFactory = $injectableFactory; - } + public function __construct(private InjectableFactory $injectableFactory) + {} public function createGlobal(): GlobalCalendar { @@ -65,4 +62,16 @@ class CalendarFactory return $this->injectableFactory->createWithBinding(TeamCalendar::class, $binding); } + + /** + * @since 8.4.0 + */ + public function create(WorkingTimeCalendar $calendar): Calendar + { + $binding = BindingContainerBuilder::create() + ->bindInstance(WorkingTimeCalendar::class, $calendar) + ->build(); + + return $this->injectableFactory->createWithBinding(SpecificCalendar::class, $binding); + } } diff --git a/application/Espo/Tools/WorkingTime/CalendarUtilityFactory.php b/application/Espo/Tools/WorkingTime/CalendarUtilityFactory.php index b31ecfe379..fb02f7e8f5 100644 --- a/application/Espo/Tools/WorkingTime/CalendarUtilityFactory.php +++ b/application/Espo/Tools/WorkingTime/CalendarUtilityFactory.php @@ -36,16 +36,10 @@ use Espo\Entities\User; class CalendarUtilityFactory { - private InjectableFactory $injectableFactory; - private CalendarFactory $calendarFactory; - public function __construct( - InjectableFactory $injectableFactory, - CalendarFactory $calendarFactory - ) { - $this->injectableFactory = $injectableFactory; - $this->calendarFactory = $calendarFactory; - } + private InjectableFactory $injectableFactory, + private CalendarFactory $calendarFactory + ) {} public function create(Calendar $calendar): CalendarUtility { diff --git a/application/Espo/Tools/WorkingTime/GlobalCalendar.php b/application/Espo/Tools/WorkingTime/GlobalCalendar.php index 2a66437381..fa42139bf8 100644 --- a/application/Espo/Tools/WorkingTime/GlobalCalendar.php +++ b/application/Espo/Tools/WorkingTime/GlobalCalendar.php @@ -29,17 +29,14 @@ namespace Espo\Tools\WorkingTime; +use Espo\Core\Binding\BindingContainerBuilder; +use Espo\Core\InjectableFactory; use Espo\Core\Utils\Config; use Espo\Entities\WorkingTimeCalendar; -use Espo\Entities\WorkingTimeRange; use Espo\ORM\EntityManager; -use Espo\ORM\Query\Part\Condition; -use Espo\ORM\Query\Part\Expression; -use Espo\ORM\Query\Part\Where\OrGroup; use Espo\Tools\WorkingTime\Calendar\WorkingWeekday; use Espo\Tools\WorkingTime\Calendar\WorkingDate; use Espo\Core\Field\Date; -use Espo\Tools\WorkingTime\Util\CalendarUtil; use DateTimeZone; use Exception; @@ -48,30 +45,22 @@ use RuntimeException; class GlobalCalendar implements Calendar { private ?WorkingTimeCalendar $workingTimeCalendar = null; - private ?CalendarUtil $util = null; - - /** @var ?array{WorkingDate[], WorkingDate[]} */ - private ?array $cache = null; - private ?string $cacheKey = null; - private DateTimeZone $timezone; + private ?SpecificCalendar $specificCalendar = null; public function __construct( private EntityManager $entityManager, - private Config $config + private Config $config, + private InjectableFactory $injectableFactory, ) { - try { - $this->timezone = new DateTimeZone($config->get('timeZone')); - } - catch (Exception $e) { - throw new RuntimeException($e->getMessage()); - } - $this->initDefault(); if ($this->workingTimeCalendar) { - $this->util = new CalendarUtil($this->workingTimeCalendar); - - $this->timezone = $this->workingTimeCalendar->getTimeZone() ?? $this->timezone; + $this->specificCalendar = $this->injectableFactory->createWithBinding( + SpecificCalendar::class, + BindingContainerBuilder::create() + ->bindInstance(WorkingTimeCalendar::class, $this->workingTimeCalendar) + ->build() + ); } } @@ -86,14 +75,28 @@ class GlobalCalendar implements Calendar $this->workingTimeCalendar = $this->entityManager->getEntityById(WorkingTimeCalendar::ENTITY_TYPE, $id); } + /** @noinspection PhpUnused */ public function isAvailable(): bool { - return $this->workingTimeCalendar !== null; + if ($this->specificCalendar) { + return $this->specificCalendar->isAvailable(); + } + + return false; } public function getTimezone(): DateTimeZone { - return $this->timezone; + if ($this->specificCalendar) { + return $this->specificCalendar->getTimezone(); + } + + try { + return new DateTimeZone($this->config->get('timeZone')); + } + catch (Exception $e) { + throw new RuntimeException($e->getMessage()); + } } /** @@ -101,11 +104,11 @@ class GlobalCalendar implements Calendar */ public function getWorkingWeekdays(): array { - if ($this->workingTimeCalendar === null) { - return []; + if ($this->specificCalendar) { + return $this->specificCalendar->getWorkingWeekdays(); } - return $this->workingTimeCalendar->getWorkingWeekdays(); + return []; } /** @@ -113,11 +116,11 @@ class GlobalCalendar implements Calendar */ public function getNonWorkingDates(Date $from, Date $to): array { - if ($this->workingTimeCalendar === null) { - return []; + if ($this->specificCalendar) { + return $this->specificCalendar->getNonWorkingDates($from, $to); } - return $this->getDates($from, $to)[0]; + return []; } /** @@ -125,100 +128,10 @@ class GlobalCalendar implements Calendar */ public function getWorkingDates(Date $from, Date $to): array { - if ($this->workingTimeCalendar === null) { - return []; + if ($this->specificCalendar) { + return $this->specificCalendar->getWorkingDates($from, $to); } - return $this->getDates($from, $to)[1]; - } - - /** - * @return array{WorkingDate[], WorkingDate[]} - */ - private function getDates(Date $from, Date $to): array - { - $cacheKey = $from->toString() . '-' . $to->toString(); - - if ($this->cacheKey === $cacheKey) { - assert($this->cache !== null); - - return $this->cache; - } - - $notWorkingList = []; - $workingList = []; - - $list = $this->fetchRanges($from, $to); - - foreach ($list as $range) { - $dates = $this->rangeToDates($range); - - if ($range->getType() === WorkingTimeRange::TYPE_NON_WORKING) { - $notWorkingList = array_merge($notWorkingList, $dates); - - continue; - } - - $workingList = array_merge($workingList, $dates); - } - - $this->cacheKey = $cacheKey; - $this->cache = [$notWorkingList, $workingList]; - - return $this->cache; - } - - /** - * @param WorkingTimeRange $range - * @return WorkingDate[] - */ - private function rangeToDates(WorkingTimeRange $range): array - { - if (!$this->util) { - return []; - } - - return $this->util->rangeToDates($range); - } - - /** - * @return WorkingTimeRange[] - */ - private function fetchRanges(Date $from, Date $to): array - { - if ($this->workingTimeCalendar === null) { - return []; - } - - $list = []; - - $collection = $this->entityManager - ->getRDBRepositoryByClass(WorkingTimeRange::class) - ->leftJoin('calendars') - ->where( - Condition::equal( - Expression::column('calendars.id'), - $this->workingTimeCalendar->getId() - ) - ) - ->where( - OrGroup::create( - Condition::greaterOrEqual( - Expression::column('dateEnd'), - $from->toString() - ), - Condition::lessOrEqual( - Expression::column('dateStart'), - $to->toString() - ), - ) - ) - ->find(); - - foreach ($collection as $entity) { - $list[] = $entity; - } - - return $list; + return []; } } diff --git a/application/Espo/Tools/WorkingTime/SpecificCalendar.php b/application/Espo/Tools/WorkingTime/SpecificCalendar.php new file mode 100644 index 0000000000..6d2a97344c --- /dev/null +++ b/application/Espo/Tools/WorkingTime/SpecificCalendar.php @@ -0,0 +1,197 @@ +. + * + * 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 Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Tools\WorkingTime; + +use Espo\Core\Utils\Config; +use Espo\Entities\WorkingTimeCalendar; +use Espo\Entities\WorkingTimeRange; +use Espo\ORM\EntityManager; +use Espo\ORM\Query\Part\Condition; +use Espo\ORM\Query\Part\Expression; +use Espo\ORM\Query\Part\Where\OrGroup; +use Espo\Tools\WorkingTime\Calendar\WorkingWeekday; +use Espo\Tools\WorkingTime\Calendar\WorkingDate; +use Espo\Core\Field\Date; +use Espo\Tools\WorkingTime\Util\CalendarUtil; + +use DateTimeZone; +use Exception; +use RuntimeException; + +/** + * @since 8.4.0 + */ +class SpecificCalendar implements Calendar +{ + private ?CalendarUtil $util = null; + + /** @var ?array{WorkingDate[], WorkingDate[]} */ + private ?array $cache = null; + private ?string $cacheKey = null; + private DateTimeZone $timezone; + + public function __construct( + private EntityManager $entityManager, + Config $config, + private WorkingTimeCalendar $workingTimeCalendar, + ) { + try { + $this->timezone = new DateTimeZone($config->get('timeZone')); + } + catch (Exception $e) { + throw new RuntimeException($e->getMessage()); + } + + $this->util = new CalendarUtil($this->workingTimeCalendar); + + $this->timezone = $this->workingTimeCalendar->getTimeZone() ?? $this->timezone; + } + + /** @noinspection PhpUnused */ + public function isAvailable(): bool + { + return $this->workingTimeCalendar !== null; + } + + public function getTimezone(): DateTimeZone + { + return $this->timezone; + } + + /** + * @return WorkingWeekday[] + */ + public function getWorkingWeekdays(): array + { + return $this->workingTimeCalendar->getWorkingWeekdays(); + } + + /** + * @return WorkingDate[] + */ + public function getNonWorkingDates(Date $from, Date $to): array + { + return $this->getDates($from, $to)[0]; + } + + /** + * @return WorkingDate[] + */ + public function getWorkingDates(Date $from, Date $to): array + { + return $this->getDates($from, $to)[1]; + } + + /** + * @return array{WorkingDate[], WorkingDate[]} + */ + private function getDates(Date $from, Date $to): array + { + $cacheKey = $from->toString() . '-' . $to->toString(); + + if ($this->cacheKey === $cacheKey) { + assert($this->cache !== null); + + return $this->cache; + } + + $notWorkingList = []; + $workingList = []; + + $list = $this->fetchRanges($from, $to); + + foreach ($list as $range) { + $dates = $this->rangeToDates($range); + + if ($range->getType() === WorkingTimeRange::TYPE_NON_WORKING) { + $notWorkingList = array_merge($notWorkingList, $dates); + + continue; + } + + $workingList = array_merge($workingList, $dates); + } + + $this->cacheKey = $cacheKey; + $this->cache = [$notWorkingList, $workingList]; + + return $this->cache; + } + + /** + * @param WorkingTimeRange $range + * @return WorkingDate[] + */ + private function rangeToDates(WorkingTimeRange $range): array + { + if (!$this->util) { + return []; + } + + return $this->util->rangeToDates($range); + } + + /** + * @return WorkingTimeRange[] + */ + private function fetchRanges(Date $from, Date $to): array + { + $list = []; + + $collection = $this->entityManager + ->getRDBRepositoryByClass(WorkingTimeRange::class) + ->leftJoin('calendars') + ->where( + Condition::equal( + Expression::column('calendars.id'), + $this->workingTimeCalendar->getId() + ) + ) + ->where( + OrGroup::create( + Condition::greaterOrEqual( + Expression::column('dateEnd'), + $from->toString() + ), + Condition::lessOrEqual( + Expression::column('dateStart'), + $to->toString() + ), + ) + ) + ->find(); + + foreach ($collection as $entity) { + $list[] = $entity; + } + + return $list; + } +} diff --git a/tests/integration/Espo/Tools/WorkingTime/UtilityTest.php b/tests/integration/Espo/Tools/WorkingTime/UtilityTest.php index 6db91737f9..88d8e53e07 100644 --- a/tests/integration/Espo/Tools/WorkingTime/UtilityTest.php +++ b/tests/integration/Espo/Tools/WorkingTime/UtilityTest.php @@ -30,13 +30,14 @@ namespace tests\integration\Espo\Tools\WorkingTime; use Espo\Core\Field\DateTime; +use Espo\Core\Utils\Config\ConfigWriter; use Espo\Entities\WorkingTimeCalendar; use Espo\Tools\WorkingTime\CalendarUtilityFactory; use tests\integration\Core\BaseTestCase; class UtilityTest extends BaseTestCase { - public function testUtility(): void + public function testUtilityUser(): void { $em = $this->getEntityManager(); @@ -55,4 +56,24 @@ class UtilityTest extends BaseTestCase $this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-25 00:00'))); $this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-26 00:00'))); } + + public function testUtilityGlobal(): void + { + $em = $this->getEntityManager(); + + $calendar = $em->createEntity(WorkingTimeCalendar::ENTITY_TYPE); + + $configWriter = $this->getInjectableFactory()->create(ConfigWriter::class); + $configWriter->set('workingTimeCalendarId', $calendar->getId()); + $configWriter->save();; + + $utility = $this->getInjectableFactory() + ->create(CalendarUtilityFactory::class) + ->createGlobal(); + + $this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-23 00:00'))); + $this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-24 00:00'))); + $this->assertFalse($utility->isWorkingDay(DateTime::fromString('2024-02-25 00:00'))); + $this->assertTrue($utility->isWorkingDay(DateTime::fromString('2024-02-26 00:00'))); + } }