/
test_sale_service.py
601 lines (521 loc) · 32.3 KB
/
test_sale_service.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.sale_timesheet.tests.common import TestCommonSaleTimesheetNoChart
from odoo.exceptions import UserError, ValidationError
class TestSaleService(TestCommonSaleTimesheetNoChart):
""" This test suite provide checks for miscellaneous small things. """
@classmethod
def setUpClass(cls):
super(TestSaleService, cls).setUpClass()
# set up
cls.setUpEmployees()
cls.setUpServiceProducts()
cls.sale_order = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
'partner_id': cls.partner_customer_usd.id,
'partner_invoice_id': cls.partner_customer_usd.id,
'partner_shipping_id': cls.partner_customer_usd.id,
})
def test_sale_service(self):
""" Test task creation when confirming a sale_order with the corresponding product """
sale_order_line = self.env['sale.order.line'].create({
'order_id': self.sale_order.id,
'name': self.product_delivery_timesheet2.name,
'product_id': self.product_delivery_timesheet2.id,
'product_uom_qty': 50,
'product_uom': self.product_delivery_timesheet2.uom_id.id,
'price_unit': self.product_delivery_timesheet2.list_price
})
self.sale_order.order_line._compute_product_updatable()
self.assertTrue(sale_order_line.product_updatable)
self.sale_order.action_confirm()
self.sale_order.order_line._compute_product_updatable()
self.sale_order.action_confirm()
self.sale_order.order_line._compute_product_updatable()
self.assertFalse(sale_order_line.product_updatable)
self.assertEqual(self.sale_order.invoice_status, 'no', 'Sale Service: there should be nothing to invoice after validation')
# check task creation
project = self.project_global
task = project.task_ids.filtered(lambda t: t.name == '%s: %s' % (self.sale_order.name, self.product_delivery_timesheet2.name))
self.assertTrue(task, 'Sale Service: task is not created, or it badly named')
self.assertEqual(task.partner_id, self.sale_order.partner_id, 'Sale Service: customer should be the same on task and on SO')
self.assertEqual(task.email_from, self.sale_order.partner_id.email, 'Sale Service: Task Email should be the same as the SO customer Email')
# log timesheet on task
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': project.id,
'task_id': task.id,
'unit_amount': 50,
'employee_id': self.employee_manager.id,
})
self.assertEqual(self.sale_order.invoice_status, 'to invoice', 'Sale Service: there should be sale_ordermething to invoice after registering timesheets')
self.sale_order._create_invoices()
self.assertTrue(sale_order_line.product_uom_qty == sale_order_line.qty_delivered == sale_order_line.qty_invoiced, 'Sale Service: line should be invoiced completely')
self.assertEqual(self.sale_order.invoice_status, 'invoiced', 'Sale Service: SO should be invoiced')
self.assertEqual(self.sale_order.tasks_count, 1, "A task should have been created on SO confirmation.")
# Add a line on the confirmed SO, and it should generate a new task directly
product_service_task = self.env['product.product'].create({
'name': "Delivered Service",
'standard_price': 30,
'list_price': 90,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': self.env.ref('uom.product_uom_hour').id,
'uom_po_id': self.env.ref('uom.product_uom_hour').id,
'default_code': 'SERV-DELI',
'service_type': 'timesheet',
'service_tracking': 'task_global_project',
'project_id': project.id
})
self.env['sale.order.line'].create({
'name': product_service_task.name,
'product_id': product_service_task.id,
'product_uom_qty': 10,
'product_uom': product_service_task.uom_id.id,
'price_unit': product_service_task.list_price,
'order_id': self.sale_order.id,
})
self.assertEqual(self.sale_order.tasks_count, 2, "Adding a new service line on a confirmer SO should create a new task.")
# not possible to delete a task linked to a SOL
with self.assertRaises(ValidationError):
task.unlink()
def test_timesheet_uom(self):
""" Test timesheet invoicing and uom conversion """
# create SO and confirm it
uom_days = self.env.ref('uom.product_uom_day')
sale_order_line = self.env['sale.order.line'].create({
'order_id': self.sale_order.id,
'name': self.product_delivery_timesheet3.name,
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 5,
'product_uom': uom_days.id,
'price_unit': self.product_delivery_timesheet3.list_price
})
self.sale_order.action_confirm()
task = self.env['project.task'].search([('sale_line_id', '=', sale_order_line.id)])
# let's log some timesheets
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': task.project_id.id,
'task_id': task.id,
'unit_amount': 16,
'employee_id': self.employee_manager.id,
})
self.assertEqual(sale_order_line.qty_delivered, 2, 'Sale: uom conversion of timesheets is wrong')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': task.project_id.id,
'task_id': task.id,
'unit_amount': 24,
'employee_id': self.employee_user.id,
})
self.sale_order._create_invoices()
self.assertEqual(self.sale_order.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
def test_task_so_line_assignation(self):
# create SO line and confirm it
so_line_deliver_global_project = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet2.name,
'product_id': self.product_delivery_timesheet2.id,
'product_uom_qty': 10,
'product_uom': self.product_delivery_timesheet2.uom_id.id,
'price_unit': self.product_delivery_timesheet2.list_price,
'order_id': self.sale_order.id,
})
so_line_deliver_global_project.product_id_change()
self.sale_order.action_confirm()
task_serv2 = self.env['project.task'].search([('sale_line_id', '=', so_line_deliver_global_project.id)])
# let's log some timesheets (on the project created by so_line_ordered_project_only)
timesheets = self.env['account.analytic.line']
timesheets |= self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': task_serv2.project_id.id,
'task_id': task_serv2.id,
'unit_amount': 4,
'employee_id': self.employee_user.id,
})
timesheets |= self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': task_serv2.project_id.id,
'task_id': task_serv2.id,
'unit_amount': 1,
'employee_id': self.employee_manager.id,
})
self.assertTrue(all([billing_type == 'billable_time' for billing_type in timesheets.mapped('timesheet_invoice_type')]), "All timesheets linked to the task should be on 'billable time'")
self.assertEqual(so_line_deliver_global_project.qty_to_invoice, 5, "Quantity to invoice should have been increased when logging timesheet on delivered quantities task")
# invoice SO, and validate invoice
invoice = self.sale_order._create_invoices()[0]
invoice.post()
# make task non billable
task_serv2.write({'sale_line_id': False})
self.assertTrue(all([billing_type == 'billable_time' for billing_type in timesheets.mapped('timesheet_invoice_type')]), "billable type of timesheet should not change when tranfering task into another project")
self.assertEqual(task_serv2.timesheet_ids.mapped('so_line'), so_line_deliver_global_project, "Old invoiced timesheet are not modified when changing the task SO line")
# try to update timesheets, catch error 'You cannot modify invoiced timesheet'
with self.assertRaises(UserError):
timesheets.write({'so_line': False})
def test_delivered_quantity(self):
# create SO line and confirm it
so_line_deliver_new_task_project = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet3.name,
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 10,
'product_uom': self.product_delivery_timesheet3.uom_id.id,
'price_unit': self.product_delivery_timesheet3.list_price,
'order_id': self.sale_order.id,
})
so_line_deliver_new_task_project.product_id_change()
self.sale_order.action_confirm()
task_serv2 = self.env['project.task'].search([('sale_line_id', '=', so_line_deliver_new_task_project.id)])
# add a timesheet
timesheet1 = self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': task_serv2.project_id.id,
'task_id': task_serv2.id,
'unit_amount': 4,
'employee_id': self.employee_user.id,
})
self.assertEqual(so_line_deliver_new_task_project.qty_delivered, timesheet1.unit_amount, 'Delivered quantity should be the same then its only related timesheet.')
# remove the only timesheet
timesheet1.unlink()
self.assertEqual(so_line_deliver_new_task_project.qty_delivered, 0.0, 'Delivered quantity should be reset to zero, since there is no more timesheet.')
# log 2 new timesheets
timesheet2 = self.env['account.analytic.line'].create({
'name': 'Test Line 2',
'project_id': task_serv2.project_id.id,
'task_id': task_serv2.id,
'unit_amount': 4,
'employee_id': self.employee_user.id,
})
timesheet3 = self.env['account.analytic.line'].create({
'name': 'Test Line 3',
'project_id': task_serv2.project_id.id,
'task_id': task_serv2.id,
'unit_amount': 2,
'employee_id': self.employee_user.id,
})
self.assertEqual(so_line_deliver_new_task_project.qty_delivered, timesheet2.unit_amount + timesheet3.unit_amount, 'Delivered quantity should be the sum of the 2 timesheets unit amounts.')
# remove timesheet2
timesheet2.unlink()
self.assertEqual(so_line_deliver_new_task_project.qty_delivered, timesheet3.unit_amount, 'Delivered quantity should be reset to the sum of remaining timesheets unit amounts.')
def test_sale_create_task(self):
""" Check that confirming SO create correctly a task, and reconfirming it does not create a second one. Also check changing
the ordered quantity of a SO line that have created a task should update the planned hours of this task.
"""
so_line1 = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet3.name,
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 7,
'product_uom': self.product_delivery_timesheet3.uom_id.id,
'price_unit': self.product_delivery_timesheet3.list_price,
'order_id': self.sale_order.id,
})
# confirm SO
self.sale_order.action_confirm()
self.assertTrue(so_line1.task_id, "SO confirmation should create a task and link it to SOL")
self.assertTrue(so_line1.project_id, "SO confirmation should create a project and link it to SOL")
self.assertEqual(self.sale_order.tasks_count, 1, "The SO should have only one task")
self.assertEqual(so_line1.task_id.sale_line_id, so_line1, "The created task is also linked to its origin sale line, for invoicing purpose.")
self.assertFalse(so_line1.task_id.user_id, "The created task should be unassigned")
self.assertEqual(so_line1.product_uom_qty, so_line1.task_id.planned_hours, "The planned hours should be the same as the ordered quantity of the native SO line")
so_line1.write({'product_uom_qty': 20})
self.assertEqual(so_line1.product_uom_qty, so_line1.task_id.planned_hours, "The planned hours should have changed when updating the ordered quantity of the native SO line")
# cancel SO
self.sale_order.action_cancel()
self.assertTrue(so_line1.task_id, "SO cancellation should keep the task")
self.assertTrue(so_line1.project_id, "SO cancellation should create a project")
self.assertEqual(self.sale_order.tasks_count, 1, "The SO should still have only one task")
self.assertEqual(so_line1.task_id.sale_line_id, so_line1, "The created task is also linked to its origin sale line, for invoicing purpose.")
so_line1.write({'product_uom_qty': 30})
self.assertEqual(so_line1.product_uom_qty, so_line1.task_id.planned_hours, "The planned hours should have changed when updating the ordered quantity, even after SO cancellation")
# reconfirm SO
self.sale_order.action_draft()
self.sale_order.action_confirm()
self.assertTrue(so_line1.task_id, "SO reconfirmation should not have create another task")
self.assertTrue(so_line1.project_id, "SO reconfirmation should bit have create another project")
self.assertEqual(self.sale_order.tasks_count, 1, "The SO should still have only one task")
self.assertEqual(so_line1.task_id.sale_line_id, so_line1, "The created task is also linked to its origin sale line, for invoicing purpose.")
self.sale_order.action_done()
with self.assertRaises(UserError):
so_line1.write({'product_uom_qty': 20})
def test_sale_create_project(self):
""" A SO with multiple product that should create project (with and without template) like ;
Line 1 : Service 1 create project with Template A ===> project created with template A
Line 2 : Service 2 create project no template ==> empty project created
Line 3 : Service 3 create project with Template A ===> Don't create any project because line 1 has already created a project with template A
Line 4 : Service 4 create project no template ==> Don't create any project because line 2 has already created an empty project
Line 5 : Service 5 create project with Template B ===> project created with template B
"""
# second project template and its associated product
project_template2 = self.env['project.project'].create({
'name': 'Second Project TEMPLATE for services',
'allow_timesheets': True,
'active': False, # this template is archived
})
Stage = self.env['project.task.type'].with_context(default_project_id=project_template2.id)
stage1_tmpl2 = Stage.create({
'name': 'Stage 1',
'sequence': 1
})
stage2_tmpl2 = Stage.create({
'name': 'Stage 2',
'sequence': 2
})
product_deli_ts_tmpl = self.env['product.product'].create({
'name': "Service delivered, create project only based on template B",
'standard_price': 17,
'list_price': 34,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': self.env.ref('uom.product_uom_hour').id,
'uom_po_id': self.env.ref('uom.product_uom_hour').id,
'default_code': 'SERV-DELI4',
'service_type': 'timesheet',
'service_tracking': 'project_only',
'project_template_id': project_template2.id,
'project_id': False,
'taxes_id': False,
'property_account_income_id': self.account_sale.id,
})
# create 5 so lines
so_line1 = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet5.name,
'product_id': self.product_delivery_timesheet5.id,
'product_uom_qty': 11,
'product_uom': self.product_delivery_timesheet5.uom_id.id,
'price_unit': self.product_delivery_timesheet5.list_price,
'order_id': self.sale_order.id,
})
so_line2 = self.env['sale.order.line'].create({
'name': self.product_order_timesheet4.name,
'product_id': self.product_order_timesheet4.id,
'product_uom_qty': 10,
'product_uom': self.product_order_timesheet4.uom_id.id,
'price_unit': self.product_order_timesheet4.list_price,
'order_id': self.sale_order.id,
})
so_line3 = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet5.name,
'product_id': self.product_delivery_timesheet5.id,
'product_uom_qty': 5,
'product_uom': self.product_delivery_timesheet5.uom_id.id,
'price_unit': self.product_delivery_timesheet5.list_price,
'order_id': self.sale_order.id,
})
so_line4 = self.env['sale.order.line'].create({
'name': self.product_delivery_manual3.name,
'product_id': self.product_delivery_manual3.id,
'product_uom_qty': 4,
'product_uom': self.product_delivery_manual3.uom_id.id,
'price_unit': self.product_delivery_manual3.list_price,
'order_id': self.sale_order.id,
})
so_line5 = self.env['sale.order.line'].create({
'name': product_deli_ts_tmpl.name,
'product_id': product_deli_ts_tmpl.id,
'product_uom_qty': 8,
'product_uom': product_deli_ts_tmpl.uom_id.id,
'price_unit': product_deli_ts_tmpl.list_price,
'order_id': self.sale_order.id,
})
# confirm SO
self.sale_order.action_confirm()
# check each line has or no generate something
self.assertTrue(so_line1.project_id, "Line1 should have create a project based on template A")
self.assertTrue(so_line2.project_id, "Line2 should have create an empty project")
self.assertEqual(so_line3.project_id, so_line1.project_id, "Line3 should reuse project of line1")
self.assertEqual(so_line4.project_id, so_line2.project_id, "Line4 should reuse project of line2")
self.assertTrue(so_line4.task_id, "Line4 should have create a new task, even if no project created.")
self.assertTrue(so_line5.project_id, "Line5 should have create a project based on template B")
# check all generated project should be active, even if the template is not
self.assertTrue(so_line1.project_id.active, "Project of Line1 should be active")
self.assertTrue(so_line2.project_id.active, "Project of Line2 should be active")
self.assertTrue(so_line5.project_id.active, "Project of Line5 should be active")
# check generated stuff are correct
self.assertTrue(so_line1.project_id in self.project_template_state.project_ids, "Stage 1 from template B should be part of project from so line 1")
self.assertTrue(so_line1.project_id in self.project_template_state.project_ids, "Stage 1 from template B should be part of project from so line 1")
self.assertTrue(so_line5.project_id in stage1_tmpl2.project_ids, "Stage 1 from template B should be part of project from so line 5")
self.assertTrue(so_line5.project_id in stage2_tmpl2.project_ids, "Stage 2 from template B should be part of project from so line 5")
self.assertTrue(so_line1.project_id.allow_timesheets, "Create project should allow timesheets")
self.assertTrue(so_line2.project_id.allow_timesheets, "Create project should allow timesheets")
self.assertTrue(so_line5.project_id.allow_timesheets, "Create project should allow timesheets")
self.assertEqual(so_line4.task_id.project_id, so_line2.project_id, "Task created with line 4 should have the project based on template A of the SO.")
self.assertEqual(so_line1.project_id.sale_line_id, so_line1, "SO line of project with template A should be the one that create it.")
self.assertEqual(so_line2.project_id.sale_line_id, so_line2, "SO line of project should be the one that create it.")
self.assertEqual(so_line5.project_id.sale_line_id, so_line5, "SO line of project with template B should be the one that create it.")
def test_sale_task_in_project_with_project(self):
""" This will test the new 'task_in_project' service tracking correctly creates tasks and projects
when a project_id is configured on the parent sale_order (ref task #1915660).
Setup:
- Configure a project_id on the SO
- SO line 1: a product with its delivery tracking set to 'task_in_project'
- SO line 2: the same product as SO line 1
- SO line 3: a product with its delivery tracking set to 'project_only'
- Confirm sale_order
Expected result:
- 2 tasks created on the project_id configured on the SO
- 1 project created with the correct template for the 'project_only' product
"""
self.sale_order.write({'project_id': self.project_global.id})
self.sale_order._onchange_project_id()
self.assertEqual(self.sale_order.analytic_account_id, self.analytic_account_sale, "Changing the project on the SO should set the analytic account accordingly.")
so_line1 = self.env['sale.order.line'].create({
'name': self.product_order_timesheet3.name,
'product_id': self.product_order_timesheet3.id,
'product_uom_qty': 11,
'product_uom': self.product_order_timesheet3.uom_id.id,
'price_unit': self.product_order_timesheet3.list_price,
'order_id': self.sale_order.id,
})
so_line2 = self.env['sale.order.line'].create({
'name': self.product_order_timesheet3.name,
'product_id': self.product_order_timesheet3.id,
'product_uom_qty': 10,
'product_uom': self.product_order_timesheet3.uom_id.id,
'price_unit': self.product_order_timesheet3.list_price,
'order_id': self.sale_order.id,
})
so_line3 = self.env['sale.order.line'].create({
'name': self.product_order_timesheet4.name,
'product_id': self.product_order_timesheet4.id,
'product_uom_qty': 5,
'product_uom': self.product_order_timesheet4.uom_id.id,
'price_unit': self.product_order_timesheet4.list_price,
'order_id': self.sale_order.id,
})
# temporary project_template_id for our checks
self.product_order_timesheet4.write({
'project_template_id': self.project_template.id
})
self.sale_order.action_confirm()
# remove it after the confirm because other tests don't like it
self.product_order_timesheet4.write({
'project_template_id': False
})
self.assertTrue(so_line1.task_id, "so_line1 should create a task as its product's service_tracking is set as 'task_in_project'")
self.assertEqual(so_line1.task_id.project_id, self.project_global, "The project on so_line1's task should be project_global as configured on its parent sale_order")
self.assertTrue(so_line2.task_id, "so_line2 should create a task as its product's service_tracking is set as 'task_in_project'")
self.assertEqual(so_line2.task_id.project_id, self.project_global, "The project on so_line2's task should be project_global as configured on its parent sale_order")
self.assertFalse(so_line3.task_id.name, "so_line3 should not create a task as its product's service_tracking is set as 'project_only'")
self.assertNotEqual(so_line3.project_id, self.project_template, "so_line3 should create a new project and not directly use the configured template")
self.assertIn(self.project_template.name, so_line3.project_id.name, "The created project for so_line3 should use the configured template")
def test_sale_task_in_project_without_project(self):
""" This will test the new 'task_in_project' service tracking correctly creates tasks and projects
when the parent sale_order does NOT have a configured project_id (ref task #1915660).
Setup:
- SO line 1: a product with its delivery tracking set to 'task_in_project'
- Confirm sale_order
Expected result:
- 1 project created with the correct template for the 'task_in_project' because the SO
does not have a configured project_id
- 1 task created from this new project
"""
so_line1 = self.env['sale.order.line'].create({
'name': self.product_order_timesheet3.name,
'product_id': self.product_order_timesheet3.id,
'product_uom_qty': 10,
'product_uom': self.product_order_timesheet3.uom_id.id,
'price_unit': self.product_order_timesheet3.list_price,
'order_id': self.sale_order.id,
})
# temporary project_template_id for our checks
self.product_order_timesheet3.write({
'project_template_id': self.project_template.id
})
self.sale_order.action_confirm()
# remove it after the confirm because other tests don't like it
self.product_order_timesheet3.write({
'project_template_id': False
})
self.assertTrue(so_line1.task_id, "so_line1 should create a task as its product's service_tracking is set as 'task_in_project'")
self.assertNotEqual(so_line1.project_id, self.project_template, "so_line1 should create a new project and not directly use the configured template")
self.assertIn(self.project_template.name, so_line1.project_id.name, "The created project for so_line1 should use the configured template")
def test_billable_task_and_subtask(self):
""" Test if subtasks and tasks are billed on the correct SO line """
# create SO line and confirm it
so_line_deliver_new_task_project = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet3.name,
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 10,
'product_uom': self.product_delivery_timesheet3.uom_id.id,
'price_unit': self.product_delivery_timesheet3.list_price,
'order_id': self.sale_order.id,
})
so_line_deliver_new_task_project_2 = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet3.name + "(2)",
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 10,
'product_uom': self.product_delivery_timesheet3.uom_id.id,
'price_unit': self.product_delivery_timesheet3.list_price,
'order_id': self.sale_order.id,
})
so_line_deliver_new_task_project.product_id_change()
so_line_deliver_new_task_project_2.product_id_change()
self.sale_order.action_confirm()
project = so_line_deliver_new_task_project.project_id
task = so_line_deliver_new_task_project.task_id
self.assertEqual(project.sale_line_id, so_line_deliver_new_task_project, "The created project should be linked to the so line")
self.assertEqual(task.sale_line_id, so_line_deliver_new_task_project, "The created task should be linked to the so line")
# create a new task and subtask
subtask = self.env['project.task'].create({
'parent_id': task.id,
'project_id': project.id,
'name': '%s: substask1' % (task.name,),
})
task2 = self.env['project.task'].create({
'project_id': project.id,
'name': '%s: substask1' % (task.name,)
})
self.assertEqual(subtask.sale_line_id, task.sale_line_id, "By, default, a child task should have the same SO line than its mother")
self.assertEqual(task2.sale_line_id, project.sale_line_id, "A new task in a billable project should have the same SO line than its project")
self.assertEqual(task2.partner_id, so_line_deliver_new_task_project.order_partner_id, "A new task in a billable project should have the same SO line than its project")
# moving subtask in another project
subtask.write({'project_id': self.project_global.id})
self.assertEqual(subtask.sale_line_id, task.sale_line_id, "A child task should always have the same SO line than its mother, even when changing project")
self.assertEqual(subtask.sale_line_id, so_line_deliver_new_task_project)
# changing the SO line of the mother task
task.write({'sale_line_id': so_line_deliver_new_task_project_2.id})
self.assertEqual(subtask.sale_line_id, so_line_deliver_new_task_project, "A child task is not impacted by the change of SO line of its mother")
self.assertEqual(task.sale_line_id, so_line_deliver_new_task_project_2, "A mother task can have its SO line set manually")
# changing the SO line of a subtask
subtask.write({'sale_line_id': so_line_deliver_new_task_project_2.id})
self.assertEqual(subtask.sale_line_id, so_line_deliver_new_task_project_2, "A child can have its SO line set manually")
def test_change_ordered_qty(self):
""" Changing the ordered quantity of a SO line that have created a task should update the planned hours of this task """
sale_order_line = self.env['sale.order.line'].create({
'order_id': self.sale_order.id,
'name': self.product_delivery_timesheet2.name,
'product_id': self.product_delivery_timesheet2.id,
'product_uom_qty': 50,
'product_uom': self.product_delivery_timesheet2.uom_id.id,
'price_unit': self.product_delivery_timesheet2.list_price
})
self.sale_order.action_confirm()
self.assertEqual(sale_order_line.product_uom_qty, sale_order_line.task_id.planned_hours, "The planned hours should be the same as the ordered quantity of the native SO line")
sale_order_line.write({'product_uom_qty': 20})
self.assertEqual(sale_order_line.product_uom_qty, sale_order_line.task_id.planned_hours, "The planned hours should have changed when updating the ordered quantity of the native SO line")
self.sale_order.action_cancel()
sale_order_line.write({'product_uom_qty': 30})
self.assertEqual(sale_order_line.product_uom_qty, sale_order_line.task_id.planned_hours, "The planned hours should have changed when updating the ordered quantity, even after SO cancellation")
self.sale_order.action_done()
with self.assertRaises(UserError):
sale_order_line.write({'product_uom_qty': 20})
def test_copy_billable_project_and_task(self):
sale_order_line = self.env['sale.order.line'].create({
'order_id': self.sale_order.id,
'name': self.product_delivery_timesheet3.name,
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 5,
'product_uom': self.product_delivery_timesheet3.uom_id.id,
'price_unit': self.product_delivery_timesheet3.list_price
})
self.sale_order.action_confirm()
task = self.env['project.task'].search([('sale_line_id', '=', sale_order_line.id)])
project = sale_order_line.project_id
# copy the project
project_copy = project.copy()
self.assertFalse(project_copy.sale_line_id, "Duplicating project should erase its Sale line")
self.assertFalse(project_copy.sale_order_id, "Duplicating project should erase its Sale order")
self.assertEqual(project_copy.billable_type, 'no', "Duplicating project should reset its billable type to none billable")
self.assertEqual(len(project.tasks), len(project_copy.tasks), "Copied project must have the same number of tasks")
self.assertFalse(project_copy.tasks.mapped('sale_line_id'), "The tasks of the duplicated project should not have a Sale Line set.")
# copy the task
task_copy = task.copy()
self.assertEqual(task_copy.sale_line_id, task.sale_line_id, "Duplicating task should keep its Sale line")