protected function _setReferenceDate() { // set reference date to the 1st january of last year $this->_referenceDate = Tinebase_DateTime::now(); $this->_referenceDate->setTimezone(Tinebase_Core::getUserTimezone()); $this->_referenceDate->subYear(1); $this->_referenceDate->setDate($this->_referenceDate->format('Y'), 1, 1); $this->_referenceDate->setTime(0, 0, 0); $this->_referenceYear = $this->_referenceDate->format('Y'); $this->_lastMonthDays = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); // find out if year is a leap year if ((bool) $this->_referenceDate->format('L')) { $this->_isLeapYear = TRUE; $this->_lastMonthDays[1] = 29; } }
/** * Creates an employee with contracts and contact, account etc. * tests auto end_date of old contract */ public function testEmployee() { $date = new Tinebase_DateTime(); $date->subYear(1); $date->setDate($date->format('Y'), 2, 1); $firstDate = substr($date->toString(), 0, 10); $startDate = clone $date; $costCenter1 = $this->_getCostCenter($date); $savedEmployee = $this->_saveEmployee($costCenter1); $this->assertArrayHasKey('account_id', $savedEmployee); $this->assertTrue(is_array($savedEmployee['account_id'])); $this->assertArrayHasKey('contracts', $savedEmployee); $this->assertArrayHasKey('costcenters', $savedEmployee); $this->assertEquals(1, count($savedEmployee['contracts'])); $this->assertEquals(1, count($savedEmployee['costcenters'])); // check if accounts has been created properly on aftercreate $filter = new HumanResources_Model_AccountFilter(array()); $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'employee_id', 'operator' => 'equals', 'value' => $savedEmployee['id']))); $result = HumanResources_Controller_Account::getInstance()->search($filter); $this->assertEquals(2, $result->count()); $date->addMonth(2); $costCenter2 = $this->_getCostCenter($date); $newContract = $this->_getContract(); $newContract->start_date->addMonth(5); $savedEmployee['contracts'][] = $newContract->toArray(); $savedEmployee['costcenters'][] = $costCenter2->toArray(); $savedEmployee = $this->_json->saveEmployee($savedEmployee); $this->assertEquals(2, count($savedEmployee['contracts']), 'There should be 2 Contracts'); $this->assertEquals(2, count($savedEmployee['costcenters']), 'There should be 2 CostCenters'); $this->assertEquals(null, $savedEmployee['contracts'][1]['end_date'], 'The end_date should have a null value.'); $this->assertEquals($firstDate, substr($savedEmployee['costcenters'][0]['start_date'], 0, 10), 'The start_date of the first costcenter should begin with the first date of the employee!'); $date1 = new Tinebase_DateTime($savedEmployee['contracts'][0]['end_date']); $date2 = new Tinebase_DateTime($savedEmployee['contracts'][1]['start_date']); // FIXME this is not working on daylight saving boundaries //$this->assertEquals($date1->addDay(1)->toString(), $date2->toString()); $freeTimes = $this->_json->getFeastAndFreeDays($savedEmployee['id'], $date2->format('Y')); $this->assertEquals($savedEmployee['id'], $freeTimes['results']['contracts'][0]['employee_id']); // 0009592: Adding a new cost center to a employee fails // https://forge.tine20.org/mantisbt/view.php?id=9592 $accountInstance = HumanResources_Controller_Account::getInstance(); $accountInstance->createMissingAccounts((int) $startDate->format('Y')); $accountFilter = new HumanResources_Model_AccountFilter(array()); $accountFilter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'employee_id', 'operator' => 'equals', 'value' => $savedEmployee['id']))); $myAccount = $accountInstance->search($accountFilter)->getFirstRecord(); $firstDayDate = clone $startDate; $firstDayDate->addDay(3); while ($firstDayDate->format('N') != 1) { $firstDayDate->addDay(1); } $vacation = new HumanResources_Model_FreeTime(array('status' => 'ACCEPTED', 'employee_id' => $savedEmployee['id'], 'account_id' => $myAccount->getId(), 'type' => 'vacation', 'freedays' => array(array('date' => $firstDayDate, 'duration' => 1), array('date' => $firstDayDate->addDay(1), 'duration' => 1), array('date' => $firstDayDate->addDay(1), 'duration' => 1), array('date' => $firstDayDate->addDay(1), 'duration' => 1), array('date' => $firstDayDate->addDay(1), 'duration' => 1)))); $vacation = HumanResources_Controller_FreeTime::getInstance()->create($vacation); $employee = $this->_json->getEmployee($savedEmployee['id']); $date->addMonth(2); $costCenter3 = $this->_getCostCenter($date); $employee['costcenters'][] = $costCenter3->toArray(); $employee = $this->_json->saveEmployee($employee); $this->assertEquals(5, $employee['vacation'][0]['days_count']); $this->assertEquals(17, count($employee['vacation'][0])); $this->assertEquals(3, count($employee['costcenters'])); // @see: 0010050: Delete last dependent record fails // if the property is set to null, no dependent record handling will be done $employee['costcenters'] = NULL; $employee = $this->_json->saveEmployee($employee); $this->assertEquals(3, count($employee['costcenters'])); // if the property is set to an empty array, all dependent records will be removed $employee['costcenters'] = array(); $employee = $this->_json->saveEmployee($employee); $this->assertEquals(0, count($employee['costcenters'])); }
/** * 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); } }
/** * appends sql to given select statement * * @param Zend_Db_Select $_select * @param Tinebase_Backend_Sql_Abstract $_backend */ function appendFilterSql($_select, $_backend) { $months = array(); $db = $_backend->getAdapter(); $date = new Tinebase_DateTime(); $format = 'Y-m'; $like = FALSE; if ($this->_operator == 'within') { switch ($this->_value) { case 'monthThis': $months = array($date->format($format)); break; case 'monthLast': $months = array($date->subMonth(1)->format($format)); break; case 'beforeLastMonth': $months = array($date->subMonth(2)->format($format)); break; case 'quarterThis': $month = ceil(intval($date->format('m')) / 3) * 3; $date->setDate($date->format('Y'), $month, 15); $months = array($date->format($format), $date->subMonth(1)->format($format), $date->subMonth(1)->format($format)); break; case 'quarterLast': $date->subMonth(3); $month = ceil(intval($date->format('m')) / 3) * 3; $date->setDate($date->format('Y'), $month, 15); $months = array($date->format($format), $date->subMonth(1)->format($format), $date->subMonth(1)->format($format)); break; case 'beforeLastQuarter': $date->subMonth(6); $month = ceil(intval($date->format('m')) / 3) * 3; $date->setDate($date->format('Y'), $month, 15); $months = array($date->format($format), $date->subMonth(1)->format($format), $date->subMonth(1)->format($format)); break; case 'yearThis': $like = $date->format('Y') . '-%'; break; case 'yearLast': $date->subYear(1); $like = $date->format('Y') . '-%'; break; default: throw new Tinebase_Exception_InvalidArgument('The value for the within operator is not supported: ' . $this->_value); } if ($like) { $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " LIKE (?)", $like)); } else { $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " IN (?)", $months)); } } elseif ($this->_operator == 'equals') { if ('' == $this->_value) { $_select->where($this->_getQuotedFieldName($_backend) . " = '0000-00-00 00:00:00' OR " . $this->_getQuotedFieldName($_backend) . ' IS NULL'); } else { $split = explode('-', $this->_value); if (!(strlen($this->_value) == 7 && (int) $split[0] > 1900 && (int) $split[1] > 0 && (int) $split[1] < 13)) { throw new Tinebase_Exception_MonthFormat(); } $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " = (?)", $this->_value)); } } else { $date = new Tinebase_DateTime($this->_value); $date->setTimezone(Tinebase_Core::getUserTimezone()); $dateString = $date->format('Y-m'); switch ($this->_operator) { case 'before': $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " < (?)", $dateString)); break; case 'after': $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " > (?)", $dateString)); break; case 'before_or_equals': $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " <= (?)", $dateString)); break; case 'after_or_equals': $_select->where($db->quoteInto($this->_getQuotedFieldName($_backend) . " >= (?)", $dateString)); break; default: throw new Tinebase_Exception_InvalidArgument('The operator ' . $this->_operator . ' is not supported for this filter!'); } } }
/** * creates missing accounts * * * optional params: * - day=YYYY-MM-DD * - remove_unbilled=1 * - contract=CONTRACT_ID or contract=NUMBER * * @param Zend_Console_Getopt $_opts * @return boolean */ public function create_auto_invoices($_opts) { if (!Sales_Config::getInstance()->featureEnabled(Sales_Config::FEATURE_INVOICES_MODULE)) { Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' create_auto_invoices ran allthoug feature ' . Sales_Config::FEATURE_INVOICES_MODULE . ' is disabled'); return false; } $executionLifeTime = Tinebase_Core::setExecutionLifeTime(3600 * 8); $this->_addOutputLogWriter(); $freeLock = $this->_aquireMultiServerLock(__CLASS__ . '::' . __FUNCTION__); if (!$freeLock) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Job already running - ' . __CLASS__ . '::' . __FUNCTION__); } return false; } $date = NULL; $args = $this->_parseArgs($_opts, array()); // if day argument is given, validate if (array_key_exists('day', $args)) { $split = explode('-', $args['day']); if (!count($split == 3)) { // failure } else { if (strlen($split[0]) != 4 || strlen($split[1]) != 2 || strlen($split[2]) != 2) { // failure } elseif (intval($split[1]) == 0 || intval($split[2]) == 0) { // failure } else { // other errors are caught by datetime try { $date = new Tinebase_DateTime(); // use usertimezone $date->setTimezone(Tinebase_Core::getUserTimezone()); // if a date is given, set hour to 3 $date->setDate($split[0], $split[1], $split[2])->setTime(3, 0, 0); } catch (Exception $e) { Tinebase_Exception::log($e); } } } if (!$date) { die('The day must have the following format: YYYY-MM-DD!' . PHP_EOL); } } if (!$date) { $date = Tinebase_DateTime::now(); $date->setTimezone(Tinebase_Core::getUserTimezone()); } if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating invoices for ' . $date->toString()); } $contract = NULL; if (array_key_exists('contract', $args)) { try { $contract = Sales_Controller_Contract::getInstance()->get($args['contract']); } catch (Tinebase_Exception_NotFound $e) { $filter = new Sales_Model_ContractFilter(array(array('field' => 'number', 'operator' => 'equals', 'value' => $args['contract']))); $contract = Sales_Controller_Contract::getInstance()->search($filter, NULL, TRUE); if ($contract->count() == 1) { $contract = $contract->getFirstRecord(); } elseif ($contract->count() > 1) { die('The number you have given is not unique! Please use the ID instead!' . PHP_EOL); } else { die('A contract could not be found!' . PHP_EOL); } } } if (array_key_exists('remove_unbilled', $args) && $args['remove_unbilled'] == 1) { $this->removeUnbilledAutoInvoices($contract); } if (array_key_exists('check_updates', $args) && $args['check_updates'] == 1) { Sales_Controller_Invoice::getInstance()->checkForContractOrInvoiceUpdates($contract); } $result = Sales_Controller_Invoice::getInstance()->createAutoInvoices($date, $contract); if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { unset($result['created']); Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . print_r($result, true)); } Tinebase_Core::setExecutionLifeTime($executionLifeTime); return true; }
/** * on saving */ public function testUpdateTimeaccountWithRelatedContact() { $this->_getTimeaccount(array(), TRUE); $ta = $this->_lastCreatedRecord; $contactController = Addressbook_Controller_Contact::getInstance(); $taController = Timetracker_Controller_Timeaccount::getInstance(); $bday = new Tinebase_DateTime(); $bday->setDate(2013, 12, 24); $bday->setTime(0, 0, 0); $contact = $contactController->create(new Addressbook_Model_Contact(array('n_given' => 'Test', 'n_family' => 'Unit', 'bday' => $bday))); $bday = $contact['bday']; Tinebase_Relations::getInstance()->setRelations('Timetracker_Model_Timeaccount', 'Sql', $ta['id'], array(array('related_backend' => 'Sql', 'type' => 'RESPONSIBLE', 'related_model' => 'Addressbook_Model_Contact', 'related_id' => $contact->getId(), 'own_degree' => 'sibling'))); // update a few times, bday of contract should not change $tajson = $this->_json->getTimeaccount($ta['id']); $this->_json->saveTimeaccount($tajson); $tajson = $this->_json->getTimeaccount($ta['id']); $this->_json->saveTimeaccount($tajson); $tajson = $this->_json->getTimeaccount($ta['id']); $ajson = new Addressbook_Frontend_Json(); $contactJson = $ajson->getContact($contact->getId()); $this->assertEquals($bday->setTimezone(Tinebase_Core::getUserTimezone())->toString(), $contactJson['bday']); }
/** * creates the invoices - no containers, just "shared" */ protected function _createSharedInvoices() { $sic = Sales_Controller_Invoice::getInstance(); $now = new Tinebase_DateTime(); $now->setTimezone(Tinebase_Core::getUserTimezone()); $now->setDate($now->format('Y'), $now->format('m'), 1); $now->setTime(3, 0, 0); $date = clone $this->_referenceDate; while ($date < $now) { $sic->createAutoInvoices($date); $date->addMonth(1); } }