diff --git a/application/Espo/Core/Select/Where/ConverterFactory.php b/application/Espo/Core/Select/Where/ConverterFactory.php index 70216d9641..548391b74f 100644 --- a/application/Espo/Core/Select/Where/ConverterFactory.php +++ b/application/Espo/Core/Select/Where/ConverterFactory.php @@ -34,13 +34,14 @@ use Espo\Core\Binding\BindingContainer; use Espo\Core\Binding\BindingData; use Espo\Core\InjectableFactory; use Espo\Core\Utils\Metadata; - use Espo\Entities\User; class ConverterFactory { - public function __construct(private InjectableFactory $injectableFactory, private Metadata $metadata) - {} + public function __construct( + private InjectableFactory $injectableFactory, + private Metadata $metadata + ) {} public function create(string $entityType, User $user): Converter { @@ -72,16 +73,12 @@ class ConverterFactory $bindingData = new BindingData(); $binder = new Binder($bindingData); - - $binder - ->bindInstance(User::class, $user); - + $binder->bindInstance(User::class, $user); $binder ->for($className) ->bindValue('$entityType', $entityType); - $binder - ->for(DateTimeItemTransformer::class) + ->for(DefaultDateTimeItemTransformer::class) ->bindValue('$entityType', $entityType); $bindingContainer = new BindingContainer($bindingData); @@ -156,6 +153,6 @@ class ConverterFactory return $className; } - return DateTimeItemTransformer::class; + return DefaultDateTimeItemTransformer::class; } } diff --git a/application/Espo/Core/Select/Where/DateTimeItemTransformer.php b/application/Espo/Core/Select/Where/DateTimeItemTransformer.php index 1310acfb0c..6436b36c77 100644 --- a/application/Espo/Core/Select/Where/DateTimeItemTransformer.php +++ b/application/Espo/Core/Select/Where/DateTimeItemTransformer.php @@ -30,502 +30,17 @@ namespace Espo\Core\Select\Where; use Espo\Core\Exceptions\BadRequest; -use Espo\Core\Utils\DateTime as DateTimeUtil; -use Espo\Core\Utils\Config; -use Espo\Core\Select\Where\Item\Type; -use Espo\Entities\User; - -use DateTime; -use DateTimeZone; -use DateInterval; -use Exception; -use RuntimeException; /** - * Transforms date-time where item. Applies timezone. + * Transforms date-time where item. Applies timezone. * + * Implementing a custom transformer should not be considered future-proof. + * + * @internal */ -class DateTimeItemTransformer +interface DateTimeItemTransformer { - public function __construct(protected User $user, private Config $config) - {} - /** * @throws BadRequest */ - public function transform(Item $item): Item - { - $format = DateTimeUtil::SYSTEM_DATE_TIME_FORMAT; - - $type = $item->getType(); - $value = $item->getValue(); - $attribute = $item->getAttribute(); - - $data = $item->getData(); - - if (!$data instanceof Item\Data\DateTime) { - throw new BadRequest("Bad where item."); - } - - $timeZone = $data->getTimeZone() ?? 'UTC'; - - if (!$attribute) { - throw new BadRequest("Bad datetime where item. Empty 'attribute'."); - } - - if (!$type) { - throw new BadRequest("Bad datetime where item. Empty 'type'."); - } - - if ( - empty($value) && - in_array( - $type, - [ - Type::ON, - Type::BEFORE, - Type::AFTER, - ] - ) - ) { - throw new BadRequest("Bad where item. Empty value."); - } - - $where = [ - 'attribute' => $attribute, - ]; - - try { - $dt = new DateTime('now', new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad timezone"); - } - - switch ($type) { - case Type::TODAY: - $where['type'] = Type::BETWEEN; - - $dt->setTime(0, 0); - - $dtTo = clone $dt; - $dtTo->modify('+1 day -1 second'); - $dt->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $from = $dt->format($format); - $to = $dtTo->format($format); - - $where['value'] = [$from, $to]; - - break; - - case Type::PAST: - $where['type'] = Type::BEFORE; - - $dt->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = $dt->format($format); - - break; - - case Type::FUTURE: - $where['type'] = Type::AFTER; - - $dt->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = $dt->format($format); - - break; - - case 'lastSevenDays': - $where['type'] = Type::BETWEEN; - - $dtFrom = clone $dt; - - $dt->setTimezone(new DateTimeZone('UTC')); - $to = $dt->format($format); - - $dtFrom->modify('-7 day'); - $dtFrom->setTime(0, 0); - $dtFrom->setTimezone(new DateTimeZone('UTC')); - - $from = $dtFrom->format($format); - - $where['value'] = [$from, $to]; - - break; - - case Type::LAST_X_DAYS: - $where['type'] = Type::BETWEEN; - - $dtFrom = clone $dt; - - $dt->setTimezone(new DateTimeZone('UTC')); - - $to = $dt->format($format); - - $number = strval(intval($value)); - - $dtFrom->modify('-'.$number.' day'); - $dtFrom->setTime(0, 0); - $dtFrom->setTimezone(new DateTimeZone('UTC')); - - $from = $dtFrom->format($format); - - $where['value'] = [$from, $to]; - - break; - - case Type::NEXT_X_DAYS: - $where['type'] = Type::BETWEEN; - - $dtTo = clone $dt; - - $dt->setTimezone(new DateTimeZone('UTC')); - - $from = $dt->format($format); - - $number = strval(intval($value)); - - $dtTo->modify('+'.$number.' day'); - $dtTo->setTime(24, 59, 59); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $to = $dtTo->format($format); - - $where['value'] = [$from, $to]; - - break; - - case Type::OLDER_THAN_X_DAYS: - $where['type'] = Type::BEFORE; - - $number = strval(intval($value)); - - $dt->modify('-'.$number.' day'); - $dt->setTime(0, 0); - $dt->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = $dt->format($format); - - break; - - case Type::AFTER_X_DAYS: - $where['type'] = Type::AFTER; - - $number = strval(intval($value)); - - $dt->modify('+'.$number.' day'); - $dt->setTime(0, 0); - $dt->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = $dt->format($format); - - break; - - case Type::ON: - $where['type'] = Type::BETWEEN; - - try { - $dt = new DateTime($value, new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad date value or timezone."); - } - - $dtTo = clone $dt; - - if (strlen($value) <= 10) { - $dtTo->modify('+1 day -1 second'); - } - - $dt->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $from = $dt->format($format); - $to = $dtTo->format($format); - - $where['value'] = [$from, $to]; - - break; - - case Type::BEFORE: - $where['type'] = Type::BEFORE; - - try { - $dt = new DateTime($value, new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad date value or timezone."); - } - - $dt->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = $dt->format($format); - - break; - - case Type::AFTER: - $where['type'] = Type::AFTER; - - try { - $dt = new DateTime($value, new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad date value or timezone."); - } - - if (strlen($value) <= 10) { - $dt->modify('+1 day -1 second'); - } - - $dt->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = $dt->format($format); - - break; - - case Type::BETWEEN: - $where['type'] = Type::BETWEEN; - - if (!is_array($value) || count($value) < 2) { - throw new BadRequest("Bad where item. Bad value."); - } - - try { - $dt = new DateTime($value[0], new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad date value or timezone."); - } - - $dt->setTimezone(new DateTimeZone('UTC')); - - $from = $dt->format($format); - - try { - $dt = new DateTime($value[1], new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad date value or timezone."); - } - - $dt->setTimezone(new DateTimeZone('UTC')); - - if (strlen($value[1]) <= 10) { - $dt->modify('+1 day -1 second'); - } - - $to = $dt->format($format); - - $where['value'] = [$from, $to]; - - break; - - case Type::CURRENT_MONTH: - case Type::LAST_MONTH: - case Type::NEXT_MONTH: - $where['type'] = Type::BETWEEN; - - $dtFrom = $dt->modify('first day of this month')->setTime(0, 0); - - if ($type == Type::LAST_MONTH) { - $dtFrom->modify('-1 month'); - } - else if ($type == Type::NEXT_MONTH) { - $dtFrom->modify('+1 month'); - } - - $dtTo = clone $dtFrom; - $dtTo->modify('+1 month'); - - $dtFrom->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = [$dtFrom->format($format), $dtTo->format($format)]; - - break; - - case Type::CURRENT_QUARTER: - case Type::LAST_QUARTER: - $where['type'] = Type::BETWEEN; - - try { - $dt = new DateTime('now', new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad timezone."); - } - - $quarter = ceil($dt->format('m') / 3); - - $dtFrom = clone $dt; - $dtFrom->modify('first day of January this year')->setTime(0, 0); - - if ($type === Type::LAST_QUARTER) { - $quarter--; - - if ($quarter == 0) { - $quarter = 4; - $dtFrom->modify('-1 year'); - } - } - - try { - $dtFrom->add(new DateInterval('P' . (($quarter - 1) * 3) . 'M')); - } - catch (Exception) { - throw new RuntimeException(); - } - - $dtTo = clone $dtFrom; - $dtTo->add(new DateInterval('P3M')); - $dtFrom->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = [ - $dtFrom->format($format), - $dtTo->format($format), - ]; - - break; - - case Type::CURRENT_YEAR: - case Type::LAST_YEAR: - $where['type'] = Type::BETWEEN; - - try { - $dtFrom = new DateTime('now', new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad timezone."); - } - - $dtFrom->modify('first day of January this year')->setTime(0, 0); - - if ($type == Type::LAST_YEAR) { - $dtFrom->modify('-1 year'); - } - - $dtTo = clone $dtFrom; - $dtTo = $dtTo->modify('+1 year'); - $dtFrom->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = [ - $dtFrom->format($format), - $dtTo->format($format), - ]; - - break; - - case Type::CURRENT_FISCAL_YEAR: - case Type::LAST_FISCAL_YEAR: - $where['type'] = Type::BETWEEN; - - try { - $dtToday = new DateTime('now', new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad timezone."); - } - - $dt = clone $dtToday; - $fiscalYearShift = $this->config->get('fiscalYearShift', 0); - - $dt - ->modify('first day of January this year') - ->modify('+' . $fiscalYearShift . ' months') - ->setTime(0, 0); - - if (intval($dtToday->format('m')) < $fiscalYearShift + 1) { - $dt->modify('-1 year'); - } - - if ($type === Type::LAST_FISCAL_YEAR) { - $dt->modify('-1 year'); - } - - $dtFrom = clone $dt; - $dtTo = clone $dt; - $dtTo = $dtTo->modify('+1 year'); - - $dtFrom->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = [ - $dtFrom->format($format), - $dtTo->format($format), - ]; - - break; - - case Type::CURRENT_FISCAL_QUARTER: - case Type::LAST_FISCAL_QUARTER: - $where['type'] = Type::BETWEEN; - - try { - $dtToday = new DateTime('now', new DateTimeZone($timeZone)); - } - catch (Exception) { - throw new BadRequest("Bad timezone."); - } - - $dt = clone $dtToday; - - $fiscalYearShift = $this->config->get('fiscalYearShift', 0); - - $dt - ->modify('first day of January this year') - ->modify('+' . $fiscalYearShift . ' months') - ->setTime(0, 0); - - $month = intval($dtToday->format('m')); - - $quarterShift = floor(($month - $fiscalYearShift - 1) / 3); - - if ($quarterShift) { - if ($quarterShift >= 0) { - try { - $dt->add(new DateInterval('P' . ($quarterShift * 3) . 'M')); - } - catch (Exception) { - throw new RuntimeException(); - } - } else { - $quarterShift *= -1; - - try { - $dt->sub(new DateInterval('P' . ($quarterShift * 3) . 'M')); - } - catch (Exception) { - throw new RuntimeException(); - } - } - } - - if ($type === Type::LAST_FISCAL_QUARTER) { - $dt->modify('-3 months'); - } - - $dtFrom = clone $dt; - $dtTo = clone $dt; - $dtTo = $dtTo->modify('+3 months'); - - $dtFrom->setTimezone(new DateTimeZone('UTC')); - $dtTo->setTimezone(new DateTimeZone('UTC')); - - $where['value'] = [ - $dtFrom->format($format), - $dtTo->format($format), - ]; - - break; - - default: - $where['type'] = $type; - } - - return Item::fromRaw($where); - } + public function transform(Item $item): Item; } diff --git a/application/Espo/Core/Select/Where/DefaultDateTimeItemTransformer.php b/application/Espo/Core/Select/Where/DefaultDateTimeItemTransformer.php new file mode 100644 index 0000000000..ab8c5298c5 --- /dev/null +++ b/application/Espo/Core/Select/Where/DefaultDateTimeItemTransformer.php @@ -0,0 +1,527 @@ +. + * + * 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\Core\Select\Where; + +use Espo\Core\Exceptions\BadRequest; +use Espo\Core\Utils\DateTime as DateTimeUtil; +use Espo\Core\Utils\Config; +use Espo\Core\Select\Where\Item\Type; + +use DateTime; +use DateTimeZone; +use DateInterval; +use Exception; +use RuntimeException; + +class DefaultDateTimeItemTransformer implements DateTimeItemTransformer +{ + public function __construct(private Config $config) + {} + + /** + * @throws BadRequest + */ + public function transform(Item $item): Item + { + $format = DateTimeUtil::SYSTEM_DATE_TIME_FORMAT; + + $type = $item->getType(); + $value = $item->getValue(); + $attribute = $item->getAttribute(); + + $data = $item->getData(); + + if (!$data instanceof Item\Data\DateTime) { + throw new BadRequest("Bad where item."); + } + + $timeZone = $data->getTimeZone() ?? 'UTC'; + + if (!$attribute) { + throw new BadRequest("Bad datetime where item. Empty 'attribute'."); + } + + if (!$type) { + throw new BadRequest("Bad datetime where item. Empty 'type'."); + } + + if ( + empty($value) && + in_array( + $type, + [ + Type::ON, + Type::BEFORE, + Type::AFTER, + ] + ) + ) { + throw new BadRequest("Bad where item. Empty value."); + } + + $where = [ + 'attribute' => $attribute, + ]; + + try { + $dt = new DateTime('now', new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad timezone"); + } + + switch ($type) { + case Type::TODAY: + $where['type'] = Type::BETWEEN; + + $dt->setTime(0, 0); + + $dtTo = clone $dt; + $dtTo->modify('+1 day -1 second'); + $dt->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $from = $dt->format($format); + $to = $dtTo->format($format); + + $where['value'] = [$from, $to]; + + break; + + case Type::PAST: + $where['type'] = Type::BEFORE; + + $dt->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = $dt->format($format); + + break; + + case Type::FUTURE: + $where['type'] = Type::AFTER; + + $dt->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = $dt->format($format); + + break; + + case 'lastSevenDays': + $where['type'] = Type::BETWEEN; + + $dtFrom = clone $dt; + + $dt->setTimezone(new DateTimeZone('UTC')); + $to = $dt->format($format); + + $dtFrom->modify('-7 day'); + $dtFrom->setTime(0, 0); + $dtFrom->setTimezone(new DateTimeZone('UTC')); + + $from = $dtFrom->format($format); + + $where['value'] = [$from, $to]; + + break; + + case Type::LAST_X_DAYS: + $where['type'] = Type::BETWEEN; + + $dtFrom = clone $dt; + + $dt->setTimezone(new DateTimeZone('UTC')); + + $to = $dt->format($format); + + $number = strval(intval($value)); + + $dtFrom->modify('-'.$number.' day'); + $dtFrom->setTime(0, 0); + $dtFrom->setTimezone(new DateTimeZone('UTC')); + + $from = $dtFrom->format($format); + + $where['value'] = [$from, $to]; + + break; + + case Type::NEXT_X_DAYS: + $where['type'] = Type::BETWEEN; + + $dtTo = clone $dt; + + $dt->setTimezone(new DateTimeZone('UTC')); + + $from = $dt->format($format); + + $number = strval(intval($value)); + + $dtTo->modify('+'.$number.' day'); + $dtTo->setTime(24, 59, 59); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $to = $dtTo->format($format); + + $where['value'] = [$from, $to]; + + break; + + case Type::OLDER_THAN_X_DAYS: + $where['type'] = Type::BEFORE; + + $number = strval(intval($value)); + + $dt->modify('-'.$number.' day'); + $dt->setTime(0, 0); + $dt->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = $dt->format($format); + + break; + + case Type::AFTER_X_DAYS: + $where['type'] = Type::AFTER; + + $number = strval(intval($value)); + + $dt->modify('+'.$number.' day'); + $dt->setTime(0, 0); + $dt->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = $dt->format($format); + + break; + + case Type::ON: + $where['type'] = Type::BETWEEN; + + try { + $dt = new DateTime($value, new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad date value or timezone."); + } + + $dtTo = clone $dt; + + if (strlen($value) <= 10) { + $dtTo->modify('+1 day -1 second'); + } + + $dt->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $from = $dt->format($format); + $to = $dtTo->format($format); + + $where['value'] = [$from, $to]; + + break; + + case Type::BEFORE: + $where['type'] = Type::BEFORE; + + try { + $dt = new DateTime($value, new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad date value or timezone."); + } + + $dt->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = $dt->format($format); + + break; + + case Type::AFTER: + $where['type'] = Type::AFTER; + + try { + $dt = new DateTime($value, new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad date value or timezone."); + } + + if (strlen($value) <= 10) { + $dt->modify('+1 day -1 second'); + } + + $dt->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = $dt->format($format); + + break; + + case Type::BETWEEN: + $where['type'] = Type::BETWEEN; + + if (!is_array($value) || count($value) < 2) { + throw new BadRequest("Bad where item. Bad value."); + } + + try { + $dt = new DateTime($value[0], new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad date value or timezone."); + } + + $dt->setTimezone(new DateTimeZone('UTC')); + + $from = $dt->format($format); + + try { + $dt = new DateTime($value[1], new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad date value or timezone."); + } + + $dt->setTimezone(new DateTimeZone('UTC')); + + if (strlen($value[1]) <= 10) { + $dt->modify('+1 day -1 second'); + } + + $to = $dt->format($format); + + $where['value'] = [$from, $to]; + + break; + + case Type::CURRENT_MONTH: + case Type::LAST_MONTH: + case Type::NEXT_MONTH: + $where['type'] = Type::BETWEEN; + + $dtFrom = $dt->modify('first day of this month')->setTime(0, 0); + + if ($type == Type::LAST_MONTH) { + $dtFrom->modify('-1 month'); + } + else if ($type == Type::NEXT_MONTH) { + $dtFrom->modify('+1 month'); + } + + $dtTo = clone $dtFrom; + $dtTo->modify('+1 month'); + + $dtFrom->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = [$dtFrom->format($format), $dtTo->format($format)]; + + break; + + case Type::CURRENT_QUARTER: + case Type::LAST_QUARTER: + $where['type'] = Type::BETWEEN; + + try { + $dt = new DateTime('now', new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad timezone."); + } + + $quarter = ceil($dt->format('m') / 3); + + $dtFrom = clone $dt; + $dtFrom->modify('first day of January this year')->setTime(0, 0); + + if ($type === Type::LAST_QUARTER) { + $quarter--; + + if ($quarter == 0) { + $quarter = 4; + $dtFrom->modify('-1 year'); + } + } + + try { + $dtFrom->add(new DateInterval('P' . (($quarter - 1) * 3) . 'M')); + } + catch (Exception) { + throw new RuntimeException(); + } + + $dtTo = clone $dtFrom; + $dtTo->add(new DateInterval('P3M')); + $dtFrom->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = [ + $dtFrom->format($format), + $dtTo->format($format), + ]; + + break; + + case Type::CURRENT_YEAR: + case Type::LAST_YEAR: + $where['type'] = Type::BETWEEN; + + try { + $dtFrom = new DateTime('now', new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad timezone."); + } + + $dtFrom->modify('first day of January this year')->setTime(0, 0); + + if ($type == Type::LAST_YEAR) { + $dtFrom->modify('-1 year'); + } + + $dtTo = clone $dtFrom; + $dtTo = $dtTo->modify('+1 year'); + $dtFrom->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = [ + $dtFrom->format($format), + $dtTo->format($format), + ]; + + break; + + case Type::CURRENT_FISCAL_YEAR: + case Type::LAST_FISCAL_YEAR: + $where['type'] = Type::BETWEEN; + + try { + $dtToday = new DateTime('now', new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad timezone."); + } + + $dt = clone $dtToday; + $fiscalYearShift = $this->config->get('fiscalYearShift', 0); + + $dt + ->modify('first day of January this year') + ->modify('+' . $fiscalYearShift . ' months') + ->setTime(0, 0); + + if (intval($dtToday->format('m')) < $fiscalYearShift + 1) { + $dt->modify('-1 year'); + } + + if ($type === Type::LAST_FISCAL_YEAR) { + $dt->modify('-1 year'); + } + + $dtFrom = clone $dt; + $dtTo = clone $dt; + $dtTo = $dtTo->modify('+1 year'); + + $dtFrom->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = [ + $dtFrom->format($format), + $dtTo->format($format), + ]; + + break; + + case Type::CURRENT_FISCAL_QUARTER: + case Type::LAST_FISCAL_QUARTER: + $where['type'] = Type::BETWEEN; + + try { + $dtToday = new DateTime('now', new DateTimeZone($timeZone)); + } + catch (Exception) { + throw new BadRequest("Bad timezone."); + } + + $dt = clone $dtToday; + + $fiscalYearShift = $this->config->get('fiscalYearShift', 0); + + $dt + ->modify('first day of January this year') + ->modify('+' . $fiscalYearShift . ' months') + ->setTime(0, 0); + + $month = intval($dtToday->format('m')); + + $quarterShift = floor(($month - $fiscalYearShift - 1) / 3); + + if ($quarterShift) { + if ($quarterShift >= 0) { + try { + $dt->add(new DateInterval('P' . ($quarterShift * 3) . 'M')); + } + catch (Exception) { + throw new RuntimeException(); + } + } else { + $quarterShift *= -1; + + try { + $dt->sub(new DateInterval('P' . ($quarterShift * 3) . 'M')); + } + catch (Exception) { + throw new RuntimeException(); + } + } + } + + if ($type === Type::LAST_FISCAL_QUARTER) { + $dt->modify('-3 months'); + } + + $dtFrom = clone $dt; + $dtTo = clone $dt; + $dtTo = $dtTo->modify('+3 months'); + + $dtFrom->setTimezone(new DateTimeZone('UTC')); + $dtTo->setTimezone(new DateTimeZone('UTC')); + + $where['value'] = [ + $dtFrom->format($format), + $dtTo->format($format), + ]; + + break; + + default: + $where['type'] = $type; + } + + return Item::fromRaw($where); + } +} diff --git a/application/Espo/Modules/Crm/Classes/Select/Meeting/Where/DateTimeItemTransformer.php b/application/Espo/Modules/Crm/Classes/Select/Meeting/Where/DateTimeItemTransformer.php index 9aecd70fa4..098bd41b97 100644 --- a/application/Espo/Modules/Crm/Classes/Select/Meeting/Where/DateTimeItemTransformer.php +++ b/application/Espo/Modules/Crm/Classes/Select/Meeting/Where/DateTimeItemTransformer.php @@ -29,27 +29,37 @@ namespace Espo\Modules\Crm\Classes\Select\Meeting\Where; -use Espo\Core\Select\Where\DateTimeItemTransformer as DateTimeItemTransformerOriginal; +use Espo\Core\Select\Where\DateTimeItemTransformer as DateTimeItemTransformerInterface; +use Espo\Core\Select\Where\DefaultDateTimeItemTransformer; use Espo\Core\Select\Where\Item; /** * Extends to take into account DateStartDate and DateEndDate fields. + * + * @noinspection PhpUnused */ -class DateTimeItemTransformer extends DateTimeItemTransformerOriginal +class DateTimeItemTransformer implements DateTimeItemTransformerInterface { + public function __construct( + private DefaultDateTimeItemTransformer $defaultDateTimeItemTransformer + ) {} + public function transform(Item $item): Item { $type = $item->getType(); $value = $item->getValue(); $attribute = $item->getAttribute(); - $transformedItem = parent::transform($item); + $transformedItem = $this->defaultDateTimeItemTransformer->transform($item); - if (!in_array($attribute, ['dateStart', 'dateEnd'])) { - return $transformedItem; - } - - if (in_array($type, ['isNull', 'ever', 'isNotNull'])) { + if ( + !in_array($attribute, ['dateStart', 'dateEnd']) || + in_array($type, [ + Item\Type::IS_NULL, + Item\Type::EVER, + Item\Type::IS_NOT_NULL, + ]) + ) { return $transformedItem; } @@ -75,15 +85,15 @@ class DateTimeItemTransformer extends DateTimeItemTransformerOriginal ]; $raw = [ - 'type' => 'or', + 'type' => Item::TYPE_OR, 'value' => [ $datePartRaw, [ - 'type' => 'and', + 'type' => Item::TYPE_AND, 'value' => [ $transformedItem->getRaw(), [ - 'type' => 'isNull', + 'type' => Item\Type::IS_NULL, 'attribute' => $attributeDate, ] ] diff --git a/tests/unit/Espo/Core/Select/Where/ConverterFactoryTest.php b/tests/unit/Espo/Core/Select/Where/ConverterFactoryTest.php index 2b386eff15..ec038ddcbb 100644 --- a/tests/unit/Espo/Core/Select/Where/ConverterFactoryTest.php +++ b/tests/unit/Espo/Core/Select/Where/ConverterFactoryTest.php @@ -36,6 +36,7 @@ use Espo\Core\InjectableFactory; use Espo\Core\Select\Where\Converter; use Espo\Core\Select\Where\ConverterFactory; use Espo\Core\Select\Where\DateTimeItemTransformer; +use Espo\Core\Select\Where\DefaultDateTimeItemTransformer; use Espo\Core\Select\Where\ItemConverter; use Espo\Core\Select\Where\ItemGeneralConverter; use Espo\Core\Utils\Metadata; @@ -56,7 +57,7 @@ class ConverterFactoryTest extends \PHPUnit\Framework\TestCase ); $this->itemConverter = $this->createMock(ItemGeneralConverter::class); - $this->dateTimeItemTransformer = $this->createMock(DateTimeItemTransformer::class); + $this->dateTimeItemTransformer = $this->createMock(DefaultDateTimeItemTransformer::class); } public function testCreate1() @@ -82,7 +83,7 @@ class ConverterFactoryTest extends \PHPUnit\Framework\TestCase [['selectDefs', $entityType, 'whereConverterClassName'], null, $className3], ]); - $className1 = $className1 ?? DateTimeItemTransformer::class; + $className1 = $className1 ?? DefaultDateTimeItemTransformer::class; $className2 = $className2 ?? ItemGeneralConverter::class; $className3 = $className3 ?? Converter::class; @@ -100,7 +101,7 @@ class ConverterFactoryTest extends \PHPUnit\Framework\TestCase ->bindValue('$entityType', $entityType); $binder1 - ->for(DateTimeItemTransformer::class) + ->for(DefaultDateTimeItemTransformer::class) ->bindValue('$entityType', $entityType); $bindingContainer1 = new BindingContainer($bindingData1); diff --git a/tests/unit/Espo/Core/Select/Where/ConverterTest.php b/tests/unit/Espo/Core/Select/Where/ConverterTest.php index c299ed80d6..9d5d46a4aa 100644 --- a/tests/unit/Espo/Core/Select/Where/ConverterTest.php +++ b/tests/unit/Espo/Core/Select/Where/ConverterTest.php @@ -31,7 +31,7 @@ namespace tests\unit\Espo\Core\Select\Where; use Espo\Core\Select\Helpers\RandomStringGenerator; use Espo\Core\Select\Where\Converter; -use Espo\Core\Select\Where\DateTimeItemTransformer; +use Espo\Core\Select\Where\DefaultDateTimeItemTransformer; use Espo\Core\Select\Where\Item; use Espo\Core\Select\Where\ItemConverter; use Espo\Core\Select\Where\ItemConverterFactory; @@ -90,10 +90,7 @@ class ConverterTest extends \PHPUnit\Framework\TestCase ->method('generate') ->willReturn('Random'); - $this->dateTimeItemTransformer = new DateTimeItemTransformer( - $this->user, - $this->config - ); + $this->dateTimeItemTransformer = new DefaultDateTimeItemTransformer($this->config); $this->itemConverter = new ItemGeneralConverter( $this->entityType, @@ -102,7 +99,6 @@ class ConverterTest extends \PHPUnit\Framework\TestCase $this->scanner, $this->itemConverterFactory, $this->randomStringGenerator, - //$this->entityManager, $this->ormDefs, $this->config, $this->metadata