YetiForceCompany/YetiForceCRM

View on GitHub
app/Utils/ServiceContracts.php

Summary

Maintainability
F
3 days
Test Coverage
F
15%
<?php
/**
 * Service contracts utils file.
 *
 * @package App
 *
 * @copyright YetiForce S.A.
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
 * @author    Mariusz Krzaczkowski <m.krzaczkowski@yetiforce.com>
 */

namespace App\Utils;

/**
 * Service contracts utils class.
 */
class ServiceContracts
{
    /**
     * Fields map.
     *
     * @var string[]
     */
    private static $fieldsMap = [
        'reaction_time' => 'response',
        'resolve_time' => 'solution',
        'idle_time' => 'idle',
    ];

    /**
     * Get the amount of business time between two dates in minutes.
     *
     * @param string $start
     * @param string $end
     * @param array  $days
     * @param string $startHour
     * @param string $endHour
     * @param bool   $holidays
     *
     * @return int
     */
    public static function businessTime(string $start, string $end, string $startHour, string $endHour, array $days, bool $holidays): int
    {
        $start = new \DateTime($start);
        $end = new \DateTime($end);
        $holidaysDates = $dates = [];
        $date = clone $start;
        $days = array_flip($days);
        if ($holidays) {
            $holidaysDates = array_flip(array_keys(\App\Fields\Date::getHolidays($start->format('Y-m-d'), $end->format('Y-m-d'))));
        }
        while ($date <= $end) {
            $datesEnd = (clone $date)->setTime(23, 59, 59);
            if (isset($days[$date->format('N')]) && (!$holidays || ($holidays && !isset($holidaysDates[$date->format('Y-m-d')])))) {
                $dates[] = [
                    'start' => clone $date,
                    'end' => clone ($end < $datesEnd ? $end : $datesEnd),
                ];
            }
            $date->modify('+1 day')->setTime(0, 0, 0);
        }
        [$sh,$sm,$ss] = explode(':', $startHour);
        [$eh,$em,$es] = explode(':', $endHour);
        return array_reduce($dates, function ($carry, $item) use ($sh, $sm, $ss, $eh, $em, $es) {
            $businessStart = (clone $item['start'])->setTime($sh, $sm, $ss);
            $businessEnd = (clone $item['end'])->setTime($eh, $em, $es);
            $start = ($item['start'] < $businessStart) ? $businessStart : $item['start'];
            $end = ($item['end'] > $businessEnd) ? $businessEnd : $item['end'];
            return $carry += max(0, $end->getTimestamp() - $start->getTimestamp());
        }, 0) / 60;
    }

    /**
     * Get default business hours.
     *
     * @return array
     */
    public static function getDefaultBusinessHours(): array
    {
        if (\App\Cache::has('UtilsServiceContracts::getDefaultBusinessHours', '')) {
            return \App\Cache::get('UtilsServiceContracts::getDefaultBusinessHours', '');
        }
        $rows = (new \App\Db\Query())->from('s_#__business_hours')->where(['default' => 1])->all(\App\Db::getInstance('admin'));
        \App\Cache::save('UtilsServiceContracts::getDefaultBusinessHours', '', $rows);
        return $rows;
    }

    /**
     * Get all business hours.
     *
     * @return array
     */
    public static function getAllBusinessHours(): array
    {
        if (\App\Cache::has('UtilsServiceContracts::getAllBusinessHours', '')) {
            return \App\Cache::get('UtilsServiceContracts::getAllBusinessHours', '');
        }
        $rows = (new \App\Db\Query())->from('s_#__business_hours')->all(\App\Db::getInstance('admin'));
        \App\Cache::save('UtilsServiceContracts::getAllBusinessHours', '', $rows);
        return $rows;
    }

    /**
     * Get business hours by ids .
     *
     * @param string $ids ex. '1,2'
     *
     * @return array
     */
    public static function getBusinessHoursByIds(array $ids): array
    {
        $cacheKey = implode(',', $ids);
        if (\App\Cache::has('UtilsServiceContracts::getBusinessHoursById', $cacheKey)) {
            return \App\Cache::get('UtilsServiceContracts::getBusinessHoursById', $cacheKey);
        }
        $rows = (new \App\Db\Query())->from('s_#__business_hours')->where(['id' => $ids])->all(\App\Db::getInstance('admin'));
        \App\Cache::save('UtilsServiceContracts::getBusinessHoursById', $cacheKey, $rows);
        return $rows;
    }

    /**
     * Get sla policy by id.
     *
     * @param int $id
     *
     * @return array
     */
    private static function getSlaPolicyById(int $id): array
    {
        if (\App\Cache::has('UtilsServiceContracts::getSlaPolicyById', $id)) {
            return \App\Cache::get('UtilsServiceContracts::getSlaPolicyById', $id);
        }
        $row = (new \App\Db\Query())->from('s_#__sla_policy')->where(['id' => $id])->one(\App\Db::getInstance('admin'));
        \App\Cache::save('UtilsServiceContracts::getSlaPolicyById', $id, $row);
        return $row;
    }

    /**
     * Get sla policy by module id.
     *
     * @param int $moduleId
     *
     * @return array
     */
    public static function getSlaPolicyByModule(int $moduleId): array
    {
        if (\App\Cache::has('UtilsServiceContracts::getSlaPolicyByModule', $moduleId)) {
            return \App\Cache::get('UtilsServiceContracts::getSlaPolicyByModule', $moduleId);
        }
        $rows = (new \App\Db\Query())->from('s_#__sla_policy')->where(['tabid' => $moduleId])->all(\App\Db::getInstance('admin'));
        \App\Cache::save('UtilsServiceContracts::getSlaPolicyByModule', $moduleId, $rows);
        return $rows;
    }

    /**
     * Get sla policy from service contracts by crm id.
     *
     * @param int      $serviceContractId Service contracts id
     * @param int|null $sourceModuleId
     *
     * @return array
     */
    public static function getSlaPolicyForServiceContracts(int $serviceContractId, ?int $sourceModuleId = null): array
    {
        if (\App\Cache::has('UtilsServiceContracts::getSlaPolicyForServiceContracts', $serviceContractId)) {
            $rows = \App\Cache::get('UtilsServiceContracts::getSlaPolicyForServiceContracts', $serviceContractId);
        } else {
            $rows = (new \App\Db\Query())->from('u_#__servicecontracts_sla_policy')->where(['crmid' => $serviceContractId])->all();
            \App\Cache::save('UtilsServiceContracts::getSlaPolicyForServiceContracts', $serviceContractId, $rows);
        }
        if ($sourceModuleId) {
            foreach ($rows as $key => $value) {
                if ($sourceModuleId !== $value['tabid']) {
                    unset($rows[$key]);
                }
            }
        }
        return $rows;
    }

    /**
     * Delete sla policy for service contracts.
     *
     * @param int      $crmId
     * @param int      $sourceModuleId
     * @param int|null $rowId
     *
     * @return void
     */
    public static function deleteSlaPolicy(int $crmId, int $sourceModuleId, ?int $rowId = null)
    {
        $where = ['crmid' => $crmId, 'tabid' => $sourceModuleId];
        if ($rowId) {
            $where['id'] = $rowId;
        }
        \App\Db::getInstance()->createCommand()
            ->delete('u_#__servicecontracts_sla_policy', $where)->execute();
        \App\Cache::delete('UtilsServiceContracts::getSlaPolicyForServiceContracts', $crmId);
    }

    /**
     * Save sla policy for service contracts.
     *
     * @param array $data
     * @param bool  $delete
     *
     * @return void
     */
    public static function saveSlaPolicy(array $data, bool $delete = true)
    {
        $db = \App\Db::getInstance();
        if ($delete) {
            self::deleteSlaPolicy($data['crmid'], $data['tabid']);
        }
        if ($data['policy_type']) {
            $db->createCommand()->insert('u_#__servicecontracts_sla_policy', $data)->execute();
            return $db->getLastInsertID();
        }
        return 0;
    }

    /**
     * Get rules for service contracts.
     *
     * @param int                  $serviceContractId Service contracts id
     * @param \Vtiger_Record_Model $recordModel       Record the model that will be updated
     *
     * @return array
     */
    public static function getRulesForServiceContracts(int $serviceContractId, \Vtiger_Record_Model $recordModel): array
    {
        $times = $businessHours = [];
        foreach (self::getSlaPolicyForServiceContracts($serviceContractId, $recordModel->getModule()->getId()) as $row) {
            switch ($row['policy_type']) {
                case 1:
                    $slaPolicy = self::getSlaPolicyById($row['sla_policy_id']);
                    $conditions = \App\Json::decode($slaPolicy['conditions']);
                    if ($conditions && \App\Condition::checkConditions($conditions, $recordModel)) {
                        if (empty($slaPolicy['operational_hours'])) {
                            return $slaPolicy;
                        }
                        if ($slaPolicy['business_hours']) {
                            return self::optimizeBusinessHours(explode(',', $slaPolicy['business_hours']));
                        }
                    }
                    break;
                case 2:
                    $conditions = \App\Json::decode($row['conditions']);
                    if ($conditions && $row['business_hours'] && \App\Condition::checkConditions($conditions, $recordModel)) {
                        $businessHours = \array_merge($businessHours, explode(',', $row['business_hours']));
                        if ((isset($times['reaction_time']) && \App\Fields\TimePeriod::convertToMinutes($row['reaction_time']) < \App\Fields\TimePeriod::convertToMinutes($times['reaction_time']))
                        || !isset($times['reaction_time'])) {
                            $times = [
                                'reaction_time' => $row['reaction_time'],
                                'idle_time' => $row['idle_time'],
                                'resolve_time' => $row['resolve_time'],
                            ];
                        }
                    }
                    break;
            }
        }
        if ($businessHours) {
            $result = [];
            foreach (self::optimizeBusinessHours(\array_unique($businessHours)) as $value) {
                $result[] = array_merge($value, $times);
            }
            return $result;
        }
        return [];
    }

    /**
     * Get rules for record model which will be updated.
     *
     * @param \Vtiger_Record_Model $recordModel Record the model that will be updated
     *
     * @return array
     */
    public static function getSlaPolicyRulesForModule(\Vtiger_Record_Model $recordModel): array
    {
        $times = $businessHours = [];
        foreach (self::getSlaPolicyForModule($recordModel->getModule()->getId()) as $row) {
            $conditions = \App\Json::decode($row['conditions']);
            if ($conditions && $row['business_hours'] && \App\Condition::checkConditions($conditions, $recordModel)) {
                $businessHours = \array_merge($businessHours, explode(',', $row['business_hours']));
                if ((isset($times['reaction_time']) && \App\Fields\TimePeriod::convertToMinutes($row['reaction_time']) < \App\Fields\TimePeriod::convertToMinutes($times['reaction_time']))
                        || !isset($times['reaction_time'])) {
                    $times = [
                        'reaction_time' => $row['reaction_time'],
                        'idle_time' => $row['idle_time'],
                        'resolve_time' => $row['resolve_time'],
                    ];
                }
                break;
            }
        }
        if ($businessHours) {
            $result = [];
            foreach (self::optimizeBusinessHours(\array_unique($businessHours)) as $value) {
                $result[] = array_merge($value, $times);
            }
            return $result;
        }
        return [];
    }

    /**
     * Get sla policy by crm id.
     *
     * @param int $moduleId
     *
     * @return array
     */
    public static function getSlaPolicyForModule(int $moduleId): array
    {
        if (\App\Cache::has('UtilsServiceContracts::getSlaPolicyForModule', $moduleId)) {
            $rows = \App\Cache::get('UtilsServiceContracts::getSlaPolicyForModule', $moduleId);
        } else {
            $rows = (new \App\Db\Query())->from('s_#__sla_policy')->where(['tabid' => $moduleId, 'available_for_record_time_count' => 1])->all(\App\Db::getInstance('admin'));
            \App\Cache::save('UtilsServiceContracts::getSlaPolicyForModule', $moduleId, $rows);
        }
        return $rows;
    }

    /**
     * Parse business hours to days.
     *
     * @param array $rows
     *
     * @return array
     */
    private static function parseBusinessHoursToDays(array $rows): array
    {
        $days = $holidays = [];
        foreach ($rows as $row) {
            foreach (explode(',', $row['working_days']) as $day) {
                if ((isset($days[$day]['working_hours_from']) && (int) $row['working_hours_from'] < (int) $days[$day]['working_hours_from'])
                    || empty($days[$day]['working_hours_from'])) {
                    $days[$day] = [
                        'working_hours_from' => $row['working_hours_from'],
                        'working_hours_to' => $row['working_hours_to'],
                        'reaction_time' => $row['reaction_time'],
                        'idle_time' => $row['idle_time'],
                        'resolve_time' => $row['resolve_time'],
                    ];
                }
            }
            if (!empty($row['holidays']) && ((isset($holidays['working_hours_from']) && (int) $row['working_hours_from'] < (int) $holidays['working_hours_from'])
                || empty($holidays['working_hours_from']))) {
                $holidays = [
                    'working_hours_from' => $row['working_hours_from'],
                    'working_hours_to' => $row['working_hours_to'],
                    'reaction_time' => $row['reaction_time'],
                    'idle_time' => $row['idle_time'],
                    'resolve_time' => $row['resolve_time'],
                ];
            }
        }
        return ['days' => $days, 'holidays' => $holidays];
    }

    /**
     * Undocumented function.
     *
     * @param array $businessHours
     *
     * @return array
     */
    private static function optimizeBusinessHours(array $businessHours): array
    {
        $result = [];
        ['days' => $days, 'holidays' => $holidays] = self::parseBusinessHoursToDays(self::getBusinessHoursByIds($businessHours));
        foreach ($days as $day => $value) {
            $key = "{$value['working_hours_from']}|{$value['working_hours_to']}";
            if (isset($result[$key])) {
                $result[$key] = ['working_days' => $result[$key]['working_days'] . ',' . $day] + $value;
            } else {
                $result[$key] = ['working_days' => $day] + $value;
            }
        }
        if ($holidays) {
            $key = "{$holidays['working_hours_from']}|{$holidays['working_hours_to']}";
            $result[$key] = $result[$key] + ['holidays' => 1];
        }
        return $result;
    }

    /**
     * Function returning difference in format between date times.
     *
     * @param string               $start
     * @param string               $end
     * @param \Vtiger_Record_Model $recordModel Record the model that will be updated
     *
     * @return int
     */
    public static function getDiff(string $start, \Vtiger_Record_Model $recordModel, string $end = ''): int
    {
        if (!$end) {
            $end = date('Y-m-d H:i:s');
        }
        $fieldModel = current($recordModel->getModule()->getReferenceFieldsForModule('ServiceContracts'));
        if ($fieldModel && ($value = $recordModel->get($fieldModel->getName()))) {
            return self::getDiffFromServiceContracts($start, $end, $value, $recordModel);
        }
        if (\is_int($diff = self::getDiffFromSlaPolicy($start, $end, $recordModel))) {
            return $diff;
        }
        if (!($diff = self::getDiffFromDefaultBusinessHours($start, $end))) {
            $diff = \App\Fields\DateTime::getDiff($start, $end, 'minutes');
        }
        return $diff;
    }

    /**
     * Get the amount of business time between the two dates in minutes based on the service contracts.
     *
     * @param string               $start
     * @param string               $end
     * @param int                  $serviceContractId Service contracts id
     * @param \Vtiger_Record_Model $recordModel       Record the model that will be updated
     *
     * @return int
     */
    public static function getDiffFromServiceContracts(string $start, string $end, int $serviceContractId, \Vtiger_Record_Model $recordModel): int
    {
        if ($rules = self::getRulesForServiceContracts($serviceContractId, $recordModel)) {
            if (isset($rules['id'])) {
                return round(\App\Fields\DateTime::getDiff($start, $end, 'minutes'));
            }
            $time = 0;
            foreach ($rules as $row) {
                $time += self::businessTime($start, $end, $row['working_hours_from'], $row['working_hours_to'], explode(',', $row['working_days']), !empty($row['holidays']));
            }
            return $time;
        }
        if (!($diff = self::getDiffFromDefaultBusinessHours($start, $end))) {
            $diff = round(\App\Fields\DateTime::getDiff($start, $end, 'minutes'));
        }
        return $diff;
    }

    /**
     * Get the amount of business time between the two dates in minutes based on the global sla policy.
     *
     * @param string               $start
     * @param string               $end
     * @param \Vtiger_Record_Model $recordModel Record the model that will be updated
     *
     * @return int|null
     */
    public static function getDiffFromSlaPolicy(string $start, string $end, \Vtiger_Record_Model $recordModel): ?int
    {
        if ($rules = self::getSlaPolicyRulesForModule($recordModel)) {
            $time = 0;
            foreach ($rules as $row) {
                $time += self::businessTime($start, $end, $row['working_hours_from'], $row['working_hours_to'], explode(',', $row['working_days']), !empty($row['holidays']));
            }
            return $time;
        }
        return null;
    }

    /**
     * Get the amount of default business time between two dates in minutes.
     *
     * @param string $start
     * @param string $end
     *
     * @return int
     */
    public static function getDiffFromDefaultBusinessHours(string $start, string $end): int
    {
        $businessHours = self::getDefaultBusinessHours();
        if (!$businessHours) {
            return false;
        }
        $time = 0;
        foreach ($businessHours as $row) {
            if ($row['working_days']) {
                $time += self::businessTime($start, $end, $row['working_hours_from'], $row['working_hours_to'], explode(',', $row['working_days']), (bool) $row['holidays']);
            }
        }
        return $time;
    }

    /**
     * Update expected times.
     *
     * @param \Vtiger_Record_Model $recordModel
     * @param array                $type
     *
     * @return void
     */
    public static function updateExpectedTimes(\Vtiger_Record_Model $recordModel, array $type)
    {
        $field = \App\Field::getRelatedFieldForModule($recordModel->getModuleName(), 'ServiceContracts');
        $serviceContract = ($field && !empty($recordModel->get($field['fieldname']))) ? $recordModel->get($field['fieldname']) : 0;
        foreach (self::getExpectedTimes($serviceContract, $recordModel, $type) as $key => $time) {
            $recordModel->set($key . '_expected', $time);
        }
    }

    /**
     * Get expected times from ServiceContracts.
     *
     * @param int                  $id          Service contract id
     * @param \Vtiger_Record_Model $recordModel
     * @param array                $type
     *
     * @return array
     */
    private static function getExpectedTimes(int $id, \Vtiger_Record_Model $recordModel, array $type): array
    {
        $return = [];
        $date = new \DateTime();
        if ($id && ($rules = self::getRulesForServiceContracts($id, $recordModel)) || ($rules = self::getSlaPolicyRulesForModule($recordModel))) {
            if (isset($rules['id'])) {
                foreach (self::$fieldsMap as $key => $fieldKey) {
                    if (\in_array($fieldKey, $type)) {
                        $minutes = \App\Fields\TimePeriod::convertToMinutes($rules[$key]);
                        $return[$fieldKey] = (clone $date)->modify("+$minutes minute")->format('Y-m-d H:i:s');
                    }
                }
                return $return;
            }
            $days = self::parseBusinessHoursToDays($rules);
        } elseif ($businessHours = self::getDefaultBusinessHours()) {
            $days = self::parseBusinessHoursToDays($businessHours);
        } else {
            return [];
        }
        $day = $date->format('N');
        $daySetting = null;
        if (\App\Fields\Date::getHolidays($date->format('Y-m-d'), $date->format('Y-m-d'))) {
            $daySetting = $days['holidays'];
        } elseif (empty($days['days'][$day])) {
            $daySetting = self::getNextBusinessDay($date, $days);
        } else {
            $daySetting = $days['days'][$day];
        }
        if ($daySetting) {
            $interval = \App\Fields\DateTime::getDiff($date->format('Y-m-d') . ' ' . $daySetting['working_hours_to'], $date->format('Y-m-d H:i:s'), 'minutes');
            foreach (self::$fieldsMap as $key => $fieldKey) {
                if (\in_array($fieldKey, $type)) {
                    $minutes = \App\Fields\TimePeriod::convertToMinutes($daySetting[$key]);
                    if ($minutes < $interval) {
                        $return[$fieldKey] = (clone $date)->modify("+$minutes minute")->format('Y-m-d H:i:s');
                    } else {
                        $tmpDate = clone $date;
                        $tmpInterval = $interval;
                        while ($minutes > $tmpInterval) {
                            $minutes -= $tmpInterval;
                            $tmpDaySetting = self::getNextBusinessDay($tmpDate, $days);
                            $tmpInterval = \App\Fields\DateTime::getDiff($tmpDate->format('Y-m-d') . ' ' . $tmpDaySetting['working_hours_to'], $tmpDate->format('Y-m-d H:i:s'), 'minutes');
                            if ($minutes < $tmpInterval) {
                                $return[$fieldKey] = (clone $tmpDate)->modify("+$minutes minute")->format('Y-m-d H:i:s');
                            }
                        }
                    }
                }
            }
            return $return;
        }
        return [];
    }

    /**
     * Get next business day.
     *
     * @param \DateTime $date
     * @param array     $days
     *
     * @return array|null
     */
    private static function getNextBusinessDay(\DateTime &$date, array $days): ?array
    {
        $tempDay = (int) $date->format('N') + 1;
        $counter = 1;
        $result = null;
        while ($counter < 14) {
            $date->modify('+1 day');
            if (\App\Fields\Date::getHolidays($date->format('Y-m-d'), $date->format('Y-m-d'))) {
                $result = $days['holidays'];
                break;
            }
            if (isset($days['days'][$tempDay])) {
                $result = $days['days'][$tempDay];
                break;
            }
            ++$tempDay;
            if (8 === $tempDay) {
                $tempDay = 1;
            }
            ++$counter;
        }
        if ($result) {
            \call_user_func_array([$date, 'setTime'], explode(':', $result['working_hours_from']));
        }
        return $result;
    }

    /**
     * Get modules name related to ServiceContracts.
     *
     * @return string[]
     */
    public static function getModules(): array
    {
        $modules = [];
        foreach (\App\Field::getRelatedFieldForModule(false, 'ServiceContracts') as $moduleName => $value) {
            if (\App\RecordStatus::getFieldName($moduleName)) {
                $modules[] = $moduleName;
            }
        }
        return $modules;
    }
}