Ctrl K
ComunidadGuías…

tine20/HumanResources/Controller/Employee.php

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

Archivo original ↗Preguntar a KiBot
<?php
/**
 * Employee 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) 2012-2024 Metaways Infosystems GmbH (http://www.metaways.de)
 *
 */

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

    /**
     * duplicate check fields / if this is NULL -> no duplicate check
     *
     * @var array
     */
    protected $_duplicateCheckFields = array(array('account_id'), array('number'));
    protected $_resolveCustomFields = TRUE;

    protected $_getMultipleGrant = [HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA, HumanResources_Model_DivisionGrants::READ_BASIC_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, HumanResources_Model_DivisionGrants::READ_BASIC_EMPLOYEE_DATA];
    protected $_requiredFilterACLexport  = [HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA, HumanResources_Model_DivisionGrants::READ_BASIC_EMPLOYEE_DATA];
    
    /**
     * the constructor
     *
     * don't use the constructor. use the singleton
     */
    protected function __construct()
    {
        $this->_applicationName = 'HumanResources';
        $this->_backend = new HumanResources_Backend_Employee();
        $this->_modelName = HumanResources_Model_Employee::class;
        $this->_purgeRecords = false;
        $this->_doContainerACLChecks = true;
        $this->_traitDelegateAclField = 'division_id';
    }

    protected function _getCheckFilterACLTraitFilter()
    {
        return new Tinebase_Model_Filter_Text('account_id', 'equals', Tinebase_Core::getUser()->getId());
    }

    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:
                if ($_record->getIdFromProperty('account_id') === Tinebase_Core::getUser()->getId()) {
                    if (parent::_checkGrant($_record, HumanResources_Model_DivisionGrants::READ_OWN_DATA, false, $_errorMessage, $_oldRecord)) {
                        return true;
                    }
                }
                if (parent::_checkGrant($_record, HumanResources_Model_DivisionGrants::READ_EMPLOYEE_DATA, false, $_errorMessage, $_oldRecord)) {
                    return true;
                }
                $_action = HumanResources_Model_DivisionGrants::READ_BASIC_EMPLOYEE_DATA;
                break;
            case HumanResources_Model_DivisionGrants::READ_OWN_DATA:
                if ($_record->getIdFromProperty('account_id') !== Tinebase_Core::getUser()->getId()) {
                    if ($_throw) {
                        throw new Tinebase_Exception_AccessDenied($_errorMessage . ' read own data');
                    }
                    return false;
                }
                break;
            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);
    }

    /**
     * inspect creation of one record (after create)
     *
     * @param   Tinebase_Record_Interface $_createdRecord
     * @param   Tinebase_Record_Interface $_record
     * @return  void
     * 
     * @todo $_record->contracts should be a Tinebase_Record_RecordSet
     */
    protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
    {
        $firstDate = $_createdRecord->employment_begin instanceof Tinebase_DateTime ? $_createdRecord->employment_begin : Tinebase_DateTime::now();
        $lastDate = $_createdRecord->employment_end instanceof Tinebase_DateTime ? $_createdRecord->employment_end : Tinebase_DateTime::now()->addYear(1);
        
        $firstYear = (int) $firstDate->format('Y');
        $lastYear  = (int) $lastDate->format('Y');
        $accountController = HumanResources_Controller_Account::getInstance();
        
        while ($firstYear <= $lastYear) {
            $account = new HumanResources_Model_Account(array(
                'employee_id' => $_createdRecord->getId(),
                'year'        => $firstYear
            ));
            $accountController->create($account);
            $firstYear++;
        }
    }
    
    /**
     * transforms arrays to their ids
     * 
     * @param unknown $_record
     */
    protected function _recordArraysToId($_record)
    {
        if (is_array($_record->account_id)) {
            $_record->account_id = $_record->account_id['accountId'];
        }
    }
    
    /**
     * inspect creation of one record (before create)
     *
     * @param   Tinebase_Record_Interface $_record
     * @return  void
     */
    protected function _inspectBeforeCreate(Tinebase_Record_Interface $_record)
    {
        $this->_checkContractsOverlap($_record);
        $this->_recordArraysToId($_record);
    }

    /**
     * checks on save or update if contracts overlap
     * 
     * @param Tinebase_Record_Interface $record
     */
    protected function _checkContractsOverlap($record)
    {
        if ($record->contracts) {
            // get intervals
            $intervals = array();
            foreach ($record->contracts as $contract) {
                if (is_array($contract)) {
                    $contract['employee_id'] = $record->getId();
                    $contract = new HumanResources_Model_Contract($contract);
                }
                if ($contract->start_date) {
                    $intervals[] = array(
                        'start' => $contract->start_date,
                        'stop' => $contract->end_date ? $contract->end_date : NULL,
                        'record' => $contract
                    );
                }
            }
            
            if (! count($intervals)) {
                return;
            }
            
            // sort by start date
            uasort($intervals, function($a, $b) {
                // throw exception here if start dates are the same
                if ($a['start'] == $b['start']) {
                    throw new HumanResources_Exception_ContractOverlap();
                }
                return ($a['start'] < $b['start']) ? -1 : 1;
            });
            
            // compare them sorted
            $lastInterval = array_shift($intervals);
            foreach ($intervals as $interval) {
                // check overlapping
                if ($interval['stop'] == NULL ? FALSE
                    : ($lastInterval['stop'] == $interval['stop'] ? TRUE
                    : ($lastInterval['stop'] > $interval['start'] ? TRUE
                    : FALSE))) {
                        throw new HumanResources_Exception_ContractOverlap();
                }
                $lastInterval = $interval;
            }
        }
    }

    /**
     * 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)
    {
        $this->_duplicateCheck($_record);
        $this->_checkContractsOverlap($_record);
        $this->_recordArraysToId($_record);
        HumanResources_Controller_Contract::getInstance()->setEmployee($_record);
    }

    protected function _inspectAfterSetRelatedDataUpdate($updatedRecord, $record, $currentRecord)
    {
        parent::_inspectAfterSetRelatedDataUpdate($updatedRecord, $record, $currentRecord);

        if ($updatedRecord->employment_end && $updatedRecord->employment_end !== $currentRecord->employment_end) {
            if (($contract = HumanResources_Controller_Contract::getInstance()->getValidContract($updatedRecord->employment_end)) &&
                    (!$contract->end_date || $updatedRecord->employment_end->isEarlier($contract->end_date))) {
                $contract->end_date = clone $updatedRecord->employment_end;
                HumanResources_Controller_Contract::getInstance()->update($contract);
            }
            HumanResources_Controller_Account::getInstance()->deleteByFilter(
                Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_Account::class, [
                    ['field' => 'employee_id', 'operator' => 'equals', 'value' => $updatedRecord->getId()],
                    ['field' => 'year', 'operator' => 'greater', 'value' => $updatedRecord->employment_end->format('Y')],
                ]));
        }
    }

    /**
     * delete linked objects (notes, relations, ...) of record
     *
     * @param Tinebase_Record_Interface $_record
     * @param bool $_purgeNow
     */
    protected function _deleteLinkedObjects(Tinebase_Record_Interface $_record, bool $_purgeNow = false)
    {
        // use textfilter for employee_id 
        // delete accounts
        $oldRequestContext = HumanResources_Controller_Account::getInstance()->getRequestContext() ?? [];
        $filter = new HumanResources_Model_AccountFilter(array());
        $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'employee_id', 'operator' => 'equals', 'value' => $_record->getId())));
        if ($_purgeNow) {
            HumanResources_Controller_Account::getInstance()->setRequestContext(array_merge($oldRequestContext, [self::RC_PURGE_DATE => self::RC_PURGE_DATE_NOW]));
        }
        try {
            HumanResources_Controller_Account::getInstance()->deleteByFilter($filter);
        } finally {
            HumanResources_Controller_Account::getInstance()->setRequestContext($oldRequestContext);
        }
        
        parent::_deleteLinkedObjects($_record, $_purgeNow);
    }

    /**
     * returns the highest employee number of all employees
     * 
     * @return integer
     */
    public function getLastEmployeeNumber()
    {
        $filter = new HumanResources_Model_EmployeeFilter();
        $pagination = new Tinebase_Model_Pagination(array("sort" => "number","dir" => "DESC"));
        
        if ($employee = $this->search($filter, $pagination)->getFirstRecord()) {
            return (int) $employee->number;
        } else {
            return 0;
        }
    }
    
    /**
     * returns employees currently belonging to the given cost center (id)
     * 
     * @param string|Tinebase_Model_EvaluationDimensionItem $costCenterId
     * @return Tinebase_Record_RecordSet|NULL
     */
    public function getEmployeesByCostCenter($costCenterId) {
        $costCenterId = is_string($costCenterId) ? $costCenterId : $costCenterId->getId();

        $ccController = HumanResources_Controller_CostCenter::getInstance();
        $now = Tinebase_DateTime::now();
        
        $filter = new HumanResources_Model_CostCenterFilter();
        $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'cost_center_id', 'operator' => 'equals', 'value' => $costCenterId)));
        $filter->addFilter(new Tinebase_Model_Filter_Date(array('field' => 'start_date', 'operator' => 'before', 'value' => $now)));
        
        $hrccs = $ccController->search($filter);
        
        if ($hrccs->count()) {
            $filter = new HumanResources_Model_EmployeeFilter(array(
                array('field' => 'id', 'operator' => 'in', 'value' => $hrccs->employee_id)
            ), 'AND');
            
            $filteredEmployees = new Tinebase_Record_RecordSet('HumanResources_Model_Employee');
            
            $employees = $this->search($filter);
            
            foreach($employees as $employee) {
                $subCC = $hrccs->filter('employee_id', $employee->getId());
                if ($subCC->sort('start_date', 'DESC')->getFirstRecord()->start_date <= $now) {
                    $filteredEmployees->addRecord($employee);
                }
            }
            
            return $filteredEmployees;
            
        } else {
            return NULL;
        }
    }
    
    /**
     * transfers user accounts to employee records
     * 
     * @param boolean $_deletePrivateInfo should private information be removed from contacts
     */
    public function transferUserAccounts($_deletePrivateInfo = FALSE, $_feastCalendarId = NULL, $_workingTimeModelId = NULL, $_vacationDays = NULL, $cliCall = FALSE)
    {
        $lastNumber = $this->getLastEmployeeNumber();
        
        // get all active accounts
        $filter = new Addressbook_Model_ContactFilter(array(
                array('field' => 'type', 'operator' => 'equals', 'value' => 'user'),
                array('field' => 'showDisabled', 'operator' => 'equals', 'value' => 1),
            )
        );
        
        $accounts = Addressbook_Controller_Contact::getInstance()->search($filter);
        $nextNumber = $lastNumber + 1;
        
        $countCreated = 0;

        $division = HumanResources_Controller_Division::getInstance()->getAll()->getFirstRecord();

        foreach ($accounts as $account) {
            $filter = new HumanResources_Model_EmployeeFilter(array(array(
                'field' => 'account_id', 'operator' => 'equals', 'value' => $account->account_id
            )), 'AND');
            
            // if not already exists
            if (($lastNumber == 0) || ($this->search($filter)->count() === 0)) {
                
                $employee = new HumanResources_Model_Employee(array(
                    'number'              => $nextNumber,
                    'account_id'          => $account->account_id,
                    'countryname'         => $account->adr_two_countryname,
                    'locality'            => $account->adr_two_locality,
                    'postalcode'          => $account->adr_two_postalcode,
                    'region'              => $account->adr_two_region,
                    'street'              => $account->adr_two_street,
                    'street2'             => $account->adr_two_street2,
                    'email'               => $account->email_home,
                    'tel_home'            => $account->tel_home,
                    'tel_cell'            => $account->tel_cell_private,
                    'n_given'             => $account->n_given,
                    'n_family'            => $account->n_family,
                    'n_fn'                => $account->n_fn,
                    'bday'                => $account->bday,
                    'bank_account_holder' => $account->n_fn,
                    'employment_begin'    => Tinebase_DateTime::now()->subYear(1),
                    'division_id'         => $division->getId(),
                ));
                
                if ($_feastCalendarId && $_workingTimeModelId && $_vacationDays) {
                    $contract = $this->createContractDataForEmployee(array(
                        'feastCalendarId' => $_feastCalendarId,
                        'workingTimeModelId' => $_workingTimeModelId,
                        'vacationDays' => $_vacationDays,
                    ), $cliCall);
                    $employee->contracts = array($contract);
                }
                
                $countCreated++;
                
                if ($cliCall) {
                    echo 'Creating Employee "'. $account->n_fn . '"' . chr(10);
                }
                
                $this->create($employee);
                $nextNumber++;
                
            } else {
                if ($cliCall) {
                    echo 'Employee "'. $account->n_fn . '" already exists. Skipping...' . chr(10);
                }
            }
            
            if ($_deletePrivateInfo) {
                $account->adr_two_countryname = NULL;
                $account->adr_two_locality = NULL;
                $account->adr_two_postalcode = NULL;
                $account->adr_two_region = NULL;
                $account->adr_two_street = NULL;
                $account->adr_two_street2 = NULL;
                $account->email_home = NULL;
                $account->tel_home = NULL;
                $account->tel_cell_private = NULL;
                // do not remove contact image
                $account->jpegphoto = NULL;
                
                if ($cliCall) {
                    echo 'Removing private information of employee "'. $account->n_fn .'"' . chr(10);
                }
                Addressbook_Controller_Contact::getInstance()->update($account);
            }
        }
        if ($cliCall) {
            echo 'Created ' . $countCreated . ' employees.' . chr(10);
            echo 'Transfer OK' . chr(10);
        }
    }
    
    /**
     * create contract data
     * 
     * @param array $contractData
     * @param boolean $cliCall
     * @return array
     */
    public function createContractDataForEmployee($contractData = array(), $cliCall = FALSE)
    {
        if (isset($contractData['feastCalendarId'])) {
            try {
                $feastCalendar = Tinebase_Controller_BankHolidayCalendar::getInstance()->get($contractData['feastCalendarId']);
            } catch (Exception $e) {
                if ($cliCall) {
                    die('The Calendar with the id ' . $contractData['feastCalendarId'] . ' could not be found!' . chr(10));
                } else {
                    throw $e;
                }
            }
            if ($cliCall) {
                echo 'Found Calendar ' . $feastCalendar->name . chr(10);
            }
        } else {
            $feastCalendar = NULL;
        }
        
        if (isset($contractData['workingTimeModelId'])) {
            try {
                $workingTimeModel = HumanResources_Controller_WorkingTimeScheme::getInstance()->get($contractData['workingTimeModelId']);
            } catch (Exception $e) {
                if ($cliCall) {
                    die('The Working Time Schema with the id ' . $contractData['workingTimeModelId'] . ' could not be found!' . chr(10));
                } else {
                    throw $e;
                }
            }
            if ($cliCall) {
                echo 'Found Working Time Schema "' . $workingTimeModel->title . '"' . chr(10);
            }
        } else {
            $workingTimeModel = NULL;
        }
        
        return array(
            'feast_calendar_id'  => $feastCalendar ? $feastCalendar->toArray() : NULL,
            'vacation_days'      => isset($contractData['vacationDays']) ? $contractData['vacationDays'] : NULL,
            'workingtime_json'   => $workingTimeModel ? $workingTimeModel->json : '',
            'working_time_scheme'=> $workingTimeModel->getId(),
            'start_date'         => $contractData['startDate']
        );
    }

    public function getEmployeeByUser(Tinebase_Model_User|string $user): ?HumanResources_Model_Employee
    {
        $accountId = $user instanceof Tinebase_Model_User ? $user->getId() : $user;
        /** @var HumanResources_Model_Employee $employee */
        $employee = HumanResources_Controller_Employee::getInstance()->search(
            Tinebase_Model_Filter_FilterGroup::getFilterForModel(HumanResources_Model_Employee::class, [
                ['field' => 'account_id', 'operator' => 'equals', 'value' => $accountId]
            ]))->getFirstRecord();
        return $employee;
    }
}