/**
  * skips date to (n'th next/previous) occurance of $_wday
  *
  * @param Tinebase_DateTime  $_date
  * @param int|string $_wday
  * @param int        $_n
  * @param bool       $_considerDateItself
  */
 public static function skipWday($_date, $_wday, $_n = +1, $_considerDateItself = FALSE)
 {
     $wdayDigit = is_int($_wday) ? $_wday : self::$WEEKDAY_DIGIT_MAP[$_wday];
     $wdayOffset = $_date->get('w') - $wdayDigit;
     if ($_n == 0) {
         throw new Exception('$_n must not be 0');
     }
     $direction = $_n > 0 ? 'forward' : 'backward';
     $weeks = abs($_n);
     if ($_considerDateItself && $wdayOffset == 0) {
         $weeks--;
     }
     switch ($direction) {
         case 'forward':
             if ($wdayOffset >= 0) {
                 $_date->addDay($weeks * 7 - $wdayOffset);
             } else {
                 $_date->addDay(abs($wdayOffset) + ($weeks - 1) * 7);
             }
             break;
         case 'backward':
             if ($wdayOffset > 0) {
                 $_date->subDay(abs($wdayOffset) + ($weeks - 1) * 7);
             } else {
                 $_date->subDay($weeks * 7 + $wdayOffset);
             }
             break;
     }
     return $_date;
 }
 /**
  * create auto invoices for one contract
  * 
  * @param Sales_Model_Contract $contract
  * @param Tinebase_DateTime $currentDate
  * @param boolean $merge
  */
 protected function _createAutoInvoicesForContract(Sales_Model_Contract $contract, Tinebase_DateTime $currentDate, $merge = false)
 {
     // set this current billing date (user timezone)
     $this->_currentBillingDate = clone $currentDate;
     $this->_currentBillingDate->setDate($this->_currentBillingDate->format('Y'), $this->_currentBillingDate->format('m'), 1);
     $this->_currentBillingDate->setTime(0, 0, 0);
     // check all prerequisites needed for billing of the contract
     if (!$this->_validateContract($contract)) {
         return false;
     }
     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Processing contract "' . $this->_currentBillingContract->number . '"');
     }
     // fire event to allow other applications do some work before billing
     $this->_firePrebillEvent();
     // find product aggregates of the current contract
     $productAggregates = $this->_findProductAggregates();
     // find month that needs to be billed next (note: _currentMonthToBill is the 01-01 00:00:00 of the next month, its the border, like last_autobill)
     $this->_currentMonthToBill = null;
     foreach ($productAggregates as $productAggregate) {
         if (null != $productAggregate->last_autobill) {
             $tmp = clone $productAggregate->last_autobill;
             $tmp->setDate($tmp->format('Y'), $tmp->format('m'), 1);
             $tmp->setTime(0, 0, 0);
             if (null == $this->_currentMonthToBill || $tmp->isLater($this->_currentMonthToBill)) {
                 $this->_currentMonthToBill = $tmp;
             }
         }
     }
     // this contract has no productAggregates, maybe just time accounts? use last invoice to find already billed month
     if (null == $this->_currentMonthToBill) {
         // find newest invoice of contract (probably can be done more efficient!)
         $invoiceRelations = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Contract', 'Sql', $contract->getId(), NULL, array(), TRUE, array('Sales_Model_Invoice'));
         // do not modify $newestInvoiceTime!!!! it does NOT get cloned!
         $newestInvoiceTime = null;
         $newestInvoice = null;
         foreach ($invoiceRelations as $invoiceRelation) {
             $invoiceRelation->related_record->setTimezone(Tinebase_Core::getUserTimezone());
             if (null == $newestInvoiceTime || $invoiceRelation->related_record->creation_time->isLater($newestInvoiceTime)) {
                 $newestInvoiceTime = $invoiceRelation->related_record->creation_time;
                 $newestInvoice = $invoiceRelation->related_record;
             }
         }
         if (null != $newestInvoice) {
             // we can only take the end_date because there are no product aggregates (that have a last_autobill set) in this contract, otherwise it might be one interval ahead!
             $this->_currentMonthToBill = clone $newestInvoice->end_date;
             $this->_currentMonthToBill->addDay(4);
             $this->_currentMonthToBill->subMonth(1);
             //$this->_currentMonthToBill->setTimezone(Tinebase_Core::getUserTimezone());
         }
     }
     $_addMonth = true;
     if (null == $this->_currentMonthToBill) {
         $this->_currentMonthToBill = clone $contract->start_date;
         $_addMonth = false;
     }
     $this->_currentMonthToBill->setTimezone(Tinebase_Core::getUserTimezone());
     $this->_currentMonthToBill->setDate($this->_currentMonthToBill->format('Y'), $this->_currentMonthToBill->format('m'), 1);
     $this->_currentMonthToBill->setTime(0, 0, 0);
     if ($_addMonth) {
         $this->_currentMonthToBill->addMonth(1);
     }
     $doSleep = false;
     if (($merge || $contract->merge_invoices) && $this->_currentMonthToBill->isEarlier($this->_currentBillingDate)) {
         $this->_currentMonthToBill = clone $this->_currentBillingDate;
     }
     while ($this->_currentMonthToBill->isEarlierOrEquals($this->_currentBillingDate)) {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' $this->_currentMonthToBill: ' . $this->_currentMonthToBill . ' $this->_currentBillingDate ' . $this->_currentBillingDate);
             foreach ($productAggregates as $productAggregate) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $productAggregate->id . ' ' . $productAggregate->last_autobill . ' ' . $productAggregate->interval);
             }
         }
         //required to have one sec difference in the invoice creation_time, can be optimized to look for milliseconds
         if ($doSleep) {
             sleep(1);
             $doSleep = false;
         }
         // prepare relations and find all billable accountables of the current contract
         list($relations, $billableAccountables) = $this->_prepareInvoiceRelationsAndFindBillableAccountables($productAggregates);
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' count $billableAccountables: ' . count($billableAccountables));
             foreach ($billableAccountables as $ba) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' accountable: ' . get_class($ba['ac']) . ' id: ' . $ba['ac']->getId());
             }
         }
         // find invoice positions and the first start date and last end date of all billables
         list($invoicePositions, $earliestStartDate, $latestEndDate) = $this->_findInvoicePositionsAndInvoiceInterval($billableAccountables);
         /**** TODO ****/
         // if there are no positions, no more bills need to be created,
         // but the last_autobill info is set, if the current date is later
         if ($invoicePositions->count() > 0) {
             // prepare invoice
             $invoice = new Sales_Model_Invoice(array('is_auto' => TRUE, 'description' => $this->_currentBillingContract->title . ' (' . $this->_currentMonthToBill->toString() . ')', 'type' => 'INVOICE', 'address_id' => $this->_currentBillingContract->billing_address_id, 'credit_term' => $this->_currentBillingCustomer['credit_term'], 'customer_id' => $this->_currentBillingCustomer['id'], 'costcenter_id' => $this->_currentBillingCostCenter->getId(), 'start_date' => $earliestStartDate, 'end_date' => $latestEndDate, 'positions' => $invoicePositions->toArray(), 'date' => clone $this->_currentMonthToBill, 'sales_tax' => 19));
             $invoice->relations = $relations;
             $invoice->setTimezone('UTC', TRUE);
             // create invoice
             $invoice = $this->create($invoice);
             $this->_autoInvoiceIterationResults[] = $invoice->getId();
             $this->_autoInvoiceIterationDetailResults[] = $invoice;
             $paToUpdate = array();
             // conjunct billables with invoice, find out which productaggregates to update
             foreach ($billableAccountables as $ba) {
                 $ba['ac']->conjunctInvoiceWithBillables($invoice);
                 if ($ba['pa']->getId()) {
                     $paToUpdate[$ba['pa']->getId()] = $ba['pa'];
                 }
             }
             foreach ($paToUpdate as $paId => $productAggregate) {
                 $firstBill = !$productAggregate->last_autobill;
                 $lab = $productAggregate->last_autobill ? clone $productAggregate->last_autobill : ($productAggregate->start_date ? clone $productAggregate->start_date : clone $this->_currentBillingContract->start_date);
                 $lab->setTimezone(Tinebase_Core::getUserTimezone());
                 $lab->setDate($lab->format('Y'), $lab->format('m'), 1);
                 $lab->setTime(0, 0, 0);
                 if (!$firstBill) {
                     $lab->addMonth($productAggregate->interval);
                 } else {
                     if ($productAggregate->billing_point == 'end') {
                         // if first bill, add interval on billing_point end
                         $lab->addMonth($productAggregate->interval);
                     }
                 }
                 while ($this->_currentMonthToBill->isLater($lab)) {
                     $lab->addMonth($productAggregate->interval);
                 }
                 if ($lab->isLater($this->_currentMonthToBill)) {
                     $lab->subMonth($productAggregate->interval);
                 }
                 $productAggregate->last_autobill = $lab;
                 $productAggregate->setTimezone('UTC');
                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating last_autobill of "' . $productAggregate->getId() . '": ' . $lab->__toString());
                 }
                 Sales_Controller_ProductAggregate::getInstance()->update($productAggregate);
                 $productAggregate->setTimezone(Tinebase_Core::getUserTimezone());
             }
             $doSleep = true;
         } elseif (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' $invoicePositions->count() == false');
         }
         $this->_currentMonthToBill->addMonth(1);
     }
 }
 /**
  * tests account summary and getFeastAndFreeDays method calculation
  */
 public function testCalculation()
 {
     $employmentBegin = new Tinebase_DateTime('2012-12-15');
     $employmentChange = new Tinebase_DateTime('2014-01-01');
     $employmentEnd = new Tinebase_DateTime('2014-06-30');
     $referenceDate = new Tinebase_DateTime('2013-10-10');
     $contractController = HumanResources_Controller_Contract::getInstance();
     $employeeController = HumanResources_Controller_Employee::getInstance();
     $contractBackend = new HumanResources_Backend_Contract();
     $employee = $this->_getEmployee('unittest');
     $employee->employment_begin = $employmentBegin;
     $employee->employment_end = $employmentEnd;
     $contract1 = $this->_getContract();
     $contract1->start_date = $employmentBegin;
     $contract1->workingtime_json = '{"days": [8,8,8,8,8,0,0]}';
     $contract1->vacation_days = 25;
     $contract2 = $this->_getContract();
     $contract2->start_date = $employmentChange;
     $contract2->end_date = $employmentEnd;
     $contract2->workingtime_json = '{"days": [4,4,4,4,4,4,4]}';
     $rs = new Tinebase_Record_RecordSet('HumanResources_Model_Contract');
     $rs->addRecord($contract1);
     $rs->addRecord($contract2);
     $employee->contracts = $rs;
     $employee = $employeeController->create($employee);
     $json = new HumanResources_Frontend_Json();
     $accountController = HumanResources_Controller_Account::getInstance();
     $accountsFilter = array(array('field' => "employee_id", 'operator' => "AND", 'value' => array(array('field' => ':id', 'operator' => 'equals', 'value' => $employee->getId()))));
     // should not be created, exist already
     $accountController->createMissingAccounts(2013, $employee);
     $accountController->createMissingAccounts(2014, $employee);
     // create feast days
     $feastDays = array('01-01', '03-29', '04-01', '05-01', '05-09', '05-20', '10-03', '12-25', '12-26', '12-31');
     foreach ($feastDays as $day) {
         $date = new Tinebase_DateTime('2013-' . $day . ' 12:00:00');
         $this->_createFeastDay($date);
         $date = new Tinebase_DateTime('2014-' . $day . ' 12:00:00');
         $this->_createFeastDay($date);
     }
     // what about the holy evening? it's recurring
     // @see 0009114: Freetime edit dialog doesn't calculate recurring feast days
     //      https://forge.tine20.org/mantisbt/view.php?id=9114
     $this->_createRecurringFeastDay(new Tinebase_DateTime('2011-12-24'));
     $result = $json->searchAccounts($accountsFilter, array('sort' => 'year', 'dir' => 'DESC'));
     $this->assertEquals('3', $result['totalcount'], 'Three accounts should have been found!');
     $accountId2013 = $result['results'][1]['id'];
     $account2013 = $json->getAccount($accountId2013);
     $accountId2014 = $result['results'][0]['id'];
     $account2014 = $json->getAccount($accountId2014);
     $this->assertEquals(25, $account2013['possible_vacation_days']);
     $this->assertEquals(250, $account2013['working_days']);
     $this->assertEquals(15, $account2014['possible_vacation_days']);
     $this->assertEquals(175, $account2014['working_days']);
     // add 5 extra free days to the account with different expiration dates, 2 days aren't expired already
     $tomorrow = Tinebase_DateTime::now();
     $tomorrow->addDay(1);
     $yesterday = Tinebase_DateTime::now();
     $yesterday->subDay(1);
     $eft1 = new HumanResources_Model_ExtraFreeTime(array('days' => 2, 'account_id' => $accountId2013, 'expires' => $tomorrow));
     $eft2 = new HumanResources_Model_ExtraFreeTime(array('days' => 3, 'account_id' => $accountId2013, 'expires' => $yesterday));
     $eftController = HumanResources_Controller_ExtraFreeTime::getInstance();
     $eftController->create($eft1);
     $eftController->create($eft2);
     $account2013 = $json->getAccount($accountId2013);
     $this->assertEquals(27, $account2013['possible_vacation_days']);
     $this->assertEquals(27, $account2013['remaining_vacation_days']);
     $this->assertEquals(250, $account2013['working_days']);
     $this->assertEquals(3, $account2013['expired_vacation_days'], 'There should be 3 expired vacation days at first!');
     // the extra freetimes added to the account2013 should not affect account 2014
     $account2014 = $json->getAccount($accountId2014);
     $this->assertEquals(15, $account2014['possible_vacation_days']);
     $this->assertEquals(175, $account2014['working_days']);
     // now add 3 vacation days before the expiration day of the second extra free time
     // #8202: Allow to book remaining free days from last years' account, respect expiration
     $freetime = array('account_id' => $accountId2013, 'employee_id' => $employee->getId(), 'type' => 'vacation', 'status' => 'ACCEPTED', 'firstday_date' => $yesterday->subWeek(1)->toString());
     $nd = $referenceDate->subMonth(2);
     $freetime['freedays'] = array(array('duration' => '1', 'date' => $nd->toString()), array('duration' => '1', 'date' => $nd->addDay(1)->toString()), array('duration' => '1', 'date' => $nd->addDay(1)->toString()));
     $freetime = $this->_json->saveFreeTime($freetime);
     // so the 3 days haven't been expired, because 3 vacation days have been booked before
     $account2013 = $json->getAccount($accountId2013);
     $this->assertEquals(30, $account2013['possible_vacation_days'], 'There should be 30 possible vacation days after all!');
     $this->assertEquals(27, $account2013['remaining_vacation_days'], 'There should be 27 remaining vacation days after all!');
     $this->assertEquals(0, $account2013['expired_vacation_days'], 'There should be no expired vacation days after all!');
     $this->assertEquals(3, $account2013['taken_vacation_days'], 'He took 3 vacation days');
     // test account filter for: employee_id and year
     $accountsFilter = array(array('field' => "employee_id", 'operator' => "AND", 'value' => array(array('field' => ':id', 'operator' => 'equals', 'value' => $employee->getId()))), array('field' => 'year', 'operator' => 'equals', 'value' => $account2013['year']));
     $result = $json->searchAccounts($accountsFilter, array());
     $this->assertEquals(1, $result['totalcount']);
     // test account quicksearch filter
     $qsFilter = array(array('field' => "query", 'operator' => "contains", 'value' => Tinebase_Core::getUser()->accountFirstName));
     $result = $json->searchAccounts($qsFilter, array());
     $this->assertEquals(3, $result['totalcount'], 'should find exactly 3 accounts');
     $qsFilter = array(array('field' => "query", 'operator' => "contains", 'value' => 'Adsmin'));
     $result = $json->searchAccounts($qsFilter, array());
     $this->assertEquals(0, $result['totalcount']);
     $refdate = clone $referenceDate;
     // now we test if adding a vacation with dates of 2013 but the account of 2014 works as expected
     $freetime = array('account_id' => $accountId2014, 'employee_id' => $employee->getId(), 'type' => 'vacation', 'status' => 'ACCEPTED', 'firstday_date' => $refdate, 'lastday_date' => $refdate->addDay(3)->toString(), 'days_count' => 3);
     $refdate = clone $referenceDate;
     $freetime['freedays'] = array(array('duration' => '1', 'date' => $referenceDate->toString()), array('duration' => '1', 'date' => $referenceDate->addDay(1)->toString()), array('duration' => '1', 'date' => $referenceDate->addDay(1)->toString()));
     $freetime = $this->_json->saveFreeTime($freetime);
     // the extra freetimes added to the account2014 should not affect account 2013
     $account2013 = $json->getAccount($accountId2013);
     $this->assertEquals(30, $account2013['possible_vacation_days']);
     $this->assertEquals(27, $account2013['remaining_vacation_days']);
     // but possible vacation days of the 2014 account should be reduced by 3
     $account2014 = $json->getAccount($accountId2014);
     $this->assertEquals(15, $account2014['possible_vacation_days']);
     $this->assertEquals(12, $account2014['remaining_vacation_days']);
     $this->assertEquals(175, $account2014['working_days']);
     // now let's test the getFeastAndFreeTimes method with the same fixtures
     $result = $this->_json->getFeastAndFreeDays($employee->getId(), "2013");
     $res = $result['results'];
     $this->assertEquals(27, $res['remainingVacation']);
     $this->assertEquals(5, $res['extraFreeTimes']['remaining']);
     $this->assertEquals(6, count($res['vacationDays']));
     $this->assertEquals(0, count($res['sicknessDays']));
     $this->assertEquals(104, count($res['excludeDates']));
     $this->assertEquals(NULL, $res['ownFreeDays']);
     $this->assertEquals(11, count($res['feastDays']));
     $this->assertEquals(1, count($res['contracts']));
     $this->assertEquals($employee->getId(), $res['employee']['id']);
     $this->assertEquals('2013-01-01 00:00:00', $res['firstDay']->toString());
     $this->assertEquals('2013-12-31 23:59:59', $res['lastDay']->toString());
     $day = Tinebase_DateTime::now()->setDate(2013, 9, 23)->setTime(0, 0, 0);
     $newFreeTime = array('account_id' => $accountId2013, 'employee_id' => $employee->getId(), '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()));
     $newFreeTime['days_count'] = 3;
     $newFreeTime['lastday_date'] = $day->toString();
     $this->_json->saveFreeTime($newFreeTime);
     $result = $this->_json->getFeastAndFreeDays($employee->getId(), "2013");
     $res = $result['results'];
     $this->assertEquals(9, count($res['vacationDays']));
     $this->assertEquals(24, $res['remainingVacation']);
     // overwrite last 2 days of previous vacation with sickness
     $day->subDay(1);
     $newFreeTime = array('account_id' => $accountId2013, 'employee_id' => $employee->getId(), 'type' => 'sickness', '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);
     $result = $this->_json->getFeastAndFreeDays($employee->getId(), "2013");
     $res = $result['results'];
     $this->assertEquals(7, count($res['vacationDays']));
     $this->assertEquals(2, count($res['sicknessDays']));
     $this->assertEquals(26, $res['remainingVacation']);
 }
Esempio n. 4
0
 public function testModifyReturnValue()
 {
     $dt = new Tinebase_DateTime('2010-11-25 12:11:00');
     $instance = $dt->addDay(-1);
     $this->assertEquals('Tinebase_DateTime', get_class($instance), 'wrong type');
 }