Ctrl K
ComunidadGuías…

tine20/HumanResources/Controller/Account.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php
/**
 * Account controller for HumanResources application
 *
 * @package     HumanResources
 * @subpackage  Controller
 * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
 * @author      Alexander Stintzing <a.stintzing@metaways.de>
 * @copyright   Copyright (c) 2013-2022 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

/**
 * Account controller class for HumanResources application
 *
 * @package     HumanResources
 * @subpackage  Controller
 */
class HumanResources_Controller_Account extends Tinebase_Controller_Record_Abstract
{
    /** @use Tinebase_Controller_SingletonTrait<HumanResources_Controller_Account> */
    use Tinebase_Controller_SingletonTrait;
    use HumanResources_Controller_CheckFilterACLEmployeeTrait;

    protected $_contractController = NULL;

    protected $_getMultipleGrant = [HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA];
    protected $_requiredFilterACLget = [
        HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA,
        '|' . HumanResources_Model_DivisionGrants::READ_BASIC_EMPLOYEE_DATA
    ];
    protected $_requiredFilterACLupdate  = [HumanResources_Model_DivisionGrants::UPDATE_EMPLOYEE_DATA];
    protected $_requiredFilterACLsync  = [HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA];
    protected $_requiredFilterACLexport  = [HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA];

    /**
     * the constructor
     *
     * don't use the constructor. use the singleton
     */
    protected function __construct()
    {
        $this->_applicationName = 'HumanResources';
        $this->_backend = new HumanResources_Backend_Account();
        $this->_modelName = 'HumanResources_Model_Account';
        $this->_purgeRecords = FALSE;
        // activate this if you want to use containers
        $this->_doContainerACLChecks = true;
        $this->_contractController = HumanResources_Controller_Contract::getInstance();
    }

    protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessage = 'No Permission.', $_oldRecord = NULL)
    {
        if (!$this->_doContainerACLChecks) {
            return true;
        }

        // if we have manage_employee right, we have all grants
        if (Tinebase_Core::getUser()->hasRight(HumanResources_Config::APP_NAME, HumanResources_Acl_Rights::MANAGE_EMPLOYEE)) {
            return true;
        }

        switch ($_action) {
            case self::ACTION_GET:
                try {
                    HumanResources_Controller_Employee::getInstance()->get($_record->getIdFromProperty('employee_id'));
                } catch (Tinebase_Exception_AccessDenied $e) {
                    if ($_throw) {
                        throw new Tinebase_Exception_AccessDenied($_errorMessage);
                    } else {
                        return false;
                    }
                }
                return true;
            case self::ACTION_CREATE:
            case self::ACTION_UPDATE:
            case self::ACTION_DELETE:
                $_action = HumanResources_Model_DivisionGrants::UPDATE_EMPLOYEE_DATA;
                break;
        }
        return parent::_checkGrant($_record, $_action, $_throw, $_errorMessage, $_oldRecord);
    }

    /**
     * @param string|HumanResources_Model_Employee $employeeId
     * @param string|DateTime $year default current year
     * @return HumanResources_Model_Account|null
     * @throws Tinebase_Exception_InvalidArgument
     */
    public function getByEmployeeYear($employeeId, $year = null)
    {
        $employeeId = $employeeId instanceof HumanResources_Model_Employee ? $employeeId->getId() : $employeeId;
        $year = $year ?: Tinebase_DateTime::now();
        $year = $year instanceof DateTime ? $year->format('Y') : $year;
        
        // find account
        $filter = new HumanResources_Model_AccountFilter([
            ['field' => 'year',         'operator' => 'equals', 'value' => intval($year)],
            ['field' => 'employee_id',  'operator' => 'equals', 'value' => $employeeId]
        ]);

        return $this->search($filter)->getFirstRecord();
    }

    public function autoCreateAccounts(): bool
    {
        // this year, next year
        $this->createMissingAccounts();
        // the year after next year
        $this->createMissingAccounts(intval(date('Y')) + 2);

        return true;
    }

    /**
     * creates missing accounts for all employees having a valid contract
     * if a year is given, missing accounts for this year will be built, otherwise the current and following year will be used
     *
     * @param integer $year
     * @param HumanResources_Model_Employee
     * @param boolean $useBackend Use Backend instead of this Controller (may called by setup also, skips rigts, creating modlog etc.)
     */
    public function createMissingAccounts($year = NULL, $employee = NULL, $useBackend = FALSE)
    {
        // if no year is given, call myself with this year and just go on with the next year
        if (! $year) {
            $date = new Tinebase_DateTime();
            $year = (int) $date->format('Y');
            $this->createMissingAccounts($year, $employee);
            $date->addYear(1);
            $year = (int) $date->format('Y');
        }
    
        // tine20 should last a hundred years :)
        if ($year < 2006 || $year >= 2106 || ! is_int($year)) {
            throw new Tinebase_Exception_Data('The year must be between 2006 and 2106');
        }
    
        // timezone problem here?
        $validEmployeeIds = array_unique($this->_contractController->getValidContracts([
            'from' => new Tinebase_DateTime($year . '-01-01 00:00:00'),
            'until' => new Tinebase_DateTime($year . '-12-31 23:59:59'),
        ], $employee)->employee_id);
        
        $existingFilter = new HumanResources_Model_AccountFilter(array(
            array('field' => 'year', 'operator' => 'equals', 'value' => $year)
        ));
        $existingFilter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'employee_id', 'operator' => 'in', 'value' => $validEmployeeIds)));
        
        $result = $this->search($existingFilter)->employee_id;
        $validEmployeeIds = array_diff($validEmployeeIds, $result);
        $createdAccounts = new Tinebase_Record_RecordSet('HumanResources_Model_Account');
        
        if ($useBackend) {
            $be = new HumanResources_Backend_Account();
            foreach($validEmployeeIds as $id) {
                $createdAccounts->addRecord($be->create(new HumanResources_Model_Account(array('employee_id' => $id, 'year' => $year))));
            }
        } else {
            foreach($validEmployeeIds as $id) {
                $createdAccounts->addRecord($this->create(new HumanResources_Model_Account(array('employee_id' => $id, 'year' => $year))));
            }
        }
        
        return $createdAccounts;
    }
    
    /**
     * resolves all virtual fields for the account
     * 
     * @param HumanResources_Model_Account $account
     * @return array with property => value
     */
    public function resolveVirtualFields(HumanResources_Model_Account $account, $expandRecord=false)
    {
        $accountPeriod = $account->getAccountPeriod();
        
        $account->contracts = $this->_contractController->getValidContracts($accountPeriod, $account->employee_id);
        
        // find out free days (vacation, sickness)
        $freetimeController = HumanResources_Controller_FreeTime::getInstance();
        
        $filter = new HumanResources_Model_FreeTimeFilter([
            ['field' => 'account_id', 'operator' => 'equals', 'value' => $account->getId()],
            ['field' => HumanResources_Model_FreeTime::FLD_PROCESS_STATUS, 'operator' => 'equals', 'value' => HumanResources_Config::FREE_TIME_PROCESS_STATUS_ACCEPTED],
            ['field' => 'type', 'operator' => 'equals', 'value' => 'sickness'],
        ], 'AND');
        
        $sickness = $freetimeController->search($filter);
        
        $unexcusedSicknessTimes = $sickness->filter(HumanResources_Model_FreeTime::FLD_TYPE_STATUS, HumanResources_Config::FREE_TIME_TYPE_STATUS_UNEXCUSED);
        $excusedSicknessTimes   = $sickness->filter(HumanResources_Model_FreeTime::FLD_TYPE_STATUS, HumanResources_Config::FREE_TIME_TYPE_STATUS_EXCUSED);
        
        $freedayController = HumanResources_Controller_FreeDay::getInstance();
        $filter = new HumanResources_Model_FreeDayFilter([], 'AND');
        
        $unexcusedSicknessFilter = clone $filter;
        $unexcusedSicknessFilter->addFilter(new Tinebase_Model_Filter_Id(array('field' => 'freetime_id', 'operator' => 'in', 'value' => $unexcusedSicknessTimes->id)));
        $unexcusedSicknessDays = $freedayController->search($unexcusedSicknessFilter);

        $excusedSicknessFilter = clone $filter;
        $excusedSicknessFilter->addFilter(new Tinebase_Model_Filter_Id(array('field' => 'freetime_id', 'operator' => 'in', 'value' => $excusedSicknessTimes->id)));
        $excusedSicknessDays = $freedayController->search($excusedSicknessFilter);
        
        
        $datesToWorkOn = $this->_contractController->getDatesToWorkOn($account->contracts, $accountPeriod['from'], $accountPeriod['until']);
        $datesToWorkOnReal = $this->_contractController->getDatesToWorkOn($account->contracts, $accountPeriod['from'], $accountPeriod['until'], TRUE);
        
        $data = array_merge(array(
            'excused_sickness'        => $excusedSicknessDays->count(),
            'unexcused_sickness'      => $unexcusedSicknessDays->count(),
            'working_days'            => count($datesToWorkOn['results']),
            'working_hours'           => $datesToWorkOn['hours'],
            'working_days_real'       => count($datesToWorkOnReal['results']),
            'working_hours_real'      => $datesToWorkOnReal['hours']
        ), $this->resolveVacation($account));
        
        if (true === $expandRecord) {
            foreach($data as $key => $value) {
                $account->{$key} = $value;
            }
        }
        return $data;
    }

    /**
     * resolve vacation properties of given account
     * 
     * @param HumanResources_Model_Account $account
     * @param DateTime $actualUntil | vacation days are computed as taken vacation until this date, null means now 
     * @return array with property => value
     * @throws Tinebase_Exception_InvalidArgument
     */
    public function resolveVacation($account, ?\DateTime $actualUntil=null)
    {
        if (!$account) throw new HumanResources_Exception_NoAccount();
        $accountPeriod = $account->getAccountPeriod();
        $contracts = $account->contracts ?: $this->_contractController->getValidContracts($accountPeriod, $account->employee_id);
        
        $actualUntil = $actualUntil ?: Tinebase_DateTime::now();
        
        // find out vacation days by contract(s) and interval
        $possibleVacationDays = round($this->_contractController->calculateVacationDays($contracts, $accountPeriod['from'], $accountPeriod['until']), 0);
        // find out free days (vacation, sickness)
        $freetimeController = HumanResources_Controller_FreeTime::getInstance();

        $filter = new HumanResources_Model_FreeTimeFilter(array(), 'AND');
        $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'account_id', 'operator' => 'equals', 'value' => $account->getId())));

        $freeTimes = $freetimeController->search($filter);
        
        $acceptedVacationTimes  = $freeTimes->filter('type', 'vacation')->filter(HumanResources_Model_FreeTime::FLD_PROCESS_STATUS, HumanResources_Config::FREE_TIME_PROCESS_STATUS_ACCEPTED);
        $requestedVacationTimes  = $freeTimes->filter('type', 'vacation')->filter(HumanResources_Model_FreeTime::FLD_PROCESS_STATUS, HumanResources_Config::FREE_TIME_PROCESS_STATUS_REQUESTED);

        $freedayController = HumanResources_Controller_FreeDay::getInstance();
        $filter = new HumanResources_Model_FreeDayFilter(array(), 'AND');

        $acceptedVacationFilter = clone $filter;
        $acceptedVacationFilter->addFilter(new Tinebase_Model_Filter_Id(array('field' => 'freetime_id', 'operator' => 'in', 'value' => $acceptedVacationTimes->id)));
        $acceptedVacationFilter->addFilter(new Tinebase_Model_Filter_Bool(array('field' => 'sickoverwrite', 'operator' => 'equals', 'value' => false)));
        $scheduledTakenVacationDays = $freedayController->search($acceptedVacationFilter);
        
        $actualTakenVacationDays = $scheduledTakenVacationDays->filter(function($freeday) use ($actualUntil) {
            //@TODO: convert date
            return $actualUntil ? $freeday->date < $actualUntil : true;
        });

        $requestedVacationFilter = clone $filter;
        $requestedVacationFilter->addFilter(new Tinebase_Model_Filter_Id(array('field' => 'freetime_id', 'operator' => 'in', 'value' => $requestedVacationTimes->id)));
        $scheduledRequestedVacationDays = $freedayController->search($requestedVacationFilter);

        $actualRequestedVacationDays = $scheduledRequestedVacationDays->filter(function($freeday) use ($actualUntil) {
            //@TODO: convert date
            return $actualUntil ? $freeday->date < $actualUntil : true;
        });

        $vacCorrections = HumanResources_Controller_VacationCorrection::getInstance()->search(
            Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_VacationCorrection::class, [
                ['field' => HumanResources_Model_VacationCorrection::FLD_ACCOUNT_ID, 'operator' => 'equals', 'value' => $account->getId()],
            ]));

        $possibleVacationDays += (int)$vacCorrections->sum(HumanResources_Model_VacationCorrection::FLD_CORRECTION);

        return [
            'vacation_expiary_date'             => HumanResources_Config::getInstance()->getVacationExpirationDate($account->year),
            'possible_vacation_days'            => intval($possibleVacationDays),
            'scheduled_taken_vacation_days'     => $scheduledTakenVacationDays->count(),
            'scheduled_requested_vacation_days' => $scheduledRequestedVacationDays->count(),
            'scheduled_remaining_vacation_days' => intval(floor($possibleVacationDays)) - $scheduledRequestedVacationDays->count() - $scheduledTakenVacationDays->count(),
            'actual_until'                      => $actualUntil,
            'actual_taken_vacation_days'        => $actualTakenVacationDays->count(),
            'actual_requested_vacation_days'    => $actualRequestedVacationDays->count(),
            'actual_remaining_vacation_days'    => intval(floor($possibleVacationDays)) - $actualTakenVacationDays->count()
        ];
    }
    
    /**
     * inspect update of one record (before update)
     *
     * @param   Tinebase_Record_Interface $_record      the update record
     * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
     * @return  void
     * 
     * @todo $_record->contracts should be a Tinebase_Record_RecordSet
     * @todo use getMigration()
     */
    protected function _inspectBeforeUpdate($_record, $_oldRecord)
    { 
        $_record->employee_id = $_oldRecord->employee_id;
        $_record->year = $_oldRecord->year;
    }
}