/** * @see Tinebase_Setup_DemoData_Abstract * */ protected function _beforeCreate() { $this->_ccController = Sales_Controller_CostCenter::getInstance(); $this->_taController = Timetracker_Controller_Timeaccount::getInstance(); $this->_taController->sendNotifications(FALSE); $this->_tsController = Timetracker_Controller_Timesheet::getInstance(); $this->_tsController->sendNotifications(FALSE); $this->_tsController->doContainerACLChecks(false); $this->_contractController = Sales_Controller_Contract::getInstance(); $contracts = $this->_contractController->getAll(); $developmentString = self::$_de ? 'Entwicklung' : 'Development'; $this->_contractsDevelopment = $contracts->filter('title', '/.' . $developmentString . '/', TRUE); $this->_contractsMarketing = $contracts->filter('title', '/.Marketing/', TRUE); $this->_loadCostCentersAndDivisions(); if (Tinebase_Application::getInstance()->isInstalled('HumanResources')) { $this->_empController = HumanResources_Controller_Employee::getInstance(); $filter = new HumanResources_Model_EmployeeFilter(array()); $this->_employees = $this->_empController->search($filter); } // set start date to start date of june 1st before last year $date = Tinebase_DateTime::now(); $this->_startDate = $date->subMonth(3)->setTime(8, 0, 0); // set clearedDate almost a month after $this->_clearedDate = clone $this->_startDate; $this->_clearedDate->addMonth(1)->subDay(2); }
/** * test employee creation/update with contracts */ public function testContract() { $sdate = new Tinebase_DateTime(); $sdate->subMonth(4); $edate = new Tinebase_DateTime(); $edate->subMonth(3)->subDay(1); $now = new Tinebase_DateTime(); $now->subHour(3); $nextMonth = clone $now; $nextMonth->addMonth(1); $fcId = $this->_getFeastCalendar(); $contracts = array(array('start_date' => clone $sdate, 'end_date' => clone $edate, 'vacation_days' => 23, 'feast_calendar_id' => $fcId, 'creation_time' => $now, 'id' => 1234567891)); $sdate->addMonth(1); $edate->addMonth(1); $contracts[] = array('start_date' => clone $sdate, 'end_date' => clone $edate, 'vacation_days' => 27, 'feast_calendar_id' => $fcId, 'creation_time' => $now, 'id' => 1234567890); $employee = $this->_getEmployee('unittest')->toArray(); $employee['contracts'] = $contracts; $employee = $this->_json->saveEmployee($employee); $this->assertEquals(2, count($employee['contracts'])); // get sure the ids are generated properly $this->assertEquals(40, strlen($employee['contracts'][1]['id'])); $this->assertEquals(40, strlen($employee['contracts'][0]['id'])); $this->_removeAllEmployees(); // remove ids unset($employee['contracts'][0]['id']); unset($employee['contracts'][0]['employee_id']); unset($employee['contracts'][1]['id']); unset($employee['contracts'][1]['employee_id']); unset($employee['id']); // test overlapping // create overlapping contract $sdate1 = clone $sdate; $edate1 = clone $edate; $sdate1->addDay(3); $edate1->addMonth(1); $employee['contracts'][] = array('start_date' => $sdate1, 'end_date' => $nextMonth, 'vacation_days' => 22, 'feast_calendar_id' => $fcId, 'creation_time' => $now->toString(), 'number' => 1); // doing this manually, this won't be the last assertion, and more assertions are needed // $this->setExpectedException('Tinebase_Exception_Data'); $exception = new Exception('no exception has been thrown'); try { $this->_json->saveEmployee($employee); } catch (HumanResources_Exception_ContractOverlap $exception) { // thrown in HR_Controller_Employee } $this->assertEquals('The contracts must not overlap!', $exception->getMessage()); $this->_removeAllEmployees(); // prevent duplicate exception $employee['account_id'] = $this->_getAccount('rwright')->getId(); // test startdate after end_date $employee['contracts'][2] = array('start_date' => $edate1->toString(), 'end_date' => $sdate1->toString(), 'vacation_days' => 22, 'feast_calendar_id' => $fcId, 'creation_time' => $now->toString()); try { $this->_json->saveEmployee($employee); $this->fail('HumanResources_Exception_ContractDates exception expected'); } catch (HumanResources_Exception_ContractDates $exception) { // thrown in HR_Controller_Contract $this->assertEquals('The start date of the contract must be before the end date!', $exception->getMessage()); } catch (Tinebase_Exception_Duplicate $ted) { $this->fail('got duplicate exception: ' . print_r($ted->toArray(), true)); } }
/** * 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); } }