Пример #1
0
 /**
  * gets the personal calendar of given user
  * 
  * @param  string $_userId
  * @return Tinebase_Model_Container
  */
 protected function _getPersonalCalendar($_userId)
 {
     if (!array_key_exists($_userId, $this->_personalCalendarCache)) {
         // get calendar by preference to ensure its the default personal
         $defaultCalendarId = Tinebase_Core::getPreference('Calendar')->getValueForUser(Calendar_Preference::DEFAULTCALENDAR, $_userId, Tinebase_Acl_Rights::ACCOUNT_TYPE_USER);
         $calendar = Tinebase_Container::getInstance()->getContainerById($defaultCalendarId);
         // detect if container just got created
         $isNewContainer = false;
         if ($calendar->creation_time instanceof DateTime) {
             $isNewContainer = $this->_migrationStartTime->isEarlier($calendar->creation_time);
         }
         if ($isNewContainer && $this->_config->setPersonalCalendarGrants || $this->_config->forcePersonalCalendarGrants) {
             // resolve grants based on user/groupmemberships
             $grants = $this->getGrantsByOwner('Calendar', $_userId);
             Tinebase_Container::getInstance()->setGrants($calendar->getId(), $grants, TRUE);
         }
         $this->_personalCalendarCache[$_userId] = $calendar;
     }
     return $this->_personalCalendarCache[$_userId];
 }
 /**
  * returns next occurrence _ignoring exceptions_ or NULL if there is none/not computable
  * 
  * NOTE: an ongoing event during $from [start, end[ is considered as next 
  * NOTE: for previous events on ongoing event is considered as previous
  *  
  * NOTE: computing the next occurrence of an open end rrule can be dangerous, as it might result
  *       in a endless loop. Therefore we only make a limited number of attempts before giving up.
  * 
  * @param  Calendar_Model_Event         $_event
  * @param  Tinebase_Record_RecordSet    $_exceptions
  * @param  Tinebase_DateTime            $_from
  * @param  Int                          $_which
  * @return Calendar_Model_Event|NULL
  */
 public static function computeNextOccurrence($_event, $_exceptions, $_from, $_which = 1)
 {
     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' $from = ' . $_from->toString());
     }
     if ($_which === 0 || $_event->dtstart >= $_from && $_event->dtend > $_from) {
         return $_event;
     }
     $freqMap = array(self::FREQ_DAILY => Tinebase_DateTime::MODIFIER_DAY, self::FREQ_WEEKLY => Tinebase_DateTime::MODIFIER_WEEK, self::FREQ_MONTHLY => Tinebase_DateTime::MODIFIER_MONTH, self::FREQ_YEARLY => Tinebase_DateTime::MODIFIER_YEAR);
     $rrule = new Calendar_Model_Rrule(NULL, TRUE);
     $rrule->setFromString($_event->rrule);
     $from = clone $_from;
     $until = clone $from;
     $interval = $_which * $rrule->interval;
     // we don't want to compute ourself
     $ownEvent = clone $_event;
     $ownEvent->setRecurId($_event->getId());
     $exceptions = clone $_exceptions;
     $exceptions->addRecord($ownEvent);
     $recurSet = new Tinebase_Record_RecordSet('Calendar_Model_Event');
     if ($_from->isEarlier($_event->dtstart)) {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' from is ealier dtstart -> given event is next occurrence');
         }
         return $_event;
     }
     $rangeDate = $_which > 0 ? $until : $from;
     if (!isset($freqMap[$rrule->freq])) {
         if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) {
             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Invalid RRULE:' . print_r($rrule->toArray(), true));
         }
         throw new Calendar_Exception('Invalid freq in RRULE: ' . $rrule->freq);
     }
     $rangeDate->add($interval, $freqMap[$rrule->freq]);
     $attempts = 0;
     if ($_event->rrule_until instanceof DateTime && Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Event rrule_until: ' . $_event->rrule_until->toString());
     }
     while (TRUE) {
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' trying to find next occurrence from ' . $from->toString());
         }
         if ($_event->rrule_until instanceof DateTime && $from->isLater($_event->rrule_until)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' passed rrule_until -> no further occurrences');
             }
             return NULL;
         }
         $until = $_event->rrule_until instanceof DateTime && $until->isLater($_event->rrule_until) ? clone $_event->rrule_until : $until;
         $recurSet->merge(self::computeRecurrenceSet($_event, $exceptions, $from, $until));
         $attempts++;
         // NOTE: computeRecurrenceSet also returns events during $from in some cases, but we need
         // to events later than $from.
         $recurSet = $recurSet->filter(function ($event) use($from) {
             return $event->dtstart >= $from;
         });
         if (count($recurSet) >= abs($_which)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " found next occurrence after {$attempts} attempt(s)");
             }
             break;
         }
         if ($attempts > count($exceptions) + 5) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " could not find the next occurrence after {$attempts} attempts, giving up");
             }
             return NULL;
         }
         $from->add($interval, $freqMap[$rrule->freq]);
         $until->add($interval, $freqMap[$rrule->freq]);
     }
     $recurSet->sort('dtstart', $_which > 0 ? 'ASC' : 'DESC');
     $nextOccurrence = $recurSet[abs($_which) - 1];
     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' $nextOccurrence->dtstart = ' . $nextOccurrence->dtstart->toString());
     }
     return $nextOccurrence;
 }
 /**
  * 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);
     }
 }
 /**
  * add calendar owner as attendee if not already set
  * 
  * @param string $calendarId
  * @param Tinebase_DateTime $from
  * @param Tinebase_DateTime $until
  * @param boolean $dry run
  * 
  * @return number of updated events
  */
 public function repairAttendee($calendarId, $from, $until, $dry = false)
 {
     $container = Tinebase_Container::getInstance()->getContainerById($calendarId);
     if ($container->type !== Tinebase_Model_Container::TYPE_PERSONAL) {
         throw new Calendar_Exception('Only allowed for personal containers!');
     }
     if ($container->owner_id !== Tinebase_Core::getUser()->getId()) {
         throw new Calendar_Exception('Only allowed for own containers!');
     }
     $updateCount = 0;
     while ($from->isEarlier($until)) {
         $endWeek = $from->getClone()->addWeek(1);
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Repairing period ' . $from . ' - ' . $endWeek);
         }
         // TODO we need to detect events with DECLINED/DELETED attendee
         $events = $this->_getEventsForPeriodAndCalendar($calendarId, $from, $endWeek);
         $from->addWeek(1);
         if (count($events) == 0) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' No events found');
             }
             continue;
         }
         foreach ($events as $event) {
             // add attendee if not already set
             if ($event->isRecurInstance()) {
                 // TODO get base event
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                     Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Skip recur instance ' . $event->toShortString());
                 }
                 continue;
             }
             $ownAttender = Calendar_Model_Attender::getOwnAttender($event->attendee);
             if (!$ownAttender) {
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                     Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Add missing attender to event ' . $event->toShortString());
                 }
                 $attender = new Calendar_Model_Attender(array('user_type' => Calendar_Model_Attender::USERTYPE_USER, 'user_id' => Tinebase_Core::getUser()->contact_id, 'status' => Calendar_Model_Attender::STATUS_ACCEPTED));
                 $event->attendee->addRecord($attender);
                 if (!$dry) {
                     $this->update($event);
                 }
                 $updateCount++;
             }
         }
     }
     return $updateCount;
 }
Пример #5
0
 /**
  * returns next occurrence _ignoring exceptions_ or NULL if there is none/not computable
  * 
  * NOTE: computing the next occurrence of an open end rrule can be dangoures, as it might result
  *       in a endless loop. Therefore we only make a limited number of attempts before giving up.
  * 
  * @param  Calendar_Model_Event         $_event
  * @param  Tinebase_Record_RecordSet    $_exceptions
  * @param  Tinebase_DateTime            $_from
  * @param  Int                          $_which
  * @return Calendar_Model_Event
  */
 public static function computeNextOccurrence($_event, $_exceptions, $_from, $_which = 1)
 {
     $freqMap = array(self::FREQ_DAILY => Tinebase_DateTime::MODIFIER_DAY, self::FREQ_WEEKLY => Tinebase_DateTime::MODIFIER_WEEK, self::FREQ_MONTHLY => Tinebase_DateTime::MODIFIER_MONTH, self::FREQ_YEARLY => Tinebase_DateTime::MODIFIER_YEAR);
     $rrule = new Calendar_Model_Rrule(NULL, TRUE);
     $rrule->setFromString($_event->rrule);
     $from = clone $_from;
     $until = clone $from;
     $interval = $_which * $rrule->interval;
     // we don't want to compute ourself
     $ownEvent = clone $_event;
     $ownEvent->setRecurId();
     $_exceptions->addRecord($ownEvent);
     $recurSet = new Tinebase_Record_RecordSet('Calendar_Model_Event');
     if ($_from->isEarlier($_event->dtstart)) {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' from is ealier dtstart -> given event is next occurrence');
         }
         return $_event;
     }
     $until->add($interval, $freqMap[$rrule->freq]);
     $attempts = 0;
     while (TRUE) {
         if ($_event->rrule_until instanceof DateTime && $from->isLater($_event->rrule_until)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' passed rrule_until -> no furthor occurrences');
             }
             return NULL;
         }
         $until = $_event->rrule_until instanceof DateTime && $until->isLater($_event->rrule_until) ? $_event->rrule_until : $until;
         $recurSet->merge(self::computeRecurrenceSet($_event, $_exceptions, $from, $until));
         $attempts++;
         if (count($recurSet) >= $_which) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " found next occurrence after {$attempts} attempt(s)");
             }
             break;
         }
         if ($attempts > count($_exceptions) + 5) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " could not find the next occurrence after {$attempts} attempts, giving up");
             }
             return NULL;
         }
         $from->add($interval, $freqMap[$rrule->freq]);
         $until->add($interval, $freqMap[$rrule->freq]);
     }
     $recurSet->sort('dtstart', 'ASC');
     return $recurSet[$_which - 1];
 }