0010122: Changing contract results in wrong vacation days
authorAlexander Stintzing <a.stintzing@metaways.de>
Wed, 6 Aug 2014 12:30:32 +0000 (14:30 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 6 Aug 2014 14:35:48 +0000 (16:35 +0200)
On adding a contract to an employee, the vacation days won't be calculated properly

https://forge.tine20.org/mantisbt/view.php?id=10122

Change-Id: Ia28ca3deeff773ae685c32cee4a2fcce022edf08
Reviewed-on: http://gerrit.tine20.com/customers/941
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/HumanResources/ControllerTests.php
tests/tine20/HumanResources/JsonTests.php
tine20/HumanResources/Controller/Account.php
tine20/HumanResources/Controller/Contract.php
tine20/HumanResources/Frontend/Json.php

index 63c9882..0bc24d8 100644 (file)
@@ -157,7 +157,7 @@ class HumanResources_ControllerTests extends HumanResources_TestCase
         $contract->employee_id = $employee->getId();
         
         $contractController->create($contract);
-        $this->assertEquals(15, $contractController->calculateVacationDays($contract, $start, $stop));
+        $this->assertEquals(15, round($contractController->calculateVacationDays($contract, $start, $stop), 0));
 
         // test "getDatesToWorkOn"
         $contract->start_date = $start;
index ac1482f..e88e9b7 100644 (file)
@@ -851,4 +851,250 @@ class HumanResources_JsonTests extends HumanResources_TestCase
         
         $this->assertEquals('2014-04-01 00:00:00', $recordData['bday']);
     }
+    
+    /**
+     * test adding a contract with manually setting the end_date of the contract before
+     */
+    public function testAddContract()
+    {
+        $sdate = new Tinebase_DateTime('2013-01-01 00:00:00');
+        $sdate->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
+        $employee = $this->_getEmployee('rwright');
+    
+        $contractController = HumanResources_Controller_Contract::getInstance();
+        $employeeController = HumanResources_Controller_Employee::getInstance();
+        $employee = $employeeController->create($employee);
+        $contract = $this->_getContract($sdate);
+        $contract->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
+        $contract->employee_id = $employee->getId();
+        
+        $feastCalendar = $this->_getFeastCalendar();
+        $contract->feast_calendar_id = $feastCalendar->getId();
+        
+        $contract->start_date = $sdate;
+        $contractController->create($contract);
+    
+        $employeeJson = $this->_json->getEmployee($employee->getId());
+        
+        $accountController = HumanResources_Controller_Account::getInstance();
+        
+        // should not be created, exist already
+        $accountController->createMissingAccounts(2013, $employee);
+        $account = $accountController->getAll()->getFirstRecord();
+        
+        $employeeJson['vacation'] = array(array(
+            'account_id' => $account->getId(),
+            'type' => 'vacation',
+            'status' => 'ACCEPTED',
+            'freedays' => array(array('duration' => '1', 'date' => '2013-01-11 00:00:00')),
+        ));
+        
+        $employeeJson = $this->_json->saveEmployee($employeeJson);
+        
+        $this->assertEquals(1, count($employeeJson['vacation']));
+        
+        // manually set the end date and add a new contract
+        $employeeJson['contracts'][0]['end_date'] = '2013-05-31 00:00:00';
+        $employeeJson['contracts'][1] = array(
+            'start_date' => '2013-06-01 00:00:00', 
+            'workingtime_json' => '{"days": [8,8,8,8,8,0,0]}',
+            'vacation_days' => 27,
+            'feast_calendar_id' => $feastCalendar->getId()
+        );
+        
+        // no exception should be thrown
+        $employeeJson = $this->_json->saveEmployee($employeeJson);
+        $this->assertEquals(2, count($employeeJson['contracts']));
+        
+        // an exception should be thrown
+        $employeeJson['contracts'][0]['vacation_days'] = 31;
+        $this->setExpectedException('HumanResources_Exception_ContractNotEditable');
+        
+        $employeeJson = $this->_json->saveEmployee($employeeJson);
+    }
+    
+    /**
+     * @see: https://forge.tine20.org/mantisbt/view.php?id=10122
+     */
+    public function testAlternatingContracts()
+    {
+        $date = Tinebase_DateTime::now()->setDate(2014, 1, 1)->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
+        $employee = $this->_getEmployee('unittest');
+        
+        $employee->employment_begin = clone $date;
+        
+        $contract1 = $this->_getContract();
+        $contract1->start_date = clone $date; // 1.1.2014
+        $date->addMonth(7)->subDay(1); 
+        $contract1->end_date = clone $date; // 31.7.2014 
+        $contract1->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
+        $contract1->vacation_days = 27;
+        $date->addDay(1); // 1.8.2014
+        $contract2 = $this->_getContract();
+        $contract2->start_date = clone $date;
+        $contract2->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
+        $contract2->vacation_days = 30;
+        
+        $recordData = $employee->toArray();
+        $recordData['contracts'] = array($contract1->toArray(), $contract2->toArray());
+        $recordData = $this->_json->saveEmployee($recordData);
+        
+        $recordData['vacation'] = array(
+            array()
+        );
+        
+        $res = $this->_json->searchAccounts(array(
+            array('field' => 'year', 'operator' => 'equals', 'value' => '2014')
+        ), array());
+        
+        $account = $res['results'][0];
+        $date->subDay(1); // 31.7.2014
+        
+        $extraFreeTime = HumanResources_Controller_ExtraFreeTime::getInstance()->create(new HumanResources_Model_ExtraFreeTime(array(
+            'account_id' => $account['id'],
+            'days' => 4,
+            'expires' => clone $date,
+            'type' => 'payed'
+        )));
+        
+        $res = $this->_json->getFeastAndFreeDays($recordData['id'], 2014);
+        
+        // at this point, vacation days are not created, so the extra freetime is expired
+        $this->assertEquals(28, $res['results']['remainingVacation']);
+        
+        // create vacation days
+        $day = Tinebase_DateTime::now()->setDate(2014, 1, 2)->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
+        $newFreeTime = array(
+            'account_id' => $account['id'],
+            'employee_id' => $recordData['id'],
+            'type' => 'vacation',
+            'status' => 'ACCEPTED',
+            'firstday_date' => $day->toString()
+        );
+        
+        $newFreeTime['freedays'] = array(
+            array('duration' => '1', 'date' => $day->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+        );
+        
+        $newFreeTime['days_count']   = 2;
+        $newFreeTime['lastday_date'] = $day->toString();
+        
+        $this->_json->saveFreeTime($newFreeTime);
+        
+        // create vacation days
+        $day = Tinebase_DateTime::now()->setDate(2014, 6, 10)->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
+        $newFreeTime = array(
+            'account_id' => $account['id'],
+            'employee_id' => $recordData['id'],
+            'type' => 'vacation',
+            'status' => 'ACCEPTED',
+            'firstday_date' => $day->toString()
+        );
+        
+        $newFreeTime['freedays'] = array(
+            array('duration' => '1', 'date' => $day->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+        );
+        
+        $newFreeTime['days_count']   = 4;
+        $newFreeTime['lastday_date'] = $day->toString();
+        
+        $this->_json->saveFreeTime($newFreeTime);
+        
+        
+        // create vacation days
+        $day = Tinebase_DateTime::now()->setDate(2014, 7, 28)->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
+        $newFreeTime = array(
+            'account_id' => $account['id'],
+            'employee_id' => $recordData['id'],
+            'type' => 'vacation',
+            'status' => 'ACCEPTED',
+            'firstday_date' => $day->toString()
+        );
+        
+        $newFreeTime['freedays'] = array(
+            array('duration' => '1', 'date' => $day->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+            array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+        );
+        
+        $newFreeTime['days_count']   = 5;
+        $newFreeTime['lastday_date'] = $day->toString();
+        
+        $this->_json->saveFreeTime($newFreeTime);
+        
+        // create sickness days
+        $day = Tinebase_DateTime::now()->setDate(2014, 1, 21)->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
+        $newFreeTime = array(
+            'account_id' => $account['id'],
+            'employee_id' => $recordData['id'],
+            'type' => 'sickness',
+            'status' => "EXCUSED",
+            'firstday_date' => $day->toString()
+        );
+        
+        $newFreeTime['freedays'] = array(
+                array('duration' => '1', 'date' => $day->toString()),
+                array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+                array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+                array('duration' => '1', 'date' => $day->addDay(1)->toString()),
+        );
+        
+        $day->addDay(2);
+        
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        
+        $day->addDay(2);
+        
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        $newFreeTime['freedays'][] = array('duration' => '1', 'date' => $day->addDay(1)->toString());
+        
+        $newFreeTime['days_count']   = 14;
+        $newFreeTime['lastday_date'] = $day->toString();
+        
+        $this->_json->saveFreeTime($newFreeTime);
+        
+        // create sickness days
+        $day = Tinebase_DateTime::now()->setDate(2014, 1, 6)->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
+        $newFreeTime = array(
+                'account_id' => $account['id'],
+                'employee_id' => $recordData['id'],
+                'type' => 'sickness',
+                'status' => "UNEXCUSED",
+                'firstday_date' => $day->toString()
+        );
+        
+        $newFreeTime['freedays'] = array(
+                array('duration' => '1', 'date' => $day->toString()),
+        );
+        
+        $this->_json->saveFreeTime($newFreeTime);
+        
+        $res = $this->_json->getFeastAndFreeDays($recordData['id'], 2014);
+        
+        // at this point the extra freetime has been taken and is not expired
+        // 28 + 4 - 11 = 21
+        $this->assertEquals(21, $res['results']['remainingVacation']);
+        
+        $account = $this->_json->getAccount($account['id']);
+        
+        $this->assertEquals(32, $account['possible_vacation_days']);
+        $this->assertEquals(0, $account['expired_vacation_days']);
+        $this->assertEquals(21, $account['remaining_vacation_days']);
+        $this->assertEquals(11, $account['taken_vacation_days']);
+        $this->assertEquals(14, $account['excused_sickness']);
+        $this->assertEquals(1, $account['unexcused_sickness']);
+    }
 }
index cafc1a0..81d4e75 100644 (file)
@@ -128,8 +128,7 @@ class HumanResources_Controller_Account extends Tinebase_Controller_Record_Abstr
         $feastDays = $this->_contractController->getFeastDays($contracts, $yearBegins, $yearEnds);
         
         // find out vacation days by contract(s) and interval
-        $possibleVacationDays = $this->_contractController->calculateVacationDays($contracts, $yearBegins, $yearEnds);
-        
+        $possibleVacationDays = round($this->_contractController->calculateVacationDays($contracts, $yearBegins, $yearEnds), 0);
         // find out free days (vacation, sickness)
         $freetimeController = HumanResources_Controller_FreeTime::getInstance();
         $employeeId = is_object($account->employee_id) ? $account->employee_id->getId() : $account->employee_id;
index 7991c5d..1e76d2e 100644 (file)
@@ -228,32 +228,32 @@ class HumanResources_Controller_Contract extends Tinebase_Controller_Record_Abst
      * @param HumanResources_Model_Contract|Tinebase_Record_RecordSet $contracts
      * @param Tinebase_DateTime $firstDate
      * @param Tinebase_DateTime $lastDate
-     * @return integer
+     * @return float
      */
-    public function calculateVacationDays($contracts, Tinebase_DateTime $firstDate, Tinebase_DateTime $lastDate)
+    public function calculateVacationDays($contracts, Tinebase_DateTime $gFirstDate, Tinebase_DateTime $gLastDate)
     {
         $contracts = $this->_convertToRecordSet($contracts);
         
         $sum = 0;
         
         foreach($contracts as $contract) {
-            
-            $firstDate = $this->_getFirstDate($contract, $firstDate);
-            $lastDate = $this->_getLastDate($contract, $lastDate);
+            $firstDate = $this->_getFirstDate($contract, $gFirstDate);
+            $lastDate = $this->_getLastDate($contract, $gLastDate);
             
             // find out how many days the year does have
-            $januaryFirst = new Tinebase_DateTime($firstDate->format('Y') . '-01-01 00:00:00');
-            $decemberLast = new Tinebase_DateTime($firstDate->format('Y') . '-12-31 23:59:59');
+            $januaryFirst = Tinebase_DateTime::createFromFormat('Y-m-d H:i:s e', $firstDate->format('Y') . '-01-01 00:00:00 ' . Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
+            $decemberLast = Tinebase_DateTime::createFromFormat('Y-m-d H:i:s e', $firstDate->format('Y') . '-12-31 23:59:59 ' . Tinebase_Core::get(Tinebase_Core::USERTIMEZONE));
             
-            $daysOfTheYear = ceil(($decemberLast->getTimestamp() - $januaryFirst->getTimestamp()) / 24 / 60 / 60);
+            $daysOfTheYear = ($decemberLast->getTimestamp() - $januaryFirst->getTimestamp()) / 24 / 60 / 60;
             
             // find out how many days the contract does have
-            $daysOfTheContract = ceil(($lastDate->getTimestamp() - $firstDate->getTimestamp()) / 24 / 60 / 60);
+            $daysOfTheContract = ($lastDate->getTimestamp() - $firstDate->getTimestamp()) / 24 / 60 / 60;
             
-            $sum = ($daysOfTheContract / $daysOfTheYear) * $contract->vacation_days;
+            $correl = $daysOfTheContract / $daysOfTheYear;
+            $sum = $sum + (($correl) * $contract->vacation_days);
         }
         
-        return round($sum);
+        return $sum;
     }
     
     /**
index 7dcc063..bf50cf5 100644 (file)
@@ -449,6 +449,7 @@ class HumanResources_Frontend_Json extends Tinebase_Frontend_Json_Abstract
             }
             
             $remainingVacation += $cController->calculateVacationDays($contract, $minDate, $maxDate);
+            
             // find out weekdays to disable
             if (is_object($json)) {
                 foreach($json->days as $index => $hours) {
@@ -469,6 +470,8 @@ class HumanResources_Frontend_Json extends Tinebase_Frontend_Json_Abstract
             $feastDays = array_merge($cController->getFeastDays($contract, $startDay, $stopDay), $feastDays);
         }
         
+        $remainingVacation = round($remainingVacation, 0);
+        
         // set time to 0
         foreach($feastDays as &$feastDay) {
             $feastDay->setTimezone(Tinebase_Core::get(Tinebase_Core::USERTIMEZONE))->setTime(0,0,0);
@@ -568,7 +571,7 @@ class HumanResources_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         // TODO: remove results property, just return results array itself
         return array(
             'results' => array(
-                'remainingVacation' => $remainingVacation,
+                'remainingVacation' => floor($remainingVacation),
                 'extraFreeTimes'    => $extraFreeTimes,
                 'vacationDays'      => $vacationDays->toArray(),
                 'sicknessDays'      => $sicknessDays->toArray(),