/** * the singleton pattern * * @return Timetracker_Controller_Timeaccount */ public static function getInstance() { if (self::$_instance === NULL) { self::$_instance = new Timetracker_Controller_Timeaccount(); } return self::$_instance; }
/** * search and show duplicate timeaccounts * * @return void */ public function searchDuplicateTimeaccounts() { $filter = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'showClosed', 'operator' => 'equals', 'value' => FALSE))); $duplicates = parent::_searchDuplicates(Timetracker_Controller_Timeaccount::getInstance(), $filter, 'title'); echo 'Found ' . count($duplicates) / 2 . ' Timeaccount duplicate(s):' . "\n"; print_r($duplicates); }
/** * creates shared tas */ protected function _createSharedTimeaccounts() { // create 2 timeaccounts for each cc $taNumber = 1; $userGroup = Tinebase_Group::getInstance()->getGroupByName('Users'); $developmentString = self::$_de ? 'Entwicklung' : 'Development'; if (!$userGroup) { die('Could not find userGroup "Users", stopping.'); } $grants = array(array('account_id' => $userGroup->getId(), 'account_type' => 'group', 'bookOwnGrant' => TRUE, 'viewAllGrant' => TRUE, 'bookAllGrant' => TRUE, 'manageBillableGrant' => TRUE, 'exportGrant' => TRUE, 'adminGrant' => TRUE)); $contractsIndex = 0; foreach ($this->_costCenters as $costcenter) { $this->_timeAccounts[$costcenter->getId()] = new Tinebase_Record_RecordSet('Timetracker_Model_Timeaccount'); $i = 0; while ($i < 2) { $i++; $ta = new Timetracker_Model_Timeaccount(array('number' => $taNumber, 'title' => Tinebase_Record_Abstract::generateUID(3), 'grants' => $grants, 'status' => 'billed', 'cleared_at' => $this->_clearedDate, 'budget' => NULL, 'description' => 'Created By Tine 2.0 DEMO DATA')); if ($costcenter->remark == 'Marketing' || $costcenter->remark == $developmentString) { $contract = $costcenter->remark == 'Marketing' ? $this->_contractsMarketing->getByIndex(rand(0, $this->_contractsMarketing->count() - 1)) : $this->_contractsDevelopment->getByIndex(rand(0, $this->_contractsDevelopment->count() - 1)); $ta->budget = $costcenter->remark == 'Marketing' ? 100 : NULL; $ta->relations = array(array('own_model' => 'Timetracker_Model_Timeaccount', 'own_backend' => 'SQL', 'own_id' => NULL, 'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING, 'related_model' => 'Sales_Model_CostCenter', 'related_backend' => Tasks_Backend_Factory::SQL, 'related_id' => $costcenter->getId(), 'type' => 'COST_CENTER'), array('own_model' => 'Timetracker_Model_Timeaccount', 'own_backend' => 'SQL', 'own_id' => NULL, 'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING, 'related_model' => 'Sales_Model_Contract', 'related_backend' => Tasks_Backend_Factory::SQL, 'related_id' => $contract->getId(), 'type' => 'TIME_ACCOUNT')); $ta->title = (self::$_de ? 'Zeitkonto mit ' : 'Timeaccount for ') . $contract->getTitle(); } else { $ta->title = (self::$_de ? 'Zeitkonto mit KST ' : 'Timeaccount for CC ') . $costcenter->getTitle(); $ta->relations = array(array('own_model' => 'Timetracker_Model_Timeaccount', 'own_backend' => 'SQL', 'own_id' => NULL, 'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING, 'related_model' => 'Sales_Model_CostCenter', 'related_backend' => Tasks_Backend_Factory::SQL, 'related_id' => $costcenter->getId(), 'type' => 'COST_CENTER')); } $this->_timeAccounts[$costcenter->getId()]->addRecord($this->_taController->create($ta)); $taNumber++; } } }
/** * calculate effective ta grants so the client doesn't need to calculate them * * @param array $_timesaccounts */ protected function _resolveTimeaccountGrants(Tinebase_Record_RecordSet $_timesaccounts) { $manageAllRight = Timetracker_Controller_Timeaccount::getInstance()->checkRight(Timetracker_Acl_Rights::MANAGE_TIMEACCOUNTS, FALSE); foreach ($_timesaccounts as $timeaccount) { $timeaccountGrantsArray = $timeaccount->account_grants; $modifyGrant = $manageAllRight || $timeaccountGrantsArray[Timetracker_Model_TimeaccountGrants::GRANT_ADMIN]; $timeaccountGrantsArray[Tinebase_Model_Grants::GRANT_READ] = true; $timeaccountGrantsArray[Tinebase_Model_Grants::GRANT_EDIT] = $modifyGrant; $timeaccountGrantsArray[Tinebase_Model_Grants::GRANT_DELETE] = $modifyGrant; $timeaccount->account_grants = $timeaccountGrantsArray; // also move the grants into the container_id property, as the clients expects records to // be contained in some kind of container where it searches the grants in $timeaccount->container_id = array('account_grants' => $timeaccountGrantsArray); } }
/** * inspects delete action * * @param array $_ids * @return array of ids to actually delete */ protected function _inspectDelete(array $_ids) { $records = $this->_backend->getMultiple($_ids); $records->setTimezone(Tinebase_Core::getUserTimezone()); $invoicePositionController = Sales_Controller_InvoicePosition::getInstance(); $contractController = Sales_Controller_Contract::getInstance(); foreach ($records as $record) { if (!$record->is_auto) { continue; } if ($record->cleared == 'CLEARED') { // cleared invoices must not be deleted throw new Sales_Exception_InvoiceAlreadyClearedDelete(); } else { // try to find a invoice after this one // there should be a contract $contractRelation = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Invoice', 'Sql', $record->getId(), NULL, array(), TRUE, array('Sales_Model_Contract'))->getFirstRecord(); if ($contractRelation) { $contract = $contractRelation->related_record; $contract->setTimezone(Tinebase_Core::getUserTimezone()); // get all invoices related to this contract. throw exception if a follwing invoice has been found $invoiceRelations = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Contract', 'Sql', $contract->getId(), NULL, array(), TRUE, array('Sales_Model_Invoice')); foreach ($invoiceRelations as $invoiceRelation) { $invoiceRelation->related_record->setTimezone(Tinebase_Core::getUserTimezone()); if ($record->getId() !== $invoiceRelation->related_record->getId() && $record->creation_time < $invoiceRelation->related_record->creation_time) { throw new Sales_Exception_DeletePreviousInvoice(); } } $this->_currentBillingContract = $contract; $productAggregates = $this->_findProductAggregates(); } else { if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) { Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Could not find contract relation -> skip contract handling'); } $contract = null; $productAggregates = array(); } // remove invoice_id from billables $filter = new Sales_Model_InvoicePositionFilter(array()); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $record->getId()))); $invoicePositions = $invoicePositionController->search($filter); $allModels = array_unique($invoicePositions->model); foreach ($allModels as $model) { if ($model == 'Sales_Model_ProductAggregate') { continue; } $filteredInvoicePositions = $invoicePositions->filter('model', $model); $billableControllerName = $model::getBillableControllerName(); $billableFilterName = $model::getBillableFilterName(); $filterInstance = new $billableFilterName(array()); $filterInstance->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $record->getId()))); $billableControllerName::getInstance()->updateMultiple($filterInstance, array('invoice_id' => NULL)); // set invoice ids of the timeaccounts if ($model == 'Timetracker_Model_Timeaccount') { $filterInstance = new Timetracker_Model_TimeaccountFilter(array()); $filterInstance->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $record->getId()))); Timetracker_Controller_Timeaccount::getInstance()->updateMultiple($filterInstance, array('invoice_id' => NULL)); } } // delete invoice positions $invoicePositionController->delete($invoicePositions->getId()); // set last_autobill a period back if ($contract) { //find the month of each productAggregate we have to set it back to $undoProductAggregates = array(); $paController = Sales_Controller_ProductAggregate::getInstance(); foreach ($invoicePositions as $inPos) { if ($inPos->model != 'Sales_Model_ProductAggregate') { continue; } //if we didnt find a month for the productAggreagte yet or if the month found is greater than the one we have at hands if (!isset($undoProductAggregates[$inPos->accountable_id]) || strcmp($undoProductAggregates[$inPos->accountable_id], $inPos->month) > 0) { $undoProductAggregates[$inPos->accountable_id] = $inPos->month; } } foreach ($productAggregates as $productAggregate) { if (!$productAggregate->last_autobill) { continue; } if (!isset($undoProductAggregates[$productAggregate->id])) { $product = $this->_cachedProducts->getById($productAggregate->product_id); if (!$product) { $product = Sales_Controller_Product::getInstance()->get($productAggregate->product_id); $this->_cachedProducts->addRecord($product); } if ($product->accountable == 'Sales_Model_Product' || $record->date != null && $record->date->isLater($productAggregate->last_autobill)) { continue; } $productAggregate->last_autobill->subMonth($productAggregate->interval); } else { $productAggregate->last_autobill = new Tinebase_DateTime($undoProductAggregates[$productAggregate->id] . '-01 00:00:00', Tinebase_Core::getUserTimezone()); if ($productAggregate->billing_point == 'begin') { $productAggregate->last_autobill->subMonth($productAggregate->interval); } if ($productAggregate->start_date && $productAggregate->last_autobill < $productAggregate->start_date) { $tmp = clone $productAggregate->start_date; $tmp->setTimezone(Tinebase_Core::getUserTimezone()); $tmp->setDate($tmp->format('Y'), $tmp->format('m'), 1); $tmp->setTime(0, 0, 0); if ($productAggregate->last_autobill < $tmp || $productAggregate->billing_point == 'end' && $productAggregate->last_autobill == $tmp) { $productAggregate->last_autobill = NULL; } } } $productAggregate->setTimezone('UTC'); $paController->update($productAggregate); $productAggregate->setTimezone(Tinebase_Core::getUserTimezone()); } } } } return $_ids; }
/** * tests the corret handling of the usertimezone in the date filter */ public function testDateIntervalFilter() { $taController = Timetracker_Controller_Timeaccount::getInstance(); $tsController = Timetracker_Controller_Timesheet::getInstance(); $dateString = '2014-07-14 00:15:00'; $date = new Tinebase_DateTime($dateString, Tinebase_Core::getUserTimezone()); $ta = $taController->create(new Timetracker_Model_Timeaccount(array('number' => '123', 'title' => 'test'))); $r = new Timetracker_Model_Timesheet(array('timeaccount_id' => $ta->getId(), 'account_id' => Tinebase_Core::getUser()->getId(), 'description' => 'lazy boring', 'start_date' => $date, 'duration' => 30)); $r->setTimezone('UTC'); $ts = $tsController->create($r); $filter = new Timetracker_Model_TimesheetFilter(array()); $dateFilter = new Tinebase_Model_Filter_DateMock(array('field' => 'start_date', 'operator' => "within", "value" => "weekThis")); $dateFilter::$testDate = $date; $filter->addFilter($dateFilter); $results = $tsController->search($filter); $this->assertEquals(1, $results->count()); }
/** * * @param array $recordData * @return Tinebase_Record_RecordSet */ protected function _createTimeaccounts($recordData = NULL) { $this->_timeaccountRecords = new Tinebase_Record_RecordSet('Timetracker_Model_Timeaccount'); $this->_timeaccountController = Timetracker_Controller_Timeaccount::getInstance(); if (!$recordData) { // ta for customer 1 and 2 is budgeted AND to bill foreach ($this->_customerRecords as $customer) { $this->_timeaccountRecords->addRecord($this->_timeaccountController->create(new Timetracker_Model_Timeaccount(array('title' => 'TA-for-' . $customer->name, 'description' => 'blabla', 'is_open' => 1, 'status' => $customer->name == 'Customer4' ? 'billed' : 'to bill', 'budget' => $customer->name == 'Customer3' ? null : 100), TRUE))); } } else { foreach ($recordData as $taArray) { $this->_timeaccountRecords->addRecord($this->_timeaccountController->create(new Timetracker_Model_Timeaccount($taArray, TRUE))); } } return $this->_timeaccountRecords; }
/** * get Timesheet (create timeaccount as well) * * @param array fields data * @param boolean force creation of the record * @return Timetracker_Model_Timesheet */ protected function _getTimesheet($_data = array(), $_forceCreation = false) { $defaultData = array('account_id' => Tinebase_Core::getUser()->getId(), 'description' => 'blabla', 'duration' => 30, 'timeaccount_id' => NULL, 'start_date' => NULL); $data = array_replace($defaultData, $_data); if ($data['timeaccount_id'] === NULL) { $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->create($this->_getTimeaccount()); $data['timeaccount_id'] = $timeaccount->getId(); } if ($data['start_date'] === NULL) { $data['start_date'] = Tinebase_DateTime::now()->toString('Y-m-d'); } $ts = new Timetracker_Model_Timesheet($data, TRUE); if ($_forceCreation) { $tsRec = $this->_json->saveTimesheet($ts->toArray()); $this->_lastCreatedRecord = $tsRec; } return $ts; }
/** * check grant for action * * @param Timetracker_Model_Timeaccount $_record * @param string $_action * @param boolean $_throw * @param string $_errorMessage * @param Timetracker_Model_Timesheet $_oldRecord * @return boolean * @throws Tinebase_Exception_AccessDenied * * @todo think about just setting the default values when user * hasn't the required grant to change the field (instead of throwing exception) */ protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessage = 'No Permission.', $_oldRecord = NULL) { $isAdmin = false; // users with MANAGE_TIMEACCOUNTS have all grants here if ($this->checkRight(Timetracker_Acl_Rights::MANAGE_TIMEACCOUNTS, FALSE) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Tinebase_Model_Grants::GRANT_ADMIN)) { $isAdmin = true; } // only TA managers are allowed to alter TS of closed TAs, but they have to confirm first that they really want to do it if ($_action != 'get') { $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->get($_record->timeaccount_id); if (!$timeaccount->is_open) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' This Timeaccount is already closed!'); } if ($isAdmin === true) { if (is_array($this->_requestContext) && isset($this->_requestContext['skipClosedCheck']) && $this->_requestContext['skipClosedCheck']) { return true; } } if ($_throw) { throw new Timetracker_Exception_ClosedTimeaccount(); } return FALSE; } // check if timeaccount->is_billable is false => set default in fieldGrants to 0 and allow only managers to change it if (!$timeaccount->is_billable) { $this->_fieldGrants['is_billable']['default'] = 0; $this->_fieldGrants['is_billable']['requiredGrant'] = Tinebase_Model_Grants::GRANT_ADMIN; } } if ($isAdmin === true) { return true; } $hasGrant = FALSE; switch ($_action) { case 'get': $hasGrant = Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, array(Timetracker_Model_TimeaccountGrants::VIEW_ALL, Timetracker_Model_TimeaccountGrants::BOOK_ALL)) || $_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN); break; case 'create': $hasGrant = $_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL); if ($hasGrant) { foreach ($this->_fieldGrants as $field => $config) { if (isset($_record->{$field}) && $_record->{$field} != $config['default']) { $hasGrant &= Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, $config['requiredGrant']); } } } break; case 'update': $hasGrant = $_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL); if ($hasGrant) { foreach ($this->_fieldGrants as $field => $config) { if (isset($_record->{$field}) && $_record->{$field} != $_oldRecord->{$field}) { $hasGrant &= Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, $config['requiredGrant']); } } } break; case 'delete': $hasGrant = $_record->account_id == Tinebase_Core::getUser()->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL); break; } if ($_throw && !$hasGrant) { throw new Tinebase_Exception_AccessDenied($_errorMessage); } return $hasGrant; }
/** * @see: rt127444 * * make sure timeaccounts won't be billed if they shouldn't */ public function testBudgetTimeaccountBilled() { $this->_createFullFixtures(); $date = clone $this->_referenceDate; $i = 0; // do not set to bill, this ta has a budget $customer1Timeaccount = $this->_timeaccountRecords->filter('title', 'TA-for-Customer1')->getFirstRecord(); $customer1Timeaccount->status = 'not yet billed'; $tsController = Timetracker_Controller_Timesheet::getInstance(); $taController = Timetracker_Controller_Timeaccount::getInstance(); $taController->update($customer1Timeaccount); // this is a ts on 20xx-01-18 $timesheet = new Timetracker_Model_Timesheet(array('account_id' => Tinebase_Core::getUser()->getId(), 'timeaccount_id' => $customer1Timeaccount->getId(), 'start_date' => $date->addDay(17), 'duration' => 120, 'description' => 'ts from ' . (string) $date)); $tsController->create($timesheet); // this is a ts on 20xx-02-03 $timesheet->id = NULL; $timesheet->start_date = $date->addDay(17); $timesheet->description = 'ts from ' . (string) $date; $tsController->create($timesheet); $date = clone $this->_referenceDate; $date->addMonth(1); $result = $this->_invoiceController->createAutoInvoices($date); $this->assertEquals(1, count($result['created'])); $customer1Timeaccount->status = 'to bill'; $taController->update($customer1Timeaccount); $date->addSecond(1); $result = $this->_invoiceController->createAutoInvoices($date); $this->assertEquals(1, count($result['created'])); $invoiceId = $result['created'][0]; $invoice = $this->_invoiceController->get($invoiceId); $found = FALSE; foreach ($invoice->relations as $relation) { if ($relation->related_model == 'Timetracker_Model_Timeaccount') { $this->assertEquals('TA-for-Customer1', $relation->related_record->title); $found = TRUE; } } $this->assertTrue($found, 'the timeaccount could not be found in the invoice!'); }
/** * set timeaccount grants * * @param Timetracker_Model_Timeaccount $_timeaccount * @param Tinebase_Record_RecordSet $_grants * @param boolean $_ignoreACL */ public static function setTimeaccountGrants(Timetracker_Model_Timeaccount $_timeaccount, Tinebase_Record_RecordSet $_grants, $_ignoreACL = FALSE) { if (!$_ignoreACL) { if (!Timetracker_Controller_Timeaccount::getInstance()->checkRight(Timetracker_Acl_Rights::MANAGE_TIMEACCOUNTS, FALSE)) { if (!self::hasGrant($_timeaccount, Tinebase_Model_Grants::GRANT_ADMIN)) { throw new Tinebase_Exception_AccessDenied("You nor have the RIGHT either the GRANT to get see all grants for this timeaccount"); } } } Tinebase_Container::getInstance()->setGrants($_timeaccount->container_id, $_grants, TRUE, FALSE); }
/** * check grant for action * * @param Timetracker_Model_Timeaccount $_record * @param string $_action * @param boolean $_throw * @param string $_errorMessage * @param Timetracker_Model_Timesheet $_oldRecord * @return boolean * @throws Tinebase_Exception_AccessDenied * * @todo think about just setting the default values when user * hasn't the required grant to change the field (instead of throwing exception) */ protected function _checkGrant($_record, $_action, $_throw = TRUE, $_errorMessage = 'No Permission.', $_oldRecord = NULL) { // users with MANAGE_TIMEACCOUNTS have all grants here if ($this->checkRight(Timetracker_Acl_Rights::MANAGE_TIMEACCOUNTS, FALSE) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Tinebase_Model_Grants::GRANT_ADMIN)) { return TRUE; } // only TA managers are allowed to alter TS of closed TAs if ($_action != 'get') { $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->get($_record->timeaccount_id); if (!$timeaccount->is_open) { return FALSE; } // check if timeaccount->is_billable is false => set default in fieldGrants to 0 and allow only managers to change it if (!$timeaccount->is_billable) { $this->_fieldGrants['is_billable']['default'] = 0; $this->_fieldGrants['is_billable']['requiredGrant'] = Tinebase_Model_Grants::GRANT_ADMIN; } } $hasGrant = FALSE; switch ($_action) { case 'get': $hasGrant = Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, array(Timetracker_Model_TimeaccountGrants::VIEW_ALL, Timetracker_Model_TimeaccountGrants::BOOK_ALL)) || $_record->account_id == $this->_currentAccount->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN); break; case 'create': $hasGrant = $_record->account_id == $this->_currentAccount->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL); if ($hasGrant) { foreach ($this->_fieldGrants as $field => $config) { if (isset($_record->{$field}) && $_record->{$field} != $config['default']) { $hasGrant &= Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, $config['requiredGrant']); } } } break; case 'update': $hasGrant = $_record->account_id == $this->_currentAccount->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL); if ($hasGrant) { foreach ($this->_fieldGrants as $field => $config) { if (isset($_record->{$field}) && $_record->{$field} != $_oldRecord->{$field}) { $hasGrant &= Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, $config['requiredGrant']); } } } break; case 'delete': $hasGrant = $_record->account_id == $this->_currentAccount->getId() && Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_OWN) || Timetracker_Model_TimeaccountGrants::hasGrant($_record->timeaccount_id, Timetracker_Model_TimeaccountGrants::BOOK_ALL); break; } if ($_throw && !$hasGrant) { throw new Tinebase_Exception_AccessDenied($_errorMessage); } return $hasGrant; }
/** * resolve records * * @param Tinebase_Record_RecordSet $_records */ protected function _resolveRecords(Tinebase_Record_RecordSet $_records) { parent::_resolveRecords($_records); $timeaccountIds = $_records->timeaccount_id; $this->_resolvedRecords['timeaccounts'] = Timetracker_Controller_Timeaccount::getInstance()->getMultiple(array_unique(array_values($timeaccountIds))); }
/** * set each billable of this accountable billed * * @param Sales_Model_Invoice $invoice */ public function clearBillables(Sales_Model_Invoice $invoice) { $tsController = Timetracker_Controller_Timesheet::getInstance(); $this->_disableTimesheetChecks($tsController); $filter = new Timetracker_Model_TimesheetFilter(array(), 'AND'); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'is_cleared', 'operator' => 'equals', 'value' => 0))); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'timeaccount_id', 'operator' => 'equals', 'value' => $this->getId()))); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice->getId()))); // if this timeaccount has a budget, close and bill this and set cleared at date if (intval($this->budget) > 0) { $this->is_open = 0; $this->status = 'billed'; $this->cleared_at = Tinebase_DateTime::now(); Timetracker_Controller_Timeaccount::getInstance()->update($this); // also clear all timesheets belonging to this invoice and timeaccount $tsController->updateMultiple($filter, array('is_cleared' => 1)); } else { // otherwise clear all timesheets of this invoice $tsController->updateMultiple($filter, array('is_cleared' => 1)); } $this->_enableTimesheetChecks($tsController); }
/** * try to search for Timesheets (with combined is_billable + cleared) */ public function testSearchTimesheetsWithCombinedIsBillableAndCleared() { // create $timesheet = $this->_getTimesheet(); $timesheetData = $this->_json->saveTimesheet($timesheet->toArray()); // update timeaccount -> is_billable = false $ta = Timetracker_Controller_Timeaccount::getInstance()->get($timesheetData['timeaccount_id']['id']); $ta->is_billable = 0; Timetracker_Controller_Timeaccount::getInstance()->update($ta); // search & check $search = $this->_json->searchTimesheets($this->_getTimesheetFilter(array('field' => 'is_cleared_combined', 'operator' => 'equals', 'value' => FALSE)), $this->_getPaging('is_billable_combined')); $this->assertEquals(0, $search['results'][0]['is_billable_combined'], 'is_billable_combined mismatch'); $this->assertEquals(0, $search['results'][0]['is_cleared_combined'], 'is_cleared_combined mismatch'); $this->assertEquals(1, $search['totalcount']); $this->assertEquals(30, $search['totalsum']); $this->assertEquals(0, $search['totalsumbillable']); // search again with is_billable filter $search = $this->_json->searchTimesheets($this->_getTimesheetFilter(array('field' => 'is_billable_combined', 'operator' => 'equals', 'value' => FALSE)), $this->_getPaging('is_billable_combined')); $this->assertEquals(0, $search['results'][0]['is_billable_combined'], 'is_billable_combined mismatch'); // search again with is_billable filter and no sorting $search = $this->_json->searchTimesheets($this->_getTimesheetFilter(array('field' => 'is_billable_combined', 'operator' => 'equals', 'value' => FALSE)), $this->_getPaging()); $this->assertEquals(0, $search['results'][0]['is_billable_combined'], 'is_billable_combined mismatch'); }
/** * here we search for all timeaccounts, which are related to an contract with a special * internal contact assigned * * @see: 0009752: create contract - internal/external contact person filter */ public function testTimeaccountContractInternalContactFilter() { $this->markTestSkipped('0010492: fix failing invoices and timetracker tests'); $this->_getTimeaccount(array('title' => 'to find'), true); $taController = Timetracker_Controller_Timeaccount::getInstance(); $taToFind = $taController->get($this->_lastCreatedRecord['id']); $this->_getTimeaccount(array('title' => 'not to find'), true); $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array('n_family' => 'Green'))); $contractController = Sales_Controller_Contract::getInstance(); $contract = new Sales_Model_Contract(array('title' => 'xtestunit', 'description' => 'nothing')); $contract = $contractController->create($contract); $contract->relations = array(new Tinebase_Model_Relation(array('own_backend' => 'Sql', 'own_id' => $contract->getId(), 'own_model' => 'Sales_Model_Contract', 'own_degree' => 'sibling', 'remark' => 'PHP UNITTEST', 'related_model' => 'Addressbook_Model_Contact', 'related_backend' => 'Sql', 'related_id' => $contact->getId(), 'type' => 'RESPONSIBLE'))); $contract = $contractController->update($contract); $taToFind->relations = array(new Tinebase_Model_Relation(array('own_backend' => 'Sql', 'own_degree' => 'sibling', 'own_id' => $taToFind->getId(), 'own_model' => 'Timetracker_Model_Timeaccount', 'remark' => 'PHP UNITTEST', 'related_model' => 'Sales_Model_Contract', 'related_backend' => 'Sql', 'related_id' => $contract->getId(), 'type' => 'CONTRACT'))); $taToFind = $taController->update($taToFind); // build request with direct id $req = Zend_Json::decode('{"params":{"filter":[{"condition":"OR","filters":[{"condition":"AND","filters": [{"field":"contract","operator":"AND","value":[{"field":"contact_external","operator":"AND","value": [{"field":":id","operator":"equals","value":"' . $contact->getId() . '"}],"id":"ext-record-266"}, {"field":":id","operator":"AND"}],"id":"ext-record-181"}],"id":"ext-comp-1350","label":"Zeitkonten"}]}], "paging":{"sort":"creation_time","dir":"DESC","start":0,"limit":50}}}'); $filter = $req['params']['filter']; $paging = $req['params']['paging']; $result = $this->_json->searchTimeaccounts($filter, $paging); $this->assertEquals(1, $result['totalcount']); $this->assertEquals($taToFind->getId(), $result['results'][0]['id']); // build request with query=Green $req = Zend_Json::decode('{"jsonrpc":"2.0","method":"Timetracker.searchTimeaccounts","params":{"filter":[{"condition":"OR","filters":[{"condition":"AND","filters":[{"field":"contract","operator":"AND","value":[{"field":"foreignRecord","operator":"AND","value":{"appName":"Addressbook","modelName":"Contact","linkType":"relation","filters":[{"field":"query","operator":"contains","value":"Green","id":"ext-record-546"}]},"id":"ext-record-480"},{"field":":id","operator":"AND"}],"id":"ext-record-181"}],"id":"ext-comp-1350","label":"Zeitkonten"}]}],"paging":{"sort":"creation_time","dir":"DESC","start":0,"limit":50}},"id":62}'); $filter = $req['params']['filter']; $paging = $req['params']['paging']; $result = $this->_json->searchTimeaccounts($filter, $paging); $this->assertEquals(1, $result['totalcount']); $this->assertEquals($taToFind->getId(), $result['results'][0]['id']); }
/** * get Timesheet (create timeaccount as well) * * @return Timetracker_Model_Timesheet */ protected function _getTimesheet() { $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->create($this->_getTimeaccount()); return new Timetracker_Model_Timesheet(array('account_id' => Tinebase_Core::getUser()->getId(), 'timeaccount_id' => $timeaccount->getId(), 'description' => 'blabla', 'start_date' => Tinebase_DateTime::now()->toString('Y-m-d')), TRUE); }
/** * create tine timeaccount * * @param array $_data with egw project data * @param string $_contractId * @return Tinebase_Record_RecordSet of Timetracker_Model_Timeaccount * * @todo add members as groups (which?) * @todo add more fields? */ protected function _createTimeaccount($_data, $_contractId) { $timeaccount = new Timetracker_Model_Timeaccount(array('title' => $this->_encode($_data['pm_title']), 'number' => $_data['pm_number'], 'description' => $this->_convertDescription($_data['pm_description']), 'budget' => $_data['pm_planned_budget'], 'is_open' => $_data['pm_status'] == 'archive' ? 0 : 1), TRUE); // link contract to timeaccount $timeaccount->relations = array(array('own_model' => 'Timetracker_Model_Timeaccount', 'own_backend' => Timetracker_Backend_Timeaccount::TYPE, 'own_degree' => Tinebase_Model_Relation::DEGREE_SIBLING, 'type' => Timetracker_Model_Timeaccount::RELATION_TYPE_CONTRACT, 'related_id' => $_contractId, 'related_model' => 'Sales_Model_Contract', 'related_backend' => Sales_Backend_Contract::TYPE, 'remark' => Timetracker_Model_Timeaccount::RELATION_TYPE_CONTRACT)); $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->create($timeaccount); // add user grants to this timeaccount (container) $members = $this->_getProjectMembers($_data['pm_id']); echo " Got " . count($members) . " members for that project.\n"; foreach ($members as $userId => $role) { $timeaccountContainer = Tinebase_Container::getInstance()->getContainerById($timeaccount->container_id); // add more different grants depending on roles switch ($role) { case 4: $grants = array(Tinebase_Model_Grants::GRANT_READ); break; case 3: $grants = array(Tinebase_Model_Grants::GRANT_READ, Tinebase_Model_Grants::GRANT_EDIT, Tinebase_Model_Grants::GRANT_ADD); break; case 2: $grants = array(Tinebase_Model_Grants::GRANT_READ, Tinebase_Model_Grants::GRANT_EDIT, Tinebase_Model_Grants::GRANT_ADD, Tinebase_Model_Grants::GRANT_DELETE); break; case 1: $grants = array(Tinebase_Model_Grants::GRANT_READ, Tinebase_Model_Grants::GRANT_EDIT, Tinebase_Model_Grants::GRANT_ADD, Tinebase_Model_Grants::GRANT_DELETE, Tinebase_Model_Grants::GRANT_ADMIN); break; } Tinebase_Container::getInstance()->addGrants($timeaccountContainer, Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, $userId, $grants, TRUE); } $this->_counters['timeaccounts']++; return $timeaccount; }
/** * tests if timeaccounts/timesheets get cleared if the invoice get billed */ public function testClearing() { if ($this->_dbIsPgsql()) { $this->markTestSkipped('0011670: fix Sales_Invoices Tests with postgresql backend'); } $this->_createFullFixtures(); // the whole year, 12 months $date = clone $this->_referenceDate; $date->addMonth(12); $this->_invoiceController->createAutoInvoices($date); $json = new Sales_Frontend_Json(); // test if timesheets get cleared $invoices = $json->searchInvoices(array(array('field' => 'foreignRecord', 'operator' => 'AND', 'value' => array('appName' => 'Sales', 'linkType' => 'relation', 'modelName' => 'Customer', 'filters' => array(array('field' => 'name', 'operator' => 'equals', 'value' => 'Customer3'))))), array()); $invoiceIds = array(); $this->assertEquals(2, $invoices['totalcount']); foreach ($invoices['results'] as $invoice) { $invoiceIds[] = $invoice['id']; // fetch invoice by get to have all relations set $invoice = $json->getInvoice($invoice['id']); $invoice['cleared'] = 'CLEARED'; $json->saveInvoice($invoice); } $tsController = Timetracker_Controller_Timesheet::getInstance(); $timesheets = $tsController->getAll(); foreach ($timesheets as $timesheet) { $this->assertTrue(in_array($timesheet->invoice_id, $invoiceIds), 'the invoice id must be set!'); $this->assertEquals(1, $timesheet->is_cleared); } // test if timeaccounts get cleared $invoices = $json->searchInvoices(array(array('field' => 'foreignRecord', 'operator' => 'AND', 'value' => array('appName' => 'Sales', 'linkType' => 'relation', 'modelName' => 'Customer', 'filters' => array(array('field' => 'name', 'operator' => 'equals', 'value' => 'Customer1'))))), array()); $invoiceIds = array(); foreach ($invoices['results'] as $invoice) { $invoiceIds[] = $invoice['id']; // fetch invoice by get to have all relations set $invoice = $json->getInvoice($invoice['id']); $invoice['cleared'] = 'CLEARED'; // check set empty number fields to an empty string $invoice['sales_tax'] = ''; $invoice['price_gross'] = ''; $invoice['price_net'] = ''; $invoice = $json->saveInvoice($invoice); $this->assertEquals(0, $invoice['sales_tax']); $this->assertEquals(0, $invoice['price_gross']); $this->assertEquals(0, $invoice['price_net']); } $taController = Timetracker_Controller_Timeaccount::getInstance(); $filter = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'budget', 'operator' => 'greater', 'value' => 0), array('field' => 'is_open', 'operator' => 'equals', 'value' => 0))); $timeaccounts = $taController->search($filter); $this->assertEquals(1, $timeaccounts->count()); foreach ($timeaccounts as $ta) { $this->assertTrue(in_array($ta->invoice_id, $invoiceIds), 'the invoice id id must be set!'); $this->assertEquals('billed', $ta->status); } }
/** * inspects delete action * * @param array $_ids * @return array of ids to actually delete */ protected function _inspectDelete(array $_ids) { $records = $this->_backend->getMultiple($_ids); $records->setTimezone(Tinebase_Core::getUserTimezone()); $invoicePositionController = Sales_Controller_InvoicePosition::getInstance(); $contractController = Sales_Controller_Contract::getInstance(); foreach ($records as $record) { if (!$record->is_auto) { continue; } if ($record->cleared == 'CLEARED') { // cleared invoices must not be deleted throw new Sales_Exception_InvoiceAlreadyClearedDelete(); } else { // try to find a invoice after this one // there should be a contract $contractRelation = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Invoice', 'Sql', $record->getId(), NULL, array(), TRUE, array('Sales_Model_Contract'))->getFirstRecord(); if ($contractRelation) { $contract = $contractRelation->related_record; $contract->setTimezone(Tinebase_Core::getUserTimezone()); // get all invoices related to this contract. throw exception if a follwing invoice has been found $invoiceRelations = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Contract', 'Sql', $contract->getId(), NULL, array(), TRUE, array('Sales_Model_Invoice')); foreach ($invoiceRelations as $invoiceRelation) { $invoiceRelation->related_record->setTimezone(Tinebase_Core::getUserTimezone()); if ($record->getId() !== $invoiceRelation->related_record->getId() && $record->creation_time < $invoiceRelation->related_record->creation_time) { throw new Sales_Exception_DeletePreviousInvoice(); } } } else { if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) { Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Could not find contract relation -> skip contract handling'); } $contract = null; } // remove invoice_id from billables $filter = new Sales_Model_InvoicePositionFilter(array()); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $record->getId()))); $invoicePositions = $invoicePositionController->search($filter); $allModels = array_unique($invoicePositions->model); foreach ($allModels as $model) { if ($model == 'Sales_Model_ProductAggregate') { continue; } $filteredInvoicePositions = $invoicePositions->filter('model', $model); $billableControllerName = $model::getBillableControllerName(); $billableFilterName = $model::getBillableFilterName(); $filterInstance = new $billableFilterName(array()); $filterInstance->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $record->getId()))); $billableControllerName::getInstance()->updateMultiple($filterInstance, array('invoice_id' => NULL)); // set invoice ids of the timeaccounts if ($model == 'Timetracker_Model_Timeaccount') { $filterInstance = new Timetracker_Model_TimeaccountFilter(array()); $filterInstance->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $record->getId()))); Timetracker_Controller_Timeaccount::getInstance()->updateMultiple($filterInstance, array('invoice_id' => NULL)); } } // delete invoice positions $invoicePositionController->delete($invoicePositions->getId()); // set last_autobill a period back if ($contract) { // check product aggregates $filter = new Sales_Model_ProductAggregateFilter(array()); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'contract_id', 'operator' => 'equals', 'value' => $contract->getId()))); $paController = Sales_Controller_ProductAggregate::getInstance(); $productAggregates = $paController->search($filter); $productAggregates->setTimezone(Tinebase_Core::getUserTimezone()); foreach ($productAggregates as $productAggregate) { if ($productAggregate->last_autobill) { $lab = clone $productAggregate->last_autobill; $add = 0 - (int) $productAggregate->interval; $productAggregate->last_autobill = $lab->addMonth($add); $productAggregate->last_autobill->setTime(0, 0, 0); // last_autobill may not be before aggregate starts (may run into this case if interval has been resized) if (!$productAggregate->start_date || $productAggregate->last_autobill < $productAggregate->start_date) { $productAggregate->last_autobill = NULL; } } $productAggregate->setTimezone('UTC'); $paController->update($productAggregate); } } } } return $_ids; }
/** * * @param Sales_Model_CostCenter|string $costCenterId * @return Tinebase_Record_RecordSet */ public function getTimeaccountsBySalesCostCenter($costCenterId) { $costCenterId = is_string($costCenterId) ? $costCenterId : $costCenterId->getId(); $filter = new Tinebase_Model_RelationFilter(array(array('field' => 'related_model', 'operator' => 'equals', 'value' => 'Sales_Model_CostCenter'), array('field' => 'related_id', 'operator' => 'equals', 'value' => $costCenterId), array('field' => 'own_model', 'operator' => 'equals', 'value' => 'Timetracker_Model_Timeaccount'), array('field' => 'type', 'operator' => 'equals', 'value' => 'COST_CENTER')), 'AND'); return Timetracker_Controller_Timeaccount::getInstance()->getMultiple(Tinebase_Relations::getInstance()->search($filter)->own_id); }
/** * create customfield value (tr_budget) for timeaccounts starting with S-AB and moves the value from budget to tr_budget */ public function moveBudget() { $filter = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'budget', 'operator' => 'greater', 'value' => 0), array('field' => 'number', 'operator' => 'startswith', 'value' => 'S-AB'))); $taController = Timetracker_Controller_Timeaccount::getInstance(); $tas = $taController->search($filter); $cfi = Tinebase_CustomField::getInstance(); $cfb = new Tinebase_Backend_Sql(array('modelName' => 'Tinebase_Model_CustomField_Value', 'tableName' => 'customfield')); $trBudget = $cfi->getCustomFieldByNameAndApplication('Timetracker', 'tr_budget'); if (!$trBudget) { die('No CustomField tr_budget found!'); } foreach ($tas as $ta) { echo 'Working on ' . $ta->title . PHP_EOL; $cf = new Tinebase_Model_CustomField_Value(array('record_id' => $ta->getId(), 'customfield_id' => $trBudget->getId(), 'value' => $ta->budget)); $cfb->create($cf); $ta->budget = NULL; $taController->update($ta); } echo PHP_EOL; echo 'done!' . PHP_EOL; }
/** * returns array with the filter settings of this filter * * @param bool $_valueToJson resolve value for json api? * @return array */ public function toArray($_valueToJson = false) { $result = parent::toArray($_valueToJson); if ($_valueToJson == true) { $result['value'] = Timetracker_Controller_Timeaccount::getInstance()->get($result['value'])->toArray(); } return $result; }
/** * returns array with the filter settings of this filter group * * @param bool $_valueToJson resolve value for json api? * @return array */ public function toArray($_valueToJson = false) { $result = parent::toArray($_valueToJson); foreach ($result as &$filterData) { if (isset($filterData['field']) && $filterData['field'] == 'id' && $_valueToJson == true && !empty($filterData['value']) && !is_array($filterData['value'])) { try { $filterData['value'] = Timetracker_Controller_Timeaccount::getInstance()->get($filterData['value'])->toArray(); } catch (Tinebase_Exception_NotFound $nfe) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__ . " could not find and resolve timeaccount {$filterData['value']}"); } } } } return $result; }