/** * test timeaccount - sales contract filter * also tests Tinebase_Model_Filter_ExplicitRelatedRecord */ public function testTimeaccountContractFilter() { $this->_getTimeaccount(array('title' => 'TA1', 'number' => 12345, 'description' => 'UnitTest'), true); $ta1 = $this->_timeaccountController->get($this->_lastCreatedRecord['id']); $this->_getTimeaccount(array('title' => 'TA2', 'number' => 12346, 'description' => 'UnitTest'), true); $ta2 = $this->_timeaccountController->get($this->_lastCreatedRecord['id']); $cId = Tinebase_Container::getInstance()->getDefaultContainer('Sales_Model_Contract')->getId(); $contract = Sales_Controller_Contract::getInstance()->create(new Sales_Model_Contract(array('title' => 'testRelateTimeaccount', 'number' => Tinebase_Record_Abstract::generateUID(), 'container_id' => $cId))); $ta1->relations = array($this->_getRelation($contract, $ta1)); $this->_timeaccountController->update($ta1); // search by contract $f = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'contract', 'operator' => 'AND', 'value' => array(array('field' => ':id', 'operator' => 'equals', 'value' => $contract->getId()))))); $filterArray = $f->toArray(); $this->assertEquals($contract->getId(), $filterArray[0]['value'][0]['value']['id']); $result = $this->_timeaccountController->search($f); $this->assertEquals(1, $result->count()); $this->assertEquals('TA1', $result->getFirstRecord()->title); // test empty filter (without contract) $f = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'contract', 'operator' => 'AND', 'value' => array(array('field' => ':id', 'operator' => 'equals', 'value' => null))), array('field' => 'description', 'operator' => 'equals', 'value' => 'UnitTest'))); $result = $this->_timeaccountController->search($f); $this->assertEquals(1, $result->count(), 'Only one record should have been found!'); $this->assertEquals('TA2', $result->getFirstRecord()->title); // test generic relation filter $f = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'foreignRecord', 'operator' => 'AND', 'value' => array('appName' => 'Sales', 'linkType' => 'relation', 'modelName' => 'Contract', 'filters' => array('field' => 'query', 'operator' => 'contains', 'value' => 'TA1'))))); $result = $this->_timeaccountController->search($f); $this->assertEquals(1, $result->count()); $this->assertEquals('TA1', $result->getFirstRecord()->title); // test "not" operator $f = new Timetracker_Model_TimeaccountFilter(array(array('field' => 'contract', 'operator' => 'AND', 'value' => array(array('field' => ':id', 'operator' => 'not', 'value' => $contract->getId()))), array('field' => 'description', 'operator' => 'equals', 'value' => 'UnitTest'))); $result = $this->_timeaccountController->search($f); // TODO is this correct? do we expect the timaccount without contract to be missing from results? $this->assertEquals(0, $result->count(), 'No record should be found'); }
/** * 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; }
/** * 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; }