c2d15934a9cac61a1728e797248395e77655f196
[tine20] / tests / tine20 / Sales / InvoiceControllerTests.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Sales
6  * @license     http://www.gnu.org/licenses/agpl.html
7  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Alexander Stintzing <a.stintzing@metaways.de>
9  * 
10  */
11
12
13 /**
14  * Test class for Sales Invoice Controller
15  */
16 class Sales_InvoiceControllerTests extends Sales_InvoiceTestCase
17 {
18     protected $_testUser = NULL;
19     
20     /**
21      * Runs the test methods of this class.
22      *
23      * @access public
24      * @static
25      */
26     public static function main()
27     {
28         $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Sales Invoice Controller Tests');
29         PHPUnit_TextUI_TestRunner::run($suite);
30     }
31     
32     /**
33      * (non-PHPdoc)
34      * @see TestCase::tearDown()
35      */
36     protected function tearDown()
37     {
38         // switch back to admin user
39         if ($this->_testUser) {
40             Tinebase_Core::set(Tinebase_Core::USER, $this->_testUser);
41         }
42         
43         parent::tearDown();
44         
45     }
46     
47     protected function _createFailingContracts()
48     {
49         // add contract not to bill
50         $this->_contractRecords->addRecord($this->_contractController->create(new Sales_Model_Contract(array(
51             'number'       => 5,
52             'title'        => Tinebase_Record_Abstract::generateUID(),
53             'description'  => '5 unittest no auto',
54             'container_id' => $this->_sharedContractsContainerId,
55             'billing_address_id' => $this->_addressRecords->filter('customer_id', $this->_customerRecords->filter('name', 'Customer3')->getFirstRecord()->getId())->filter('type', 'billing')->getFirstRecord()->getId(),
56             'start_date' => $this->_referenceDate,
57             'end_date' => NULL,
58         ))));
59         
60         // add contract without customer
61         $contract = new Sales_Model_Contract(array(
62             'number'       => 6,
63             'title'        => Tinebase_Record_Abstract::generateUID(),
64             'description'  => '6 unittest auto not possible',
65             'container_id' => $this->_sharedContractsContainerId,
66             'start_date' => $this->_referenceDate,
67             'end_date' => NULL,
68             'billing_address_id' => $this->_addressRecords->filter('customer_id', $this->_customerRecords->filter('name', 'Customer3')->getFirstRecord()->getId())->filter('type', 'billing')->getFirstRecord()->getId(),
69         ));
70         
71         $contract->relations = array(
72             array(
73                 'own_model'              => 'Sales_Model_Contract',
74                 'own_backend'            => Tasks_Backend_Factory::SQL,
75                 'own_id'                 => NULL,
76                 'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
77                 'related_model'          => 'Sales_Model_CostCenter',
78                 'related_backend'        => Tasks_Backend_Factory::SQL,
79                 'related_id'             => $this->_costcenterRecords->getFirstRecord()->getId(),
80                 'type'                   => 'LEAD_COST_CENTER'
81             ),
82         );
83         
84         $this->_contractRecords->addRecord($this->_contractController->create($contract));
85         
86         // add contract without address
87         $contract = new Sales_Model_Contract(array(
88             'number'       => 7,
89             'title'        => Tinebase_Record_Abstract::generateUID(),
90             'description'  => '7 unittest auto not possible',
91             'container_id' => $this->_sharedContractsContainerId,
92             'start_date' => $this->_referenceDate,
93             'end_date' => NULL,
94         ));
95         
96         $this->_contractRecords->addRecord($this->_contractController->create($contract));
97     }
98     
99     /**
100      * tests auto invoice creation
101      */
102     public function testFullAutoInvoice()
103     {
104         $this->markTestSkipped('0010492: fix failing invoices and timetracker tests');
105         
106         $this->_createFullFixtures();
107         $this->_createFailingContracts();
108         
109         $this->assertEquals(7, $this->_contractRecords->count());
110         
111         $date = clone $this->_referenceDate;
112         
113         $i = 0;
114         
115         // the whole year, 12 months
116         while ($i < 12) {
117             $result = $this->_invoiceController->createAutoInvoices($date);
118             $date->addMonth(1);
119             $i++;
120         }
121         
122         $this->assertEquals(6, count($result['failures']));
123         
124         $failures = '';
125         foreach($result['failures'] as $failure) {
126             $failures .= $failure;
127         }
128         
129         $this->assertTrue(strstr($failures, 'no customer') !== FALSE);
130         $this->assertTrue(strstr($failures, 'no billing') !== FALSE);
131         $this->assertTrue(strstr($failures, 'no costcenter') !== FALSE);
132         
133         // also add an hour to get the last end
134         $date->addHour(1);
135         $this->_invoiceController->createAutoInvoices($date);
136         $date->addHour(1);
137         $this->_invoiceController->createAutoInvoices($date);
138         
139         $all = $this->_invoiceController->getAll();
140         
141         $cc1 = $this->_costcenterRecords->filter('remark', 'unittest1')->getFirstRecord();
142         $cc2 = $this->_costcenterRecords->filter('remark', 'unittest2')->getFirstRecord();
143         $cc3 = $this->_costcenterRecords->filter('remark', 'unittest3')->getFirstRecord();
144         $cc4 = $this->_costcenterRecords->filter('remark', 'unittest4')->getFirstRecord();
145         
146         $all->setTimezone(Tinebase_Core::getUserTimezone());
147         
148         $customer1Invoices = $all->filter('costcenter_id', $cc1->getId())->sort('start_date');
149         $customer2Invoices = $all->filter('costcenter_id', $cc2->getId())->sort('start_date');
150         $customer3Invoices = $all->filter('costcenter_id', $cc3->getId())->sort('start_date');
151         $customer4Invoices = $all->filter('costcenter_id', $cc4->getId())->sort('start_date');
152         
153         // customer 1 must have one invoice (timeaccount with budget has been billed the first month)
154         $this->assertEquals(1, $customer1Invoices->count(), 'Customer 1 must have 1 invoice!');
155         
156         // customer 2 must have one invoice (timeaccount with budget has been billed the first time)
157         $this->assertEquals(1, $customer2Invoices->count(), 'Customer 2 must have 1 invoice!');
158         
159         // there are timesheets in 2 intervals, so no empty invoice should be generated
160         $this->assertEquals(2, $customer3Invoices->count(), 'Customer 3 must have 2 invoices!');
161         
162         // there are 2 products, interval 3,6 -> so every quarter in this year and the first of next year must be found
163         $this->assertEquals(5, $customer4Invoices->count(), 'Customer 4 must have 5 invoices!');
164         
165         // test invoice positions
166         $allInvoicePositions = Sales_Controller_InvoicePosition::getInstance()->getAll();
167
168         $this->assertEquals(1, $allInvoicePositions->filter('invoice_id', $customer1Invoices->getFirstRecord()->getId())->count());
169         $this->assertEquals(1, $allInvoicePositions->filter('invoice_id', $customer2Invoices->getFirstRecord()->getId())->count());
170         
171         // each invoice should contain 1 timeaccount
172         foreach($customer3Invoices as $ci) {
173             $this->assertEquals(1, $allInvoicePositions->filter('invoice_id', $ci->getId())->count());
174         }
175         
176         // we need 9,3,9,3,9 invoice positions
177         $i = 1;
178         foreach($customer4Invoices as $ci) {
179             $ip = $allInvoicePositions->filter('invoice_id', $ci->getId());
180             $this->assertEquals(($i % 2 == 1) ? 9 : 3, $ip->count());
181             $i++;
182         }
183         
184         // contract 1 gets billed at the begin of the period
185         $c1IArray = $customer1Invoices->start_date;
186         
187         $this->assertEquals($this->_referenceYear . '-01-01 00:00:00', $c1IArray[0]->toString());
188         
189         $c1IArray = $customer1Invoices->end_date;
190         $this->assertEquals($this->_referenceYear . '-01-31 23:59:59', $c1IArray[0]->toString());
191         
192         // contract 2 gets billed at the end of the period, and the second period ends at 1.8.20xx
193         $c2IsArray = $customer2Invoices->start_date;
194         $c2IeArray = $customer2Invoices->end_date;
195         
196         $this->assertEquals($this->_referenceYear . '-05-01 00:00:00', $c2IsArray[0]->toString());
197         $this->assertEquals($this->_referenceYear . '-05-31 23:59:59', $c2IeArray[0]->toString());
198         
199         // test correct timesheet handling of customer 3
200         $c3IsArray = $customer3Invoices->start_date;
201         $c3IeArray = $customer3Invoices->end_date;
202         
203         $this->assertEquals($this->_referenceYear . '-05-01 00:00:00', $c3IsArray[0]->toString());
204         $this->assertEquals($this->_referenceYear . '-05-31 23:59:59', $c3IeArray[0]->toString());
205         
206         $this->assertEquals($this->_referenceYear . '-09-01 00:00:00', $c3IsArray[1]->toString());
207         $this->assertEquals($this->_referenceYear . '-09-30 23:59:59', $c3IeArray[1]->toString());
208         
209         // test customer 4 having products only
210         $c4IsArray = $customer4Invoices->start_date;
211         $c4IeArray = $customer4Invoices->end_date;
212         
213         // should contain billeachquarter & billhalfyearly
214         $this->assertEquals($this->_referenceYear . '-01-01 00:00:00', $c4IsArray[0]->toString());
215         $this->assertEquals($this->_referenceYear . '-06-30 23:59:59', $c4IeArray[0]->toString());
216         
217         // should contain billeachquarter
218         $this->assertEquals($this->_referenceYear . '-04-01 00:00:00', $c4IsArray[1]->toString());
219         $this->assertEquals($this->_referenceYear . '-06-30 23:59:59', $c4IeArray[1]->toString());
220         
221         // should contain billeachquarter & billhalfyearly
222         $this->assertEquals($this->_referenceYear . '-07-01 00:00:00', $c4IsArray[2]->toString());
223         $this->assertEquals($this->_referenceYear . '-12-31 23:59:59', $c4IeArray[2]->toString());
224         
225         // should contain billeachquarter
226         $this->assertEquals($this->_referenceYear . '-10-01 00:00:00', $c4IsArray[3]->toString());
227         $this->assertEquals($this->_referenceYear . '-12-31 23:59:59', $c4IeArray[3]->toString());
228         
229         // look if hours of timesheets gets calculated properly
230         $c3Invoice = $customer3Invoices->getFirstRecord();
231         $filter = new Sales_Model_InvoicePositionFilter(array());
232         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $c3Invoice->getId())));
233         $c3InvoicePositions = Sales_Controller_InvoicePosition::getInstance()->search($filter);
234         
235         $this->assertEquals(1, $c3InvoicePositions->count());
236         $this->assertEquals(3.5, $c3InvoicePositions->getFirstRecord()->quantity);
237         
238         $invoice = $customer1Invoices->getFirstRecord();
239         $invoice->relations = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Invoice', 'Sql', $invoice->getId())->toArray();
240         
241         $filter = new Sales_Model_InvoicePositionFilter(array());
242         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice['id'])));
243         $invoice->positions = Sales_Controller_InvoicePosition::getInstance()->search($filter);
244         
245         $invoice->cleared = 'CLEARED';
246         $invoice = $this->_invoiceController->update($invoice);
247         
248         // check correct number generation
249         $this->assertEquals("R-00001", $invoice->number);
250         
251         $invoice = $customer2Invoices->getFirstRecord();
252         $invoice->relations = Tinebase_Relations::getInstance()->getRelations('Sales_Model_Invoice', 'Sql', $invoice->getId())->toArray();
253         $filter = new Sales_Model_InvoicePositionFilter(array());
254         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice['id'])));
255         $invoice->positions = Sales_Controller_InvoicePosition::getInstance()->search($filter);
256         
257         $invoice->cleared = 'CLEARED';
258         $invoice = $this->_invoiceController->update($invoice);
259         
260         $this->assertEquals("R-00002", $invoice->number);
261         
262         // check disallow editing invoice after clearing
263         $invoice->credit_term = 20;
264         $this->setExpectedException('Sales_Exception_InvoiceAlreadyClearedEdit');
265         
266         $this->_invoiceController->update($invoice);
267     }
268     
269     public function testDeleteInvoice()
270     {
271         $this->_createFullFixtures();
272         
273         $date = clone $this->_referenceDate;
274         $date->addMonth(12);
275         
276         $this->_invoiceController->createAutoInvoices($date);
277         
278         $paController = Sales_Controller_ProductAggregate::getInstance();
279         $productAggregates = $paController->getAll();
280         $contracts = $this->_contractController->getAll();
281         $contracts->sort('id', 'DESC');
282         
283         $c1 = $contracts->getFirstRecord();
284         
285         $this->assertEquals(5, $productAggregates->count());
286         
287         $taController = Timetracker_Controller_Timeaccount::getInstance();
288         $tsController = Timetracker_Controller_Timesheet::getInstance();
289         
290         $allTimesheets = $tsController->getAll();
291         $allTimeaccounts = $taController->getAll();
292         
293         foreach($allTimesheets as $ts) {
294             $this->assertTrue($ts->invoice_id != NULL);
295         }
296         
297         foreach($allTimeaccounts as $ta) {
298             if (intval($ta->budget) == 0) {
299                 $this->assertTrue($ta->invoice_id == NULL);
300             }
301         }
302
303         $allInvoices = $this->_invoiceController->getAll('start_date', 'DESC');
304         $this->assertEquals(9, $allInvoices->count(), print_r($allInvoices->toArray(), 1));
305         
306         foreach($allInvoices as $invoice) {
307             $this->_invoiceController->delete($invoice);
308         }
309         
310         $allTimesheets = $tsController->getAll();
311         $allTimeaccounts = $taController->getAll();
312         
313         foreach($allTimeaccounts as $ta) {
314             if (intval($ta->budget) == 0) {
315                 $this->assertTrue($ta->invoice_id == NULL, print_r($ta->toArray(), 1));
316             }
317         }
318         
319         foreach($allTimesheets as $ts) {
320             $this->assertTrue($ts->invoice_id == NULL, print_r($ts->toArray(), 1));
321         }
322     }
323
324     protected function _createInvoiceUpdateRecreationFixtures($createTimesheet = true)
325     {
326         $this->_createFullFixtures();
327
328         // we dont want this contract 1 to be part of the runs below, move it out of the way
329         $this->_contractRecords->getByIndex(0)->start_date->addMonth(12);
330         Sales_Controller_Contract::getInstance()->update($this->_contractRecords->getByIndex(0));
331
332         $date = clone $this->_referenceDate;
333         $customer4Timeaccount = $this->_timeaccountRecords->filter('title', 'TA-for-Customer4')->getFirstRecord();
334         $customer4Timeaccount->status = 'to bill';
335         $customer4Timeaccount->budget = NULL;
336
337         if (null === $this->_timesheetController)
338             $this->_timesheetController = Timetracker_Controller_Timesheet::getInstance();
339         if (null === $this->_timeaccountController)
340             $this->_timeaccountController = Timetracker_Controller_Timeaccount::getInstance();
341         $this->_timeaccountController->update($customer4Timeaccount);
342
343         // this is a ts on 20xx-03-18
344         $this->sharedTimesheet = new Timetracker_Model_Timesheet(array(
345             'account_id' => Tinebase_Core::getUser()->getId(),
346             'timeaccount_id' => $customer4Timeaccount->getId(),
347             'start_date' => $date->addMonth(2)->addDay(17),
348             'duration' => 120,
349             'description' => 'ts from ' . (string) $date,
350         ));
351         if (true === $createTimesheet)
352             $this->_timesheetController->create($this->sharedTimesheet);
353
354         //run autoinvoicing with 20xx-04-01
355         $date = clone $this->_referenceDate;
356         $date->addMonth(3);
357         $result = $this->_invoiceController->createAutoInvoices($date);
358         $this->assertEquals(2, count($result['created']));
359
360         return $result;
361     }
362
363     public function testInvoiceRecreation()
364     {
365         $result = $this->_createInvoiceUpdateRecreationFixtures();
366
367         $oldInvoiceId0 = $result['created'][0];
368         $ipc = Sales_Controller_InvoicePosition::getInstance();
369         $f = new Sales_Model_InvoicePositionFilter(array(
370             array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
371                 array('field' => 'id', 'operator' => 'equals', 'value' => $oldInvoiceId0),
372             )),
373         ));
374         $positions = $ipc->search($f);
375         $this->assertEquals(9, $positions->count());
376
377         $oldInvoiceId1 = $result['created'][1];
378         $ipc = Sales_Controller_InvoicePosition::getInstance();
379         $f = new Sales_Model_InvoicePositionFilter(array(
380             array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
381                 array('field' => 'id', 'operator' => 'equals', 'value' => $oldInvoiceId1),
382             )),
383         ));
384         $positions = $ipc->search($f);
385         $this->assertEquals(4, $positions->count());
386
387         $contract4 = $this->_contractRecords->getByIndex(3);
388         $filter = new Sales_Model_ProductAggregateFilter(
389             array(
390                 array('field' => 'interval', 'operator' => 'equals', 'value' => 3),
391                 //array('field' => 'contract_id', 'operator' => 'equals', 'value' => $this->_contractRecords->getByIndex(3)->getId()),
392             ), 'AND');
393         $filter->addFilter(new Tinebase_Model_Filter_ForeignId(//ExplicitRelatedRecord(
394             array('field' => 'contract_id', 'operator' => 'AND', 'value' =>
395                 array(
396                     array(
397                         'field' =>  ':id', 'operator' => 'equals', 'value' => $contract4->getId()
398                     )
399                 ),
400                 'options' => array(
401                     'controller'        => 'Sales_Controller_Contract',
402                     'filtergroup'       => 'Sales_Model_ContractFilter',
403                     //'own_filtergroup'   => 'Sales_Model_ProductAggregateFilter',
404                     //'own_controller'    => 'Sales_Controller_ProductAggregate',
405                     //'related_model'     => 'Sales_Model_Contract',
406                     'modelName' => 'Sales_Model_Contract',
407                 ),
408             )
409         ));
410
411         $pA = Sales_Controller_ProductAggregate::getInstance()->search($filter);
412         $this->assertEquals(1, $pA->count());
413         $pA = $pA->getFirstRecord();
414         $pA->interval = 4;
415         Sales_Controller_ProductAggregate::getInstance()->update($pA);
416         $contract4->title = $contract4->getTitle() . ' changed';
417         sleep(1);
418         $this->_contractController->update($contract4);
419
420         $this->sharedTimesheet->id = NULL;
421         $this->_timesheetController->create($this->sharedTimesheet);
422
423         $result = $this->_invoiceController->checkForContractOrInvoiceUpdates();
424         $this->assertEquals(true, (count($result)===2||count($result)===3));
425
426         $mapping = $this->_invoiceController->getAutoInvoiceRecreationResults();
427         $this->assertEquals(true, isset($mapping[$oldInvoiceId0]));
428         $this->assertEquals(true, isset($mapping[$oldInvoiceId1]));
429         $newInvoiceId0 = $mapping[$oldInvoiceId0];
430         $newInvoiceId1 = $mapping[$oldInvoiceId1];
431         $this->assertNotEquals($oldInvoiceId0, $newInvoiceId0);
432         $this->assertNotEquals($oldInvoiceId1, $newInvoiceId1);
433
434         $this->_checkInvoiceUpdateExistingTimeaccount($newInvoiceId1);
435
436         $f = new Sales_Model_InvoicePositionFilter(array(
437             array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
438                 array('field' => 'id', 'operator' => 'equals', 'value' => $newInvoiceId0),
439             )),
440         ));
441         $positions = $ipc->search($f);
442         $this->assertEquals(10, $positions->count());
443
444         $f = new Sales_Model_InvoicePositionFilter(array(
445             array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
446                 array('field' => 'id', 'operator' => 'equals', 'value' => $newInvoiceId1),
447             )),
448         ));
449         $positions = $ipc->search($f);
450         $this->assertEquals(1, $positions->count());
451     }
452
453     /**
454      *
455      */
456     public function testInvoiceUpdateExistingTimeaccount()
457     {
458         $result = $this->_createInvoiceUpdateRecreationFixtures();
459
460         $this->sharedTimesheet->id = NULL;
461         $this->_timesheetController->create($this->sharedTimesheet);
462
463         $maybeRecreated = $this->_invoiceController->checkForUpdate($result['created'][1]);
464         if (isset($maybeRecreated[0])) {
465             $result = $maybeRecreated;
466         } else {
467             $result = array($result['created'][1]);
468         }
469
470         $this->_checkInvoiceUpdateExistingTimeaccount($result[0]);
471
472         //check that the same update run doesnt do anything anymore
473         $maybeRecreated = $this->_invoiceController->checkForUpdate($result[0]);
474         if (isset($maybeRecreated[0])) {
475             $result = $maybeRecreated;
476         }
477
478         $this->_checkInvoiceUpdateExistingTimeaccount($result[0]);
479     }
480
481     public function testCheckForContractOrInvoiceUpdatesExistingTimeaccount()
482     {
483         $result = $this->_createInvoiceUpdateRecreationFixtures();
484
485         $this->sharedTimesheet->id = NULL;
486         $this->_timesheetController->create($this->sharedTimesheet);
487
488         $maybeRecreated = $this->_invoiceController->checkForContractOrInvoiceUpdates();
489         if (isset($maybeRecreated[0])) {
490             $result = $maybeRecreated;
491         } else {
492             $result = array($result['created'][1]);
493         }
494
495         $this->_checkInvoiceUpdateExistingTimeaccount($result[0]);
496
497         $maybeRecreated = $this->_invoiceController->checkForContractOrInvoiceUpdates();
498         if (isset($maybeRecreated[0])) {
499             $result = $maybeRecreated;
500         }
501
502         $this->_checkInvoiceUpdateExistingTimeaccount($result[0]);
503     }
504
505     protected function _checkInvoiceUpdateExistingTimeaccount($invoiceId, $result = 4)
506     {
507         $ipc = Sales_Controller_InvoicePosition::getInstance();
508         $f = new Sales_Model_InvoicePositionFilter(array(
509             array('field' => 'model', 'operator' => 'equals', 'value' => 'Timetracker_Model_Timeaccount'),
510             array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
511                 array('field' => 'id', 'operator' => 'equals', 'value' => $invoiceId),
512             )),
513         ));
514         $positions = $ipc->search($f);
515         $this->assertEquals(1, $positions->count());
516         $this->assertEquals($result, $positions->getFirstRecord()->quantity);
517     }
518
519     public function testCheckForContractOrInvoiceUpdatesWithUpdatedTimesheet()
520     {
521         $result = $this->_createInvoiceUpdateRecreationFixtures();
522
523         $this->sharedTimesheet->id = NULL;
524         $this->sharedTimesheet = $this->_timesheetController->create($this->sharedTimesheet);
525
526         $maybeRecreated = $this->_invoiceController->checkForContractOrInvoiceUpdates();
527         if (isset($maybeRecreated[0])) {
528             $result = $maybeRecreated;
529         } else {
530             $result = array($result['created'][1]);
531         }
532
533         $this->assertEquals(true, isset($result[0]));
534
535         $this->_checkInvoiceUpdateExistingTimeaccount($result[0]);
536
537         sleep(1);
538
539         $this->sharedTimesheet->duration = 180;
540         $this->sharedTimesheet = $this->_timesheetController->update($this->sharedTimesheet);
541
542         $maybeRecreated = $this->_invoiceController->checkForContractOrInvoiceUpdates();
543         if (isset($maybeRecreated[0])) {
544             $result = $maybeRecreated;
545         }
546
547         $this->_checkInvoiceUpdateExistingTimeaccount($result[0], 5);
548     }
549
550     protected function _checkInvoiceUpdateWithNewTimeaccount($invoiceId)
551     {
552         $ipc = Sales_Controller_InvoicePosition::getInstance();
553         $f = new Sales_Model_InvoicePositionFilter(array(
554             array('field' => 'model', 'operator' => 'equals', 'value' => 'Timetracker_Model_Timeaccount'),
555             array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
556                 array('field' => 'id', 'operator' => 'equals', 'value' => $invoiceId),
557             )),
558         ));
559         $positions = $ipc->search($f);
560         $this->assertEquals(1, $positions->count());
561         $this->assertEquals(2, $positions->getFirstRecord()->quantity);
562     }
563     /**
564      *
565      */
566     public function testInvoiceUpdateWithNewTimeaccount()
567     {
568         $result = $this->_createInvoiceUpdateRecreationFixtures(false);
569
570         $this->_timesheetController->create($this->sharedTimesheet);
571
572         $maybeRecreated = $this->_invoiceController->checkForUpdate($result['created'][1]);
573         if (isset($maybeRecreated[0])) {
574             $result = $maybeRecreated;
575         } else {
576             $result = array($result['created'][1]);
577         }
578
579         $this->_checkInvoiceUpdateWithNewTimeaccount($result[0]);
580
581         //check that the same update run doesnt do anything anymore
582         $maybeRecreated = $this->_invoiceController->checkForUpdate($result[0]);
583         if (isset($maybeRecreated[0])) {
584             $result = $maybeRecreated;
585         }
586
587         $this->_checkInvoiceUpdateWithNewTimeaccount($result[0]);
588     }
589
590     public function testCheckForContractOrInvoiceUpdatesWithNewTimeaccount()
591     {
592         $result = $this->_createInvoiceUpdateRecreationFixtures(false);
593
594         $this->_timesheetController->create($this->sharedTimesheet);
595
596         $maybeRecreated = $this->_invoiceController->checkForContractOrInvoiceUpdates();
597         if (isset($maybeRecreated[0])) {
598             $result = $maybeRecreated;
599         } else {
600             $result = array($result['created'][1]);
601         }
602
603         $this->_checkInvoiceUpdateWithNewTimeaccount($result[0]);
604
605         $maybeRecreated = $this->_invoiceController->checkForContractOrInvoiceUpdates();
606         if (isset($maybeRecreated[0])) {
607             $result = $maybeRecreated;
608         }
609
610         $this->_checkInvoiceUpdateWithNewTimeaccount($result[0]);
611     }
612
613     /**
614      * @see: rt127444
615      * 
616      * make sure timeaccounts won't be billed if they shouldn't
617      */
618     public function testBudgetTimeaccountBilled()
619     {
620         $this->_createFullFixtures();
621         
622         $date = clone $this->_referenceDate;
623         $i = 0;
624         
625         // do not set to bill, this ta has a budget
626         $customer1Timeaccount = $this->_timeaccountRecords->filter('title', 'TA-for-Customer1')->getFirstRecord();
627         $customer1Timeaccount->status = 'not yet billed';
628         
629         $tsController = Timetracker_Controller_Timesheet::getInstance();
630         $taController = Timetracker_Controller_Timeaccount::getInstance();
631         $taController->update($customer1Timeaccount);
632         
633         // this is a ts on 20xx-01-18
634         $timesheet = new Timetracker_Model_Timesheet(array(
635             'account_id' => Tinebase_Core::getUser()->getId(),
636             'timeaccount_id' => $customer1Timeaccount->getId(),
637             'start_date' => $date->addDay(17),
638             'duration' => 120,
639             'description' => 'ts from ' . (string) $date,
640         ));
641         
642         $tsController->create($timesheet);
643         
644         // this is a ts on 20xx-02-03
645         $timesheet->id = NULL;
646         $timesheet->start_date  = $date->addDay(17);
647         $timesheet->description = 'ts from ' . (string) $date;
648         
649         $tsController->create($timesheet);
650         
651         $date = clone $this->_referenceDate;
652         $date->addMonth(1);
653         
654         $result = $this->_invoiceController->createAutoInvoices($date);
655         
656         $this->assertEquals(1, count($result['created']));
657         
658         $customer1Timeaccount->status = 'to bill';
659         $taController->update($customer1Timeaccount);
660         
661         $date->addSecond(1);
662         
663         $result = $this->_invoiceController->createAutoInvoices($date);
664         $this->assertEquals(1, count($result['created']));
665         
666         $invoiceId = $result['created'][0];
667         $invoice = $this->_invoiceController->get($invoiceId);
668         $found = FALSE;
669         
670         foreach($invoice->relations as $relation) {
671             if ($relation->related_model == 'Timetracker_Model_Timeaccount') {
672                 $this->assertEquals('TA-for-Customer1',     $relation->related_record->title);
673                 $found = TRUE;
674             }
675         }
676
677         $this->assertTrue($found, 'the timeaccount could not be found in the invoice!');
678     }
679     
680
681     /**
682      * tests if the rights work: Sales_Acl_Rights::SET_INVOICE_NUMBER, Sales_Acl_Rights::MANAGE_INVOICES
683      */
684     public function testSetManualNumberRight()
685     {
686         $this->_createCustomers();
687         $this->_createCostCenters();
688         
689         $customer = $this->_customerRecords->filter('name', 'Customer1')->getFirstRecord();
690         $invoice = $this->_invoiceController->create(new Sales_Model_Invoice(array(
691             'number' => 'R-3000',
692             'customer_id' => $customer->getId(),
693             'description' => 'Manual',
694             'address_id' => $this->_addressRecords->filter('customer_id', $customer->getId())->getFirstRecord()->getId(),
695             'costcenter_id' => $this->_costcenterRecords->getFirstRecord()->getId()
696         )));
697         
698         // fetch user group
699         $group   = Tinebase_Group::getInstance()->getGroupByName('Users');
700         $groupId = $group->getId();
701         
702         // create new user
703         $user = new Tinebase_Model_FullUser(array(
704             'accountLoginName'      => 'testuser',
705             'accountPrimaryGroup'   => $groupId,
706             'accountDisplayName'    => 'Test User',
707             'accountLastName'       => 'User',
708             'accountFirstName'      => 'Test',
709             'accountFullName'       => 'Test User',
710             'accountEmailAddress'   => 'unittestx8@tine20.org',
711         ));
712         
713         $user = Admin_Controller_User::getInstance()->create($user, 'pw', 'pw');
714         $this->_testUser = Tinebase_Core::getUser();
715
716         Tinebase_Core::set(Tinebase_Core::USER, $user);
717         
718         $e = new Exception('No Message');
719         
720         try {
721             $invoice = $this->_invoiceController->create(new Sales_Model_Invoice(array(
722                 'number' => 'R-3001',
723                 'customer_id' => $customer->getId(),
724                 'description' => 'Manual Forbidden',
725                 'address_id' => $this->_addressRecords->filter('customer_id', $customer->getId())->getFirstRecord()->getId(),
726                 'costcenter_id' => $this->_costcenterRecords->getFirstRecord()->getId()
727             )));
728         } catch (Exception $e) {
729         }
730         
731         $this->assertTrue(get_class($e) == 'Tinebase_Exception_AccessDenied');
732         $this->assertTrue($e->getMessage() == 'You don\'t have the right to manage invoices!');
733         
734         Tinebase_Core::set(Tinebase_Core::USER, $this->_testUser);
735         
736         $fe = new Admin_Frontend_Json();
737         $userRoles = $fe->getRoles('user', array(), array(), 0, 1);
738         $userRole = $fe->getRole($userRoles['results'][0]['id']);
739         
740         $roleRights = $fe->getRoleRights($userRole['id']);
741         $roleMembers = $fe->getRoleMembers($userRole['id']);
742         $roleMembers['results'][] = array('name' => 'testuser', 'type' => 'user', 'id' => $user->accountId);
743         
744         $app = Tinebase_Application::getInstance()->getApplicationByName('Sales');
745         
746         $roleRights['results'][] = array('application_id' => $app->getId(), 'right' => Sales_Acl_Rights::MANAGE_INVOICES);
747         $fe->saveRole($userRole, $roleMembers['results'], $roleRights['results']);
748         
749         Tinebase_Core::set(Tinebase_Core::USER, $user);
750         
751         $e = new Exception('No Message');
752         
753         try {
754             $invoice = $this->_invoiceController->create(new Sales_Model_Invoice(array(
755                 'number' => 'R-3001',
756                 'customer_id' => $customer->getId(),
757                 'description' => 'Manual Forbidden',
758                 'address_id' => $this->_addressRecords->filter('customer_id', $customer->getId())->getFirstRecord()->getId(),
759                 'costcenter_id' => $this->_costcenterRecords->getFirstRecord()->getId()
760             )));
761         } catch (Exception $e) {
762         }
763         
764         $this->assertEquals('Tinebase_Exception_AccessDenied', get_class($e));
765         $this->assertEquals('You have no right to set the invoice number!', $e->getMessage());
766     }
767     
768     /**
769      * tests if a product aggregate gets billed in the correct periods
770      */
771     public function testOneProductContractInterval()
772     {
773         $startDate = clone $this->_referenceDate;
774         
775         $this->_createProducts();
776         
777         $this->_createCustomers(1);
778         $this->_createCostCenters();
779         
780         $monthBack = clone $this->_referenceDate;
781         $monthBack->subMonth(1);
782         $addressId = $this->_addressRecords->filter(
783                 'customer_id', $this->_customerRecords->filter(
784                     'name', 'Customer1')->getFirstRecord()->getId())->filter(
785                         'type', 'billing')->getFirstRecord()->getId();
786         
787         $this->assertTrue($addressId !== NULL);
788         
789         // this contract begins 6 months before the first invoice will be created
790         $this->_createContracts(array(array(
791             'number'       => 100,
792             'title'        => 'MyContract',
793             'description'  => 'unittest',
794             'container_id' => $this->_sharedContractsContainerId,
795             'billing_point' => 'begin',
796             'billing_address_id' => $addressId,
797             
798             'interval' => 1,
799             'start_date' => $startDate->subMonth(6),
800             'last_autobill' => clone $this->_referenceDate,
801             'end_date' => NULL,
802             'products' => array(
803                 array('product_id' => $this->_productRecords->getByIndex(0)->getId(), 'quantity' => 1, 'interval' => 1, 'last_autobill' => $monthBack),
804             )
805         )));
806         
807         $startDate = clone $this->_referenceDate;
808         $startDate->addDay(5);
809         $startDate->addMonth(24);
810         
811         $result = $this->_invoiceController->createAutoInvoices($startDate);
812         $this->assertEquals(25, $result['created_count']);
813         
814         $invoices = $this->_invoiceController->getAll('start_date');
815         $firstInvoice = $invoices->getFirstRecord();
816         $this->assertInstanceOf('Tinebase_DateTime', $firstInvoice->start_date);
817         $this->assertEquals('0101', $firstInvoice->start_date->format('md'));
818         
819         $this->assertEquals(25, $invoices->count());
820         
821         $filter = new Sales_Model_InvoicePositionFilter(array());
822         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'in', 'value' => $invoices->getArrayOfIds())));
823         
824         $pagination = new Tinebase_Model_Pagination(array('sort' => 'month', 'dir' => 'ASC'));
825         
826         $invoicePositions = Sales_Controller_InvoicePosition::getInstance()->search($filter, $pagination);
827         
828         // get sure each invoice positions has the same month as the invoice and the start_date is the first
829         foreach($invoices as $invoice) {
830             $month = (int) $invoice->start_date->format('n');
831             $index = $month - 1;
832             
833             $this->assertEquals('01', $invoice->start_date->format('d'));
834             $this->assertEquals($invoice->end_date->format('t'), $invoice->end_date->format('d'), print_r($invoice->toArray(), 1));
835             
836             $this->assertEquals(1, $invoice->start_date->format('d'));
837             
838             $pos = $invoicePositions->filter('invoice_id', $invoice->getId())->getFirstRecord();
839             $this->assertEquals($invoice->start_date->format('Y-m'), $pos->month);
840             $this->assertEquals($invoice->end_date->format('Y-m'), $pos->month);
841         }
842         
843         $this->assertEquals(25, $invoicePositions->count());
844         
845         $this->assertEquals($this->_referenceYear . '-01', $invoicePositions->getFirstRecord()->month);
846         
847         $invoicePositions->sort('month', 'DESC');
848         
849         $this->assertEquals($this->_referenceYear + 2 . '-01', $invoicePositions->getFirstRecord()->month);
850     }
851     
852     /**
853      * test product only contract setting last_autobill and resetting last_autobill on delete
854      */
855     public function testLastAutobillAfterDeleteInvoice()
856     {
857         $startDate = clone $this->_referenceDate;
858         $lab = clone $this->_referenceDate;
859         $lab->subMonth(1);
860         $this->_createProducts(array(array(
861             'name' => 'Hours',
862             'description' => 'timesheets',
863             'price' => '100',
864             'accountable' => 'Timetracker_Model_Timeaccount'
865         )));
866         
867         $this->_createCustomers(1);
868         $this->_createCostCenters();
869         
870         // has budget, is to bill
871         $ta = $this->_createTimeaccounts(array(array(
872             'title'         => 'Tacss',
873             'description'   => 'blabla',
874             'is_open'       => 1,
875             'status'        => 'open',
876             'budget'        => NULL,
877             
878         )))->getFirstRecord();
879         
880         // has timeaccount without budget, must be billed at end of the period (each month has at least one timesheet)
881         $this->_createContracts(array(array(
882             'number'       => 100,
883             'title'        => 'MyContract',
884             'description'  => 'unittest',
885             'container_id' => $this->_sharedContractsContainerId,
886             'billing_address_id' => $this->_addressRecords->filter(
887                 'customer_id', $this->_customerRecords->filter(
888                     'name', 'Customer1')->getFirstRecord()->getId())->filter(
889                         'type', 'billing')->getFirstRecord()->getId(),
890         
891             'start_date' => $startDate,
892             'last_autobill' => NULL,
893             'end_date' => NULL,
894             'products' => array(
895                 array('product_id' => $this->_productRecords->getByIndex(0)->getId(), 
896                     'quantity' => 1, 'interval' => 1, 'billing_point' => 'end'),
897             )
898         )));
899         
900         // create timesheets
901         $tsDate = clone $this->_referenceDate;
902         $tsDate->addDay(10);
903         
904         $i = 0;
905         while($i < 12) {
906             $this->_createTimesheets(array(array(
907                 'account_id' => Tinebase_Core::getUser()->getId(),
908                 'timeaccount_id' => $ta->getId(),
909                 'start_date' => $tsDate,
910                 'duration' => 105,
911                 'description' => 'ts from ' . (string) $tsDate,
912             )));
913             $tsDate->addMonth(1);
914             $i++;
915         }
916         
917         
918         $contract = $this->_contractController->getAll()->getFirstRecord();
919         $this->assertEquals($startDate->__toString(), $contract->start_date->__toString());
920         
921         // find product aggregate
922         $paController = Sales_Controller_ProductAggregate::getInstance();
923         $productAggregate = $paController->getAll()->getFirstRecord();
924         $productAggregate->setTimezone(Tinebase_Core::getUserTimezone());
925         
926         $this->assertEquals(NULL, $productAggregate->last_autobill);
927         
928         // create 6 invoices - each month one invoice - last autobill must be increased each month
929         for ($i = 1; $i < 7; $i++) {
930             $myDate = clone $this->_referenceDate;
931             $myDate->addMonth($i)->addHour(3);
932             
933             $testDate = clone $this->_referenceDate;
934             $testDate->addMonth($i);
935             
936             $result = $this->_invoiceController->createAutoInvoices($myDate);
937             $this->assertEquals(1, $result['created_count']);
938             
939             $productAggregate = $paController->get($productAggregate->getId());
940             $productAggregate->setTimezone(Tinebase_Core::getUserTimezone());
941             $this->assertEquals($testDate, $productAggregate->last_autobill);
942         }
943         
944         $testDate = clone $this->_referenceDate;
945         $testDate->addMonth(6);
946         $this->assertEquals($testDate, $productAggregate->last_autobill);
947         
948         // delete all created invoices again
949         $allInvoices = $this->_invoiceController->getAll('start_date', 'DESC');
950         
951         foreach($allInvoices as $invoice) {
952             $this->_invoiceController->delete($invoice);
953         }
954         
955         $productAggregate = $paController->get($productAggregate->getId());
956         $productAggregate->setTimezone(Tinebase_Core::getUserTimezone());
957         
958         $this->assertEquals($this->_referenceDate, $productAggregate->last_autobill);
959         
960         // create 6 invoices again - each month one invoice - last autobill must be increased each month
961         for ($i = 1; $i < 7; $i++) {
962             $myDate = clone $this->_referenceDate;
963             $myDate->addMonth($i)->addHour(3);
964         
965             $testDate = clone $this->_referenceDate;
966             $testDate->addMonth($i);
967         
968             $result = $this->_invoiceController->createAutoInvoices($myDate);
969             $this->assertEquals(1, $result['created_count']);
970         
971             $productAggregate = $paController->get($productAggregate->getId());
972             $productAggregate->setTimezone(Tinebase_Core::getUserTimezone());
973             $this->assertEquals($testDate, $productAggregate->last_autobill);
974         }
975         
976         $testDate = clone $this->_referenceDate;
977         $testDate->addMonth(6);
978         $this->assertEquals($testDate, $productAggregate->last_autobill);
979         
980     }
981     
982     /**
983      * @see step2 FS0012
984      * 
985      * products must be billed on the beginning of a period
986      * the first dates must fit
987      */
988     public function testProductWithOneYearInterval()
989     {
990         // last year, the 1.1 in usertimezone
991         $date      = clone $this->_referenceDate;
992         
993         $startDateContract = clone $this->_referenceDate;
994         $startDateContract->subMonth(13);
995         
996         // this has been billed for the last year.
997         $startDateProduct = clone $this->_referenceDate;
998         $startDateProduct->subMonth(12);
999         
1000         $this->_createProducts();
1001         
1002         $this->_createCustomers(1);
1003         $this->_createCostCenters();
1004         
1005         $addressId = $this->_addressRecords->filter(
1006                 'customer_id', $this->_customerRecords->filter(
1007                     'name', 'Customer1')->getFirstRecord()->getId())->filter(
1008                         'type', 'billing')->getFirstRecord()->getId();
1009         
1010         // the contract has an interval of 0, but it has to be billed
1011         $this->_createContracts(array(array(
1012             'number'       => 100,
1013             'title'        => 'MyContract',
1014             'description'  => 'unittest',
1015             'container_id' => $this->_sharedContractsContainerId,
1016             'billing_point' => 'begin',
1017             'billing_address_id' => $addressId,
1018             'interval' => 0,
1019             'start_date' => $startDateContract,
1020             'last_autobill' => clone $this->_referenceDate,
1021             'end_date' => NULL,
1022             'products' => array(
1023                 array('product_id' => $this->_productRecords->getByIndex(0)->getId(),
1024                     'quantity' => 1, 'interval' => 12, 'last_autobill' => $startDateProduct),
1025             )
1026         )));
1027         
1028         $startDate = clone $this->_referenceDate;
1029         $startDate->addHour(3);
1030         
1031         $i=0;
1032         
1033         $result = $this->_invoiceController->createAutoInvoices($startDate);
1034         $this->assertEquals(1, $result['created_count']);
1035         
1036         $invoice = $this->_invoiceController->get($result['created'][0]);
1037         
1038         $invoicePositions = Sales_Controller_InvoicePosition::getInstance()->getAll('month')->filter('invoice_id', $result['created'][0]);
1039         $this->assertEquals(12, $invoicePositions->count());
1040         
1041         $i = 1;
1042         
1043         foreach($invoicePositions as $ipo) {
1044             $this->assertEquals($this->_referenceYear . '-' . ($i > 9 ? '' : '0') . $i, $ipo->month, print_r($invoicePositions->toArray(), 1));
1045             $this->assertEquals($ipo->invoice_id, $invoice->getId());
1046             $i++;
1047         }
1048     }
1049     
1050     /**
1051      * make sure that timesheets get created for the right month
1052      */
1053     public function testTimesheetOnMonthEndAndBegin()
1054     {
1055         $dates = array(
1056             clone $this->_referenceDate,
1057             clone $this->_referenceDate,
1058             clone $this->_referenceDate,
1059             clone $this->_referenceDate
1060         );
1061         // 0: 1.1.xxxx, 1: 31.1.xxxx, 2: 1.2.xxxx, 3: 28/29.2.xxxx
1062         $dates[1]->addMonth(1)->subDay(1);
1063         $dates[2]->addMonth(1);
1064         $dates[3]->addMonth(2)->subDay(1);
1065         
1066         $customer = $this->_createCustomers(1)->getFirstRecord();
1067         $this->_createCostCenters();
1068         
1069         // has no budget
1070         $ta = $this->_createTimeaccounts(array(array(
1071             'title'         => 'TaTest',
1072             'description'   => 'blabla',
1073             'is_open'       => 1,
1074             'status'        => 'not yet billed',
1075             'budget'        => null
1076         )))->getFirstRecord();
1077         
1078         foreach($dates as $date) {
1079             $this->_createTimesheets(array(
1080                 array(
1081                     'account_id' => Tinebase_Core::getUser()->getId(),
1082                     'timeaccount_id' => $ta->getId(),
1083                     'start_date' => $date,
1084                     'duration' => 105,
1085                     'description' => 'ts from ' . (string) $date,
1086                 ))
1087             );
1088         }
1089         
1090         $this->assertEquals(4, $this->_timesheetRecords->count());
1091         
1092         $csDate = clone $this->_referenceDate;
1093         $csDate->subMonth(10);
1094         
1095         $lab = clone $this->_referenceDate;
1096         // set start position (must be manually set on introducing the invoice module)
1097         $lab->subMonth(1);
1098         $this->_createProducts();
1099         $this->_createContracts(array(array(
1100             'number'       => 100,
1101             'title'        => 'MyContract',
1102             'description'  => 'unittest',
1103             'container_id' => $this->_sharedContractsContainerId,
1104             'billing_point' => 'begin',
1105             'billing_address_id' => $this->_addressRecords->filter(
1106                 'customer_id', $customer->getId())->filter(
1107                         'type', 'billing')->getFirstRecord()->getId(),
1108         
1109             'start_date' => $csDate,
1110             'end_date' => NULL,
1111             'products' => array(
1112                     array('start_date' => $csDate, 'end_date' => NULL, 'quantity' => 1, 'interval' => 1, 'billing_point' => 'end', 'product_id' => $this->_productRecords->filter('name', 'Hours')->getFirstRecord()->getId()),
1113             )
1114         )));
1115         
1116         $json = new Sales_Frontend_Json();
1117
1118         $date = clone $this->_referenceDate;
1119         // this is set by cli if called by cli
1120         $date->setTime(3,0,0);
1121         
1122         $result = $this->_invoiceController->createAutoInvoices($date);
1123         $this->assertEquals(0, $result['created_count'], (string) $date);
1124         sleep(1);
1125         $date->addMonth(1);
1126         $result = $this->_invoiceController->createAutoInvoices($date);
1127         $this->assertEquals(1, $result['created_count'], (string) $date);
1128         $invoice1Id = $result['created'][0];
1129         $invoice = $json->getInvoice($invoice1Id);
1130         $this->assertEquals(1, count($invoice['positions']), print_r($invoice['positions'], 1));
1131         sleep(1);
1132         $date->addMonth(1);
1133         $result = $this->_invoiceController->createAutoInvoices($date);
1134         $this->assertEquals(1, $result['created_count'], (string) $date);
1135         $invoice2Id = $result['created'][0];
1136         $invoice = $json->getInvoice($invoice2Id);
1137         $this->assertEquals(1, count($invoice['positions']));
1138         sleep(1);
1139         $date->addMonth(1);
1140         $result = $this->_invoiceController->createAutoInvoices($date);
1141         $this->assertEquals(0, $result['created_count'], (string) $date);
1142         
1143         $filter = new Timetracker_Model_TimesheetFilter(array());
1144         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice1Id)));
1145         $timesheets = $this->_timesheetController->search($filter);
1146         $this->assertEquals(2, $timesheets->count());
1147         
1148         $filter = new Timetracker_Model_TimesheetFilter(array());
1149         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice2Id)));
1150         $timesheets = $this->_timesheetController->search($filter);
1151         $this->assertEquals(2, $timesheets->count());
1152         
1153         // now try to delete the first invoice, which is not allowed
1154         $this->setExpectedException('Sales_Exception_DeletePreviousInvoice');
1155         
1156         $this->_invoiceController->delete(array($invoice1Id));
1157     }
1158     
1159     /**
1160      * make sure that timesheets get created for the right month
1161      */
1162     public function test2MonthIntervalTimesheetOnMonthEndAndBegin()
1163     {
1164         $dates = array(
1165             clone $this->_referenceDate,
1166             clone $this->_referenceDate,
1167             clone $this->_referenceDate,
1168             clone $this->_referenceDate
1169         );
1170         // 0: 1.1.xxxx, 1: 31.1.xxxx, 2: 1.2.xxxx, 3: 28/29.2.xxxx
1171         $dates[1]->addMonth(1)->subDay(1);
1172         $dates[2]->addMonth(1);
1173         $dates[3]->addMonth(2)->subDay(1);
1174     
1175         // create much more timesheets
1176         $dt = clone $this->_referenceDate;
1177         for ($i = 0; $i < 80; $i++) {
1178             $dt->addHour(12);
1179             $dates[] = clone $dt;
1180         }
1181
1182         $customer = $this->_createCustomers(1)->getFirstRecord();
1183         $this->_createCostCenters();
1184     
1185         // has no budget
1186         $ta = $this->_createTimeaccounts(array(array(
1187             'title'         => 'TaTest',
1188             'description'   => 'blabla',
1189             'is_open'       => 1,
1190             'status'        => 'not yet billed',
1191             'budget'        => null
1192         )))->getFirstRecord();
1193     
1194         foreach($dates as $date) {
1195             $this->_createTimesheets(array(
1196                 array(
1197                     'account_id' => Tinebase_Core::getUser()->getId(),
1198                     'timeaccount_id' => $ta->getId(),
1199                     'start_date' => $date,
1200                     'duration' => 105,
1201                     'description' => 'ts from ' . (string) $date,
1202                 ))
1203             );
1204         }
1205     
1206         $this->assertEquals(84, $this->_timesheetRecords->count());
1207         $this->_createProducts();
1208         $csDate = clone $this->_referenceDate;
1209         $csDate->subMonth(10);
1210     
1211         $lab = clone $this->_referenceDate;
1212         $this->_createContracts(array(array(
1213             'number'       => 100,
1214             'title'        => 'MyContract',
1215             'description'  => 'unittest',
1216             'container_id' => $this->_sharedContractsContainerId,
1217             'billing_point' => 'begin',
1218             'billing_address_id' => $this->_addressRecords->filter(
1219                 'customer_id', $customer->getId())->filter(
1220                     'type', 'billing')->getFirstRecord()->getId(),
1221     
1222             'start_date' => $csDate,
1223             'end_date' => NULL,
1224             'products' => array(
1225                 array('start_date' => $csDate, 'end_date' => NULL, 'quantity' => 1, 'interval' => 1, 'billing_point' => 'end', 'product_id' => $this->_productRecords->filter('name', 'Hours')->getFirstRecord()->getId())
1226             )
1227         )));
1228     
1229         $json = new Sales_Frontend_Json();
1230     
1231         $date = clone $this->_referenceDate;
1232         // this is set by cli if called by cli
1233         $date->setTime(3,0,0);
1234     
1235         $result = $this->_invoiceController->createAutoInvoices($date);
1236         $this->assertEquals(0, $result['created_count']);
1237     
1238         $date->addMonth(1);
1239         $result = $this->_invoiceController->createAutoInvoices($date);
1240         $this->assertEquals(1, $result['created_count']);
1241         
1242         $invoice1Id = $result['created'][0];
1243         $filter = new Timetracker_Model_TimesheetFilter(array());
1244         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice1Id)));
1245         $timesheets = $this->_timesheetController->search($filter);
1246         $this->assertEquals(63, $timesheets->count());
1247         
1248         $date->addMonth(1);
1249         $result = $this->_invoiceController->createAutoInvoices($date);
1250         $this->assertEquals(1, $result['created_count'], (string) $date);
1251         $invoice2Id = $result['created'][0];
1252         $invoice = $json->getInvoice($invoice2Id);
1253         $this->assertEquals(1, count($invoice['positions']));
1254     
1255         $date->addMonth(1);
1256         $result = $this->_invoiceController->createAutoInvoices($date);
1257         $this->assertEquals(0, $result['created_count']);
1258     
1259         $filter = new Timetracker_Model_TimesheetFilter(array());
1260         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $invoice2Id)));
1261         $timesheets = $this->_timesheetController->search($filter);
1262         $this->assertEquals(21, $timesheets->count());
1263     }
1264     
1265     /**
1266      * tests new fields
1267      */
1268     public function testManualInvoice()
1269     {
1270         $customer = $this->_createCustomers(1)->getFirstRecord();
1271         $this->_createCostCenters();
1272         
1273         $invoice = $this->_invoiceController->create(new Sales_Model_Invoice(array(
1274             'number' => 100,
1275             'description' => 'test',
1276             'address_id' => $this->_addressRecords->getFirstRecord()->getId(),
1277             'costcenter_id' => $this->_costcenterRecords->getFirstRecord()->getId(),
1278             'is_auto' => TRUE,
1279             'price_net' => 200.20,
1280             'price_gross' => 238.45,
1281             'sales_tax' => 19.5
1282         )));
1283         
1284         $this->assertEquals(19.5, $invoice->sales_tax);
1285         $this->assertEquals(200.20, $invoice->price_net);
1286         $this->assertEquals(238.45, $invoice->price_gross);
1287     }
1288     
1289     /**
1290      * tests if timesheets get resetted properly after deleting the invoice
1291      * and recreate the same invoice again containing the same timesheets
1292      */
1293     public function testDeleteAndRunAgainInvoice()
1294     {
1295         $this->_createFullFixtures();
1296     
1297         $date = clone $this->_referenceDate;
1298         $date->addMonth(8);
1299         $i = 0;
1300     
1301         $result = $this->_invoiceController->createAutoInvoices($date);
1302     
1303         $this->assertEquals(6, count($result['created']));
1304         
1305         $tsController = Timetracker_Controller_Timesheet::getInstance();
1306     
1307         // get first valid invoice id from all timesheets
1308         $tsInvoiceIds = array_unique($tsController->getAll()->invoice_id);
1309         sort($tsInvoiceIds);
1310         $tsInvoiceIds = array_reverse($tsInvoiceIds);
1311         $this->assertTrue(! empty($tsInvoiceIds[0]));
1312         $myInvoice = $this->_invoiceController->get($tsInvoiceIds[0]);
1313
1314         $f = new Timetracker_Model_TimesheetFilter(array());
1315         $f->addFilter(new Tinebase_Model_Filter_Text(
1316                 array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $myInvoice->getId())
1317         ));
1318         $myTimesheets = $tsController->search($f);
1319         $this->assertEquals(2, $myTimesheets->count(), 'timesheets not found for invoice ' . $myInvoice->getId());
1320         
1321         $this->_invoiceController->delete(array($myInvoice->getId()));
1322         $allTimesheets = $tsController->getAll();
1323         foreach($allTimesheets as $ts) {
1324             $this->assertSame(NULL, $ts->invoice_id, 'invoice id should be reset');
1325         }
1326         
1327         $this->_invoiceController->createAutoInvoices($date);
1328         
1329         $tsId = $myTimesheets->getFirstRecord()->getId();
1330         
1331         $myTimesheet = $tsController->get($tsId);
1332         $f = new Timetracker_Model_TimesheetFilter(array());
1333         $f->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $myTimesheet->invoice_id)));
1334         
1335         $myTimesheets = $tsController->search($f);
1336         $this->assertEquals(2, $myTimesheets->count());
1337         
1338         foreach($myTimesheets as $ts) {
1339             $this->assertEquals(40, strlen($ts->invoice_id));
1340         }
1341     }
1342     
1343     public function testInterval12LastAutobill()
1344     {
1345         $startDate = clone $this->_referenceDate;
1346         $startDate->subYear(1);
1347         
1348         $this->_createProducts(array(
1349                 array('name' => 'bill yearly',
1350                 'description' => 'bill every year',
1351                 'price' => '1002','accountable' => 'Sales_Model_Product')
1352         ));
1353         $this->_createCustomers(1);
1354         $this->_createCostCenters();
1355         $addressId = $this->_addressRecords->filter(
1356                         'customer_id', $this->_customerRecords->filter(
1357                                 'name', 'Customer1')->getFirstRecord()->getId())->filter(
1358                                         'type', 'billing')->getFirstRecord()->getId();
1359         
1360         // this contract begins 6 months before the first invoice will be created
1361         $this->_createContracts(array(array(
1362                 'number'       => 100,
1363                 'title'        => 'MyContract',
1364                 'description'  => 'unittest',
1365                 'container_id' => $this->_sharedContractsContainerId,
1366                 'billing_point' => 'begin',
1367                 'billing_address_id' => $addressId,
1368                 'interval' => 12,
1369                 'start_date' => $startDate,
1370                 'last_autobill' => $startDate,
1371                 'end_date' => NULL,
1372                 'products' => array(
1373                         array('product_id' => $this->_productRecords->getByIndex(0)->getId(), 'quantity' => 1, 'interval' => 12, 'last_autobill' => $startDate),
1374                 )
1375         )));
1376         
1377         $startDate = clone $this->_referenceDate;
1378         $startDate->subMonth(1);
1379         
1380         $startDate = clone $this->_referenceDate;
1381         $startDate->addDay(5);
1382         $result = $this->_invoiceController->createAutoInvoices($startDate);
1383         
1384         $this->assertEquals(1, $result['created_count']);
1385         
1386         $invoices = $this->_invoiceController->getAll();
1387         $firstInvoice = $invoices->getFirstRecord();
1388         $this->assertEquals(1, $invoices->count());
1389         
1390         $filter = new Sales_Model_InvoicePositionFilter(array());
1391         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'in', 'value' => $invoices->getArrayOfIds())));
1392         
1393         $invoicePositions = Sales_Controller_InvoicePosition::getInstance()->search($filter);
1394         
1395         $this->assertEquals(12, $invoicePositions->count());
1396         
1397         $contract = $this->_contractRecords->getFirstRecord();
1398         $contract->setTimezone(Tinebase_Core::getUserTimezone());
1399         
1400         $autobillDate = clone $this->_referenceDate;
1401         
1402         for ($i = 0; $i < 8; $i++) {
1403             $startDate->addDay(1);
1404             $result = $this->_invoiceController->createAutoInvoices($startDate);
1405             $this->assertEquals(0, $result['created_count']);
1406         }
1407         
1408         $productAggregate = Sales_Controller_ProductAggregate::getInstance()->getAll()->getFirstRecord();
1409         $productAggregate->setTimezone(Tinebase_Core::getUserTimezone());
1410         $this->assertEquals($autobillDate, $productAggregate->last_autobill);
1411     }
1412     
1413     /**
1414      * tests if uncleared invoices gets deleted
1415      */
1416     public function testUnclearedDeletion()
1417     {
1418         $this->_createFullFixtures();
1419     
1420         $date = clone $this->_referenceDate;
1421         $date->addMonth(8);
1422         $i = 0;
1423     
1424         $result = $this->_invoiceController->createAutoInvoices($date);
1425     
1426         $this->assertEquals(6, count($result['created']));
1427         
1428         $invoice = $this->_invoiceController->get($result['created'][0]);
1429         $invoice->cleared = 'CLEARED';
1430         $this->_invoiceController->update($invoice);
1431         
1432         $cli = new Sales_Frontend_Cli();
1433         $cli->removeUnbilledAutoInvoices();
1434         
1435         $invoices = $this->_invoiceController->getAll();
1436         
1437         $this->assertEquals(1, $invoices->count());
1438     }
1439     
1440     /**
1441      * if no productaggregates are defined for a contract, but 
1442      * accountables are related, use default billing Info from accountable
1443      * (product will be created if it does not exist - is needed in the invoice position)
1444      */
1445     public function testDefaultAutobillInterval()
1446     {
1447         $startDate = clone $this->_referenceDate;
1448         $startDate->subYear(1);
1449         
1450         $this->_createCustomers(1);
1451         $this->_createCostCenters();
1452     
1453         $this->_createTimeaccounts(array(array(
1454                 'title'         => 'TA',
1455                 'description'   => 'blabla',
1456                 'is_open'       => 1,
1457                 'status'        => 'to bill',
1458                 'budget'        => 100
1459         )));
1460         
1461         $addressId = $this->_addressRecords->filter(
1462                         'customer_id', $this->_customerRecords->filter(
1463                                 'name', 'Customer1')->getFirstRecord()->getId())->filter(
1464                                         'type', 'billing')->getFirstRecord()->getId();
1465         
1466         // this contract begins 6 months before the first invoice will be created
1467         $this->_createContracts(array(array(
1468                 'number'       => 100,
1469                 'title'        => 'MyContract',
1470                 'description'  => 'unittest',
1471                 'container_id' => $this->_sharedContractsContainerId,
1472                 'billing_point' => 'begin',
1473                 'billing_address_id' => $addressId,
1474     
1475                 'start_date' => $startDate,
1476                 'end_date' => NULL,
1477         )));
1478     
1479         $startDate = clone $this->_referenceDate;
1480         $startDate->subMonth(1);
1481     
1482         $startDate = clone $this->_referenceDate;
1483         $startDate->addDay(5);
1484         $result = $this->_invoiceController->createAutoInvoices($startDate);
1485     
1486         $this->assertEquals(1, $result['created_count']);
1487         
1488         $filter = new Sales_Model_ProductFilter(array());
1489         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'accountable', 'operator' => 'equals', 'value' => 'Timetracker_Model_Timeaccount')));
1490         
1491         $products = Sales_Controller_Product::getInstance()->search($filter);
1492         $this->assertEquals(1, $products->count());
1493         
1494         
1495         $this->assertEquals('Timetracker_Model_Timeaccount', $products->getFirstRecord()->accountable);
1496         
1497         $filter = new Sales_Model_InvoicePositionFilter(array());
1498         $filter->addFilter(new Tinebase_Model_Filter_Text(array('field' => 'invoice_id', 'operator' => 'equals', 'value' => $result['created'][0])));
1499         
1500         $invoicePositions = Sales_Controller_InvoicePosition::getInstance()->search($filter);
1501         
1502         $this->assertEquals(1, $invoicePositions->count());
1503     }
1504 }