| - | - | - | - | - | + | + | + | + | + | + | |||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + | - | + |
|
|
| - - | + | + + + or view + + + + No order to invoice, create + + /> | |||||
| + | - | + |
|
|
| - + | + + + or view + + + Nothing to invoice, create + - | ||||||
| + | - | - | + | + |
| + |
| + |
| + |
|
+ When invoicing on timesheet, OpenERP uses the + pricelist of the contract which uses the price + defined on the product related to each employee to + define the customer invoice price rate. +
+Once the end date of the contract is passed or the maximum number of service - units is reached, the account manager - is warned by email to renew the contract. + units (e.g. support contract) is + reached, the account manager is warned + by email to renew the contract with the + customer.
+ Define a specific service (e.g. Senior Consultant) + and price for some users to use these data instead + of the default values when invoicing the customer. +
++ OpenERP will recursively search on parent accounts + to check if specific conditions are defined for a + specific user. This allows to set invoicing + conditions for a group of contracts. +
++ No activity yet on this contract. +
+ In OpenERP, contracts and projects are implemented using + analytic account. So, you can track costs and revenues to analyse + your margins easily. +
+ Costs will be created automatically when you register supplier + invoices, expenses or timesheets. +
+ Revenues will be created automatically when you create customer + invoices. Customer invoices can be created based on sale orders + (fixed price invoices), on timesheets (based on the work done) or + on expenses (e.g. reinvoicing of travel costs). +
+- Click to record your timesheets. + Click to record activities.
You can register and track your workings hours by project every day. Every time spent on a project will become a cost in the - analytic accounting and can be re-invoiced to customers if - required. + analytic accounting/contract and can be re-invoiced to + customers if required.
Do you want to show details of each activity to your customer?
+- Click to go to your timesheet. + New timesheet to approve.
You must record timesheets every day and confirm at the end - of the week. Once the timesheet is confirmed, it his sent to - the manager for validation. + of the week. Once the timesheet is confirmed, it should be + validated by a manager.
Timesheets can also be invoiced to customers, depending on the - configuration of the project. + configuration of each project's related contract.
| + |
|
TOTAL | +Total | |
|---|---|---|---|---|
|
@@ -26,31 +26,47 @@
|
+ | |||
|
-
-
TOTAL
+
+ Total
|
+ |
|
+ |
+ Click to add projects/analytic accounts you worked on. +
+ You will be able to register your working hours and + activities. +
+ By default, you record timesheets on analytic accounts. + But if an analytic account represents a customer + contract, you can change the type of the analytic + account to 'Contract or Project' to setup the invoicing + options. +
+\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n\n', 'plaintext mail incorrectly parsed') + # Do: post a new message, with a known partner + test_msg_id = '
', compose.body, 'mail.message body is incorrect') + # self.assertIn('Administrator wrote:Pigs rules
', compose.body, 'mail.message body is incorrect') self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect') # Test: mail.message: attachments for attach in compose.attachment_ids: @@ -526,20 +573,6 @@ class test_mail(TestMailMockups): def test_30_message_read(self): """ Tests for message_read and expandables. """ - - def _print_debug(message, debug=True, prefix=''): - if debug: - print prefix, message - - def _print_msg(message, debug=True, prefix=''): - if not debug: - return - if message.get('type') == 'expandable': - print prefix, 'expandable', message - else: - print prefix, 'message', message.get('body'), message.get('id'), message.get('ancestor_id') - - _debug = False cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)] @@ -574,7 +607,6 @@ class test_mail(TestMailMockups): # CASE1: message_read with domain, threaded # We simulate an entire flow, using the expandables to test them # ---------------------------------------- - _print_debug('\nCASE1: message_read with domain, threaded\n', _debug) # Do: read last message, threaded read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1) @@ -582,11 +614,10 @@ class test_mail(TestMailMockups): # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables') self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent') - self.assertEqual(read_msg_list[1].get('ancestor_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header') + self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header') # Data: get expandables new_threads_exp, new_msg_exp = None, None for msg in read_msg_list: - _print_msg(msg, _debug) if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1: new_threads_exp = msg elif msg.get('type') == 'expandable': @@ -596,15 +627,12 @@ class test_mail(TestMailMockups): self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable') domain = new_msg_exp.get('domain', []) # Test: expandable, conditions in domain - _print_debug('Executing %s' % domain, _debug) self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition') self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition') self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition') - self.assertEqual(new_msg_exp.get('ancestor_id'), msg_id2, 'new messages expandable should have ancestor_id set to the thread header') + self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have ancestor_id set to the thread header') # Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS) read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id2) - for msg in read_msg_list: - _print_msg(msg, _debug) read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable'] # Test: other message in thread have been fetch self.assertEqual(set([msg_id4, msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return all the previous messages') @@ -613,21 +641,19 @@ class test_mail(TestMailMockups): self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable') domain = new_threads_exp.get('domain', []) # Test: expandable, conditions in domain - _print_debug('Executing %s' % domain, _debug) for condition in pigs_domain: self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter') - self.assertFalse(new_threads_exp.get('ancestor_id'), 'new threads expandable should not have an ancestor_id') + self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an ancestor_id') # Do: message_read with domain, thread_level=1 (should be imposed by JS) read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1) read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable'] # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables self.assertEqual(len(read_msg_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables') self.assertEqual(set([msg_id1, msg_id9]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent') - self.assertEqual(read_msg_list[1].get('ancestor_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header') + self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header') # Data: get expandables new_threads_exp, new_msg_exp = None, None for msg in read_msg_list: - _print_msg(msg, _debug) if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1: new_threads_exp = msg elif msg.get('type') == 'expandable': @@ -637,15 +663,12 @@ class test_mail(TestMailMockups): self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable') domain = new_msg_exp.get('domain', []) # Test: expandable, conditions in domain - _print_debug('Executing %s' % domain, _debug) self.assertIn(('id', 'child_of', msg_id1), domain, 'new messages expandable domain should contain a child_of condition') self.assertIn(('id', '>=', msg_id3), domain, 'new messages expandable domain should contain an id greater than condition') self.assertIn(('id', '<=', msg_id7), domain, 'new messages expandable domain should contain an id less than condition') - self.assertEqual(new_msg_exp.get('ancestor_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header') + self.assertEqual(new_msg_exp.get('parent_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header') # Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS) read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id1) - for msg in read_msg_list: - _print_msg(msg, _debug) read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable'] # Test: other message in thread have been fetch self.assertEqual(set([msg_id3, msg_id5, msg_id7]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent') @@ -654,7 +677,6 @@ class test_mail(TestMailMockups): self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable') domain = new_threads_exp.get('domain', []) # Test: expandable, conditions in domain - _print_debug('Executing %s' % domain, _debug) for condition in pigs_domain: self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter') # Do: message_read with domain, thread_level=1 (should be imposed by JS) @@ -663,13 +685,10 @@ class test_mail(TestMailMockups): # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables self.assertEqual(len(read_msg_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched') self.assertEqual([msg_id0], read_msg_ids, 'message_read after 2 More should return only 1 last message') - for msg in read_msg_list: - _print_msg(msg, _debug) # ---------------------------------------- # CASE2: message_read with domain, flat # ---------------------------------------- - _print_debug('\nCASE2: message_read with domain, flat\n', _debug) # Do: read 2 lasts message, flat read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=2, thread_level=0) @@ -677,12 +696,11 @@ class test_mail(TestMailMockups): # Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable') self.assertEqual(set([msg_id9, msg_id10]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages') - self.assertFalse(read_msg_list[0].get('ancestor_id'), 'message_read flat should set the ancestor as False') - self.assertFalse(read_msg_list[1].get('ancestor_id'), 'message_read flat should set the ancestor as False') + self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False') + self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False') # Data: get expandables new_threads_exp, new_msg_exp = None, None for msg in read_msg_list: - _print_msg(msg, _debug) if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1: new_threads_exp = msg @@ -690,7 +708,6 @@ class test_mail(TestMailMockups): self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable') domain = new_threads_exp.get('domain', []) # Test: expandable, conditions in domain - _print_debug('Executing %s' % domain, _debug) for condition in pigs_domain: self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter') # Do: message_read with domain, thread_level=0 (should be imposed by JS) @@ -700,8 +717,6 @@ class test_mail(TestMailMockups): self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable') self.assertEqual([msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8], read_msg_ids, 'message_read, More on flat, should return all remaning messages') - for msg in read_msg_list: - _print_msg(msg, _debug) def test_40_needaction(self): """ Tests for mail.message needaction. """ @@ -760,22 +775,11 @@ class test_mail(TestMailMockups): reply_msg = MAIL_TEMPLATE.format(to='Pretty PigsPigs rules
+ Click to add a personal note. +
+ Use notes to organize personal tasks or notes. All + notes are private; no one else will be able to see them. However + you can share some notes with other people by inviting followers + on the note. (Useful for meeting minutes, especially if + you activate the pad feature for collaborative writings). +
+ You can customize how you process your notes/tasks by adding, + removing or modifying columns. +
+
+ To invoice or setup invoicing and renewal options, go to the related contract: