[REF] sale_timesheet: test suite improvements

Tests of sale_timesheet were using many demo data. This
is a practice we want to avoid. A common class setting up
test data (product service, partner, ...) is created to
help writting good test cases.

All tests has been adapted, moved, and some were added
to cover the new flow (new product configuration).
This commit is contained in:
Jérome Maes
2017-09-13 08:41:31 +02:00
parent 38b4a95940
commit 3890f8d694
5 changed files with 560 additions and 114 deletions
+1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import common
from . import test_sale_timesheet
from . import test_sale_service
from . import test_timesheet_revenue
+265
View File
@@ -0,0 +1,265 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import common
class CommonTest(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(CommonTest, cls).setUpClass()
# Create accounts
cls.account_debit = cls.env['account.account'].create({
'code': 'X1012',
'name': 'Debtors - (test)',
'reconcile': True,
'user_type_id': cls.env.ref('account.data_account_type_receivable').id,
})
cls.account_credit = cls.env['account.account'].create({
'code': 'X1111',
'name': 'Creditors - (test)',
'reconcile': True,
'user_type_id': cls.env.ref('account.data_account_type_payable').id,
})
cls.account_sale = cls.env['account.account'].create({
'code': 'X2020',
'name': 'Product Sales - (test)',
'reconcile': True,
'user_type_id': cls.env.ref('account.data_account_type_revenue').id,
})
# Create project
cls.project_global = cls.env['project.project'].create({
'name': 'Project for selling timesheets',
'allow_timesheets': True,
})
# Create service products
# -- ordered quantities (ordered, timesheet)
cls.product_order_timesheet1 = cls.env['product.product'].create({
'name': "Service Ordered, create no task",
'standard_price': 11,
'list_price': 13,
'type': 'service',
'invoice_policy': 'order',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-ORDERED1',
'service_type': 'timesheet',
'service_tracking': 'no',
'project_id': False,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_order_timesheet2 = cls.env['product.product'].create({
'name': "Service Ordered, create task in global project",
'standard_price': 30,
'list_price': 90,
'type': 'service',
'invoice_policy': 'order',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-ORDERED2',
'service_type': 'timesheet',
'service_tracking': 'task_global_project',
'project_id': cls.project_global.id,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_order_timesheet3 = cls.env['product.product'].create({
'name': "Service Ordered, create task in new project",
'standard_price': 10,
'list_price': 20,
'type': 'service',
'invoice_policy': 'order',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-ORDERED3',
'service_type': 'timesheet',
'service_tracking': 'task_new_project',
'project_id': False, # will create a project
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_order_timesheet4 = cls.env['product.product'].create({
'name': "Service Ordered, create project only",
'standard_price': 15,
'list_price': 30,
'type': 'service',
'invoice_policy': 'order',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-ORDERED4',
'service_type': 'timesheet',
'service_tracking': 'project_only',
'project_id': False,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
# -- timesheet on tasks (delivered, timesheet)
cls.product_delivery_timesheet1 = cls.env['product.product'].create({
'name': "Service delivered, create no task",
'standard_price': 11,
'list_price': 13,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI1',
'service_type': 'timesheet',
'service_tracking': 'no',
'project_id': False,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_delivery_timesheet2 = cls.env['product.product'].create({
'name': "Service delivered, create task in global project",
'standard_price': 30,
'list_price': 90,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI2',
'service_type': 'timesheet',
'service_tracking': 'task_global_project',
'project_id': cls.project_global.id,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_delivery_timesheet3 = cls.env['product.product'].create({
'name': "Service delivered, create task in new project",
'standard_price': 10,
'list_price': 20,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI3',
'service_type': 'timesheet',
'service_tracking': 'task_new_project',
'project_id': False, # will create a project
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_delivery_timesheet4 = cls.env['product.product'].create({
'name': "Service delivered, create project only",
'standard_price': 15,
'list_price': 30,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI4',
'service_type': 'timesheet',
'service_tracking': 'project_only',
'project_id': False,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
# -- milestons (delivered, manual)
cls.product_delivery_manual1 = cls.env['product.product'].create({
'name': "Service delivered, create no task",
'standard_price': 11,
'list_price': 13,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI1',
'service_type': 'manual',
'service_tracking': 'no',
'project_id': False,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_delivery_manual2 = cls.env['product.product'].create({
'name': "Service delivered, create task in global project",
'standard_price': 30,
'list_price': 90,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI2',
'service_type': 'manual',
'service_tracking': 'task_global_project',
'project_id': cls.project_global.id,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_delivery_manual3 = cls.env['product.product'].create({
'name': "Service delivered, create task in new project",
'standard_price': 10,
'list_price': 20,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI3',
'service_type': 'manual',
'service_tracking': 'task_new_project',
'project_id': False, # will create a project
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
cls.product_delivery_manual4 = cls.env['product.product'].create({
'name': "Service delivered, create project only",
'standard_price': 15,
'list_price': 30,
'type': 'service',
'invoice_policy': 'delivery',
'uom_id': cls.env.ref('product.product_uom_hour').id,
'uom_po_id': cls.env.ref('product.product_uom_hour').id,
'default_code': 'SERV-DELI4',
'service_type': 'manual',
'service_tracking': 'project_only',
'project_id': False,
'taxes_id': False,
'property_account_income_id': cls.account_sale.id,
})
# Create pricelists
cls.pricelist_usd = cls.env['product.pricelist'].create({
'name': 'USD pricelist',
'active': True,
'currency_id': cls.env.ref('base.USD').id,
'company_id': cls.env.user.company_id.id,
})
cls.pricelist_eur = cls.env['product.pricelist'].create({
'name': 'EUR pricelist',
'active': True,
'currency_id': cls.env.ref('base.EUR').id,
'company_id': cls.env.user.company_id.id,
})
# Create partners
cls.partner_usd = cls.env['res.partner'].create({
'name': 'Cool Partner in USD',
'email': 'partner.usd@test.com',
'property_product_pricelist': cls.pricelist_usd.id,
'property_account_payable_id': cls.account_credit.id,
'property_account_receivable_id': cls.account_debit.id,
})
cls.partner_eur = cls.env['res.partner'].create({
'name': 'Cool partner in EUR',
'email': 'partner.eur@test.com',
'property_product_pricelist': cls.pricelist_eur.id,
'property_account_payable_id': cls.account_credit.id,
'property_account_receivable_id': cls.account_debit.id,
})
# Create employees
cls.employee_user = cls.env['hr.employee'].create({
'name': 'Employee User',
'timesheet_cost': 15,
})
cls.employee_manager = cls.env['hr.employee'].create({
'name': 'Employee Manager',
'timesheet_cost': 45,
})
@@ -1,43 +1,51 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.sale.tests.test_sale_common import TestSale
from odoo.addons.sale_timesheet.tests.common import CommonTest
class TestSaleService(TestSale):
class TestSaleService(CommonTest):
""" This test suite provide checks for miscellaneous small things. """
def test_sale_service(self):
""" Test task creation when confirming a so with the corresponding product """
prod_task = self.env.ref('product.product_product_1')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': prod_task.name, 'product_id': prod_task.id, 'product_uom_qty': 50, 'product_uom': prod_task.uom_id.id, 'price_unit': prod_task.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
""" Test task creation when confirming a sale_order with the corresponding product """
sale_order_vals = {
'partner_id': self.partner_usd.id,
'partner_invoice_id': self.partner_usd.id,
'partner_shipping_id': self.partner_usd.id,
'order_line': [(0, 0, {
'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
}),
],
'pricelist_id': self.pricelist_usd.id,
}
so = self.env['sale.order'].create(so_vals)
so.action_confirm()
self.assertEqual(so.invoice_status, 'no', 'Sale Service: there should be nothing to invoice after validation')
sale_order = self.env['sale.order'].create(sale_order_vals)
sale_order.action_confirm()
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Service: there should be nothing to invoice after validation')
# check task creation
project = self.env.ref('sale_timesheet.project_GAP')
task = project.task_ids.filtered(lambda t: t.name == '%s:%s' % (so.name, prod_task.name))
project = self.project_global
task = project.task_ids.filtered(lambda t: t.name == '%s:%s' % (sale_order.name, self.product_delivery_timesheet2.name))
self.assertTrue(task, 'Sale Service: task is not created')
self.assertEqual(task.partner_id, so.partner_id, 'Sale Service: customer should be the same on task and on SO')
self.assertEqual(task.partner_id, sale_order.partner_id, 'Sale Service: customer should be the same on task and on SO')
# register timesheet on task
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': project.id,
'task_id': task.id,
'unit_amount': 50,
'user_id': self.manager.id,
'employee_id': self.employee_manager.id,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Service: there should be something to invoice after registering timesheets')
so.action_invoice_create()
line = so.order_line
self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Service: there should be sale_ordermething to invoice after registering timesheets')
sale_order.action_invoice_create()
line = sale_order.order_line
self.assertTrue(line.product_uom_qty == line.qty_delivered == line.qty_invoiced, 'Sale Service: line should be invoiced completely')
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Service: SO should be invoiced')
self.assertEqual(so.tasks_count, 1, "A task should have been created on SO confirmation.")
self.assertEqual(sale_order.invoice_status, 'invoiced', 'Sale Service: SO should be invoiced')
self.assertEqual(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({
@@ -60,7 +68,49 @@ class TestSaleService(TestSale):
'product_uom_qty': 10,
'product_uom': product_service_task.uom_id.id,
'price_unit': product_service_task.list_price,
'order_id': so.id,
'order_id': sale_order.id,
})
self.assertEqual(so.tasks_count, 2, "Adding a new service line on a confirmer SO should create a new task.")
self.assertEqual(sale_order.tasks_count, 2, "Adding a new service line on a confirmer SO should create a new task.")
def test_timesheet_uom(self):
""" Test timesheet invoicing and uom conversion """
# create SO and confirm it
uom_days = self.env.ref('product.product_uom_day')
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_usd.id,
'partner_invoice_id': self.partner_usd.id,
'partner_shipping_id': self.partner_usd.id,
'order_line': [
(0, 0, {
'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
})
],
'pricelist_id': self.pricelist_usd.id,
})
sale_order.action_confirm()
task = self.env['project.task'].search([('sale_line_id', '=', sale_order.order_line.id)])
# let's log some timesheets
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': sale_order.project_project_id.id,
'task_id': task.id,
'unit_amount': 16,
'employee_id': self.employee_manager.id,
})
self.assertEqual(sale_order.order_line.qty_delivered, 2, 'Sale: uom conversion of timesheets is wrong')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': sale_order.project_project_id.id,
'task_id': task.id,
'unit_amount': 24,
'employee_id': self.employee_user.id,
})
sale_order.action_invoice_create()
self.assertEqual(sale_order.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
@@ -1,140 +1,271 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tools import float_is_zero
from odoo.tools import float_is_zero, float_compare
from odoo.addons.sale.tests.test_sale_common import TestSale
from odoo.addons.sale_timesheet.tests.common import CommonTest
class TestSaleTimesheet(TestSale):
def setUp(self):
super(TestSaleTimesheet, self).setUp()
# link hr employee to res.users of sale tests
self.employee_user = self.env['hr.employee'].create({
'name': 'Employee User',
'user_id': self.user.id,
})
self.employee_manager = self.env['hr.employee'].create({
'name': 'Employee Manager',
'user_id': self.manager.id,
})
class TestSaleTimesheet(CommonTest):
""" This test suite provide tests for the 3 main flows of selling services:
- Selling services based on ordered quantities
- Selling timesheet based on delivered quantities
- Selling milestones, based on manual delivered quantities
For that, we check the task/project created, the invoiced amounts, the delivered
quantities changes, ...
"""
def test_timesheet_order(self):
""" Test timesheet invoicing with 'invoice on order' timetracked products """
# intial so
prod_ts = self.env.ref('product.service_order_01')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': prod_ts.name, 'product_id': prod_ts.id, 'product_uom_qty': 50, 'product_uom': prod_ts.uom_id.id, 'price_unit': prod_ts.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
so = self.env['sale.order'].create(so_vals)
so.action_confirm()
so.action_invoice_create()
""" Test timesheet invoicing with 'invoice on order' timetracked products
1. create SO with 2 ordered product and confirm
2. create invoice
3. log timesheet
4. add new SO line (ordered service)
5. create new invoice
"""
# create SO and confirm it
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_usd.id,
'partner_invoice_id': self.partner_usd.id,
'partner_shipping_id': self.partner_usd.id,
'pricelist_id': self.pricelist_usd.id,
})
so_line_ordered_project_only = 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': sale_order.id,
})
so_line_ordered_global_project = self.env['sale.order.line'].create({
'name': self.product_order_timesheet2.name,
'product_id': self.product_order_timesheet2.id,
'product_uom_qty': 50,
'product_uom': self.product_order_timesheet2.uom_id.id,
'price_unit': self.product_order_timesheet2.list_price,
'order_id': sale_order.id,
})
so_line_ordered_project_only.product_id_change()
so_line_ordered_global_project.product_id_change()
sale_order.action_confirm()
task_serv2 = self.env['project.task'].search([('sale_line_id', '=', so_line_ordered_global_project.id)])
# let's log some timesheets
self.assertEqual(sale_order.tasks_count, 1, "One task should have been created on SO confirmation")
self.assertTrue(sale_order.project_project_id, "A project should have been created by the SO, when confirmed.")
# create invoice
sale_order.action_invoice_create()
# let's log some timesheets (on the project created by so_line_ordered_project_only)
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'project_id': sale_order.project_project_id.id,
'task_id': task_serv2.id,
'unit_amount': 10.5,
'employee_id': self.employee_user.id,
})
self.assertEqual(so.order_line.qty_delivered, 10.5, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.assertEqual(so_line_ordered_global_project.qty_delivered, 10.5, 'Timesheet directly on project does not increase delivered quantity on so line')
self.assertEqual(sale_order.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'project_id': sale_order.project_project_id.id,
'task_id': task_serv2.id,
'unit_amount': 39.5,
'employee_id': self.employee_user.id,
})
self.assertEqual(so.order_line.qty_delivered, 50, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.assertEqual(so_line_ordered_global_project.qty_delivered, 50, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(sale_order.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'project_id': sale_order.project_project_id.id,
'unit_amount': 10,
'employee_id': self.employee_user.id,
})
self.assertEqual(so.order_line.qty_delivered, 60, 'Sale Timesheet: timesheet does not increase delivered quantity on so line')
self.assertEqual(so.invoice_status, 'upselling', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
self.assertEqual(so_line_ordered_project_only.qty_delivered, 0.0, 'Timesheet directly on project does not increase delivered quantity on so line')
# log timesheet on task in global project (higher than the initial ordrered qty)
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': sale_order.project_project_id.id,
'task_id': task_serv2.id,
'unit_amount': 5,
'employee_id': self.employee_user.id,
})
self.assertEqual(sale_order.invoice_status, 'upselling', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so')
# add so line with produdct "create task in new project". (project will be the one from SO)
so_line_ordered_task_new_project = self.env['sale.order.line'].create({
'name': self.product_order_timesheet3.name,
'product_id': self.product_order_timesheet3.id,
'product_uom_qty': 3,
'product_uom': self.product_order_timesheet3.uom_id.id,
'price_unit': self.product_order_timesheet3.list_price,
'order_id': sale_order.id,
})
task_serv3 = self.env['project.task'].search([('sale_line_id', '=', so_line_ordered_task_new_project.id)])
self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Timesheet: Adding a new service line (so line) should put the SO in "to invocie" state.')
self.assertEqual(sale_order.tasks_count, 2, "Two tasks (1 per SO line) should have been created on SO confirmation")
# create invoice
invoice_id = sale_order.action_invoice_create()
invoice = self.env['account.invoice'].browse(invoice_id)
self.assertEqual(len(sale_order.invoice_ids), 2, "A second invoice should have been created from the SO")
self.assertTrue(float_is_zero(invoice.amount_total - so_line_ordered_task_new_project.price_unit * 3, precision_digits=2), 'Sale: invoice generation on timesheets product is wrong')
self.assertEqual(sale_order.project_project_id, task_serv3.project_id, "When creating task in new project, the task should be in SO project (if already exists), otherwise it created one.")
def test_timesheet_delivery(self):
""" Test timesheet invoicing with 'invoice on delivery' timetracked products """
inv_obj = self.env['account.invoice']
# intial so
prod_ts = self.env.ref('product.product_product_2')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': prod_ts.name, 'product_id': prod_ts.id, 'product_uom_qty': 50, 'product_uom': prod_ts.uom_id.id, 'price_unit': prod_ts.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
so = self.env['sale.order'].create(so_vals)
so.action_confirm()
self.assertEqual(so.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation')
""" Test timesheet invoicing with 'invoice on delivery' timetracked products
1. Create SO and confirm it
2. log timesheet
3. create invoice
4. log other timesheet
5. create a second invoice
6. add new SO line (delivered service)
"""
# create SO and confirm it
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_usd.id,
'partner_invoice_id': self.partner_usd.id,
'partner_shipping_id': self.partner_usd.id,
'pricelist_id': self.pricelist_usd.id,
})
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': 50,
'product_uom': self.product_delivery_timesheet2.uom_id.id,
'price_unit': self.product_delivery_timesheet2.list_price,
'order_id': sale_order.id,
})
so_line_deliver_task_project = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet3.name,
'product_id': self.product_delivery_timesheet3.id,
'product_uom_qty': 20,
'product_uom': self.product_delivery_timesheet3.uom_id.id,
'price_unit': self.product_delivery_timesheet3.list_price,
'order_id': sale_order.id,
})
so_line_deliver_global_project.product_id_change()
so_line_deliver_task_project.product_id_change()
# confirm SO
sale_order.action_confirm()
task_serv2 = self.env['project.task'].search([('sale_line_id', '=', so_line_deliver_global_project.id)])
task_serv3 = self.env['project.task'].search([('sale_line_id', '=', so_line_deliver_task_project.id)])
self.assertEqual(task_serv2.project_id, self.project_global, "Sale Timesheet: task should be created in global project")
self.assertTrue(task_serv2, "Sale Timesheet: on SO confirmation, a task should have been created in global project")
self.assertTrue(task_serv3, "Sale Timesheet: on SO confirmation, a task should have been created in a new project")
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation')
self.assertEqual(sale_order.analytic_account_id, task_serv3.project_id.analytic_account_id, "SO should have create a project")
self.assertEqual(sale_order.tasks_count, 2, "Two tasks (1 per SO line) should have been created on SO confirmation")
# let's log some timesheets
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'project_id': task_serv2.project_id.id, # global project
'task_id': task_serv2.id,
'unit_amount': 10.5,
'employee_id': self.employee_manager.id,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so in "to invoice" status when logged')
inv_id = so.action_invoice_create()
inv = inv_obj.browse(inv_id)
self.assertTrue(float_is_zero(inv.amount_total - so.order_line.price_unit * 10.5, precision_digits=2), 'Sale: invoice generation on timesheets product is wrong')
self.assertEqual(so_line_deliver_global_project.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged')
self.assertEqual(so_line_deliver_task_project.invoice_status, 'no', 'Sale Timesheet: so line invoice status should not change when no timesheet linked to the line')
self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so in "to invoice" status when logged')
# invoice SO
invoice_id = sale_order.action_invoice_create()
invoice = self.env['account.invoice'].browse(invoice_id)
self.assertTrue(float_is_zero(invoice.amount_total - so_line_deliver_global_project.price_unit * 10.5, precision_digits=2), 'Sale: invoice generation on timesheets product is wrong')
# log some timesheets again
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'project_id': task_serv2.project_id.id, # global project
'task_id': task_serv2.id,
'unit_amount': 39.5,
'employee_id': self.employee_user.id,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
so.action_invoice_create()
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on delivery" timesheets should be invoiced completely by now')
self.assertEqual(so_line_deliver_global_project.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged')
self.assertEqual(so_line_deliver_task_project.invoice_status, 'no', 'Sale Timesheet: so line invoice status should not change when no timesheet linked to the line')
self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
# create a second invoice
sale_order.action_invoice_create()
self.assertEqual(so_line_deliver_global_project.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged')
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" timesheets should be invoiced completely by now')
# add a line on SO
so_line_deliver_only_project = self.env['sale.order.line'].create({
'name': self.product_delivery_timesheet4.name,
'product_id': self.product_delivery_timesheet4.id,
'product_uom_qty': 5,
'product_uom': self.product_delivery_timesheet4.uom_id.id,
'price_unit': self.product_delivery_timesheet4.list_price,
'order_id': sale_order.id,
})
self.assertEqual(sale_order.project_project_id, task_serv3.project_id, "SO should not have create a second project, since so line 3 already create one project for the SO")
# let's log some timesheets on the project
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'unit_amount': 10,
'project_id': sale_order.project_project_id.id, # global project
'unit_amount': 7,
'employee_id': self.employee_user.id,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Timesheet: supplementary timesheets do not change the status of the SO')
# import pdb;pdb.set_trace()
self.assertTrue(float_is_zero(so_line_deliver_only_project.qty_delivered, precision_digits=2), "Timesheeting on project should not incremented the delivered quantity on the SO line")
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" timesheets should be invoiced completely by now')
def test_timesheet_uom(self):
""" Test timesheet invoicing and uom conversion """
# intial so
prod_ts = self.env.ref('product.product_product_2')
uom_days = self.env.ref('product.product_uom_day')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': prod_ts.name, 'product_id': prod_ts.id, 'product_uom_qty': 5, 'product_uom': uom_days.id, 'price_unit': prod_ts.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
so = self.env['sale.order'].create(so_vals)
so.action_confirm()
# let's log some timesheets
def test_timesheet_manual(self):
""" Test timesheet invoicing with 'invoice on delivery' timetracked products
"""
# create SO and confirm it
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_usd.id,
'partner_invoice_id': self.partner_usd.id,
'partner_shipping_id': self.partner_usd.id,
'pricelist_id': self.pricelist_usd.id,
})
so_line_manual_global_project = self.env['sale.order.line'].create({
'name': self.product_delivery_manual2.name,
'product_id': self.product_delivery_manual2.id,
'product_uom_qty': 50,
'product_uom': self.product_delivery_manual2.uom_id.id,
'price_unit': self.product_delivery_manual2.list_price,
'order_id': sale_order.id,
})
so_line_manual_only_project = self.env['sale.order.line'].create({
'name': self.product_delivery_manual4.name,
'product_id': self.product_delivery_manual4.id,
'product_uom_qty': 20,
'product_uom': self.product_delivery_manual4.uom_id.id,
'price_unit': self.product_delivery_manual4.list_price,
'order_id': sale_order.id,
})
# confirm SO
sale_order.action_confirm()
self.assertTrue(sale_order.project_project_id, "Sales Order should have create a project")
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: manually product should not need to be invoiced on so confirmation')
# let's log some timesheets (on task and project)
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'unit_amount': 16,
'project_id': self.project_global.id, # global project
'task_id': so_line_manual_global_project.task_id.id,
'unit_amount': 6,
'employee_id': self.employee_manager.id,
})
self.assertEqual(so.order_line.qty_delivered, 2, 'Sale: uom conversion of timesheets is wrong')
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': so.project_project_id.id,
'unit_amount': 24,
'employee_id': self.employee_user.id,
'project_id': sale_order.project_project_id.id, # global project
'unit_amount': 3,
'employee_id': self.employee_manager.id,
})
so.action_invoice_create()
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
self.assertEqual(so_line_manual_global_project.qty_to_invoice, 0.0, "Manual service should not be affected by timesheet on their created task.")
self.assertEqual(so_line_manual_only_project.qty_to_invoice, 0.0, "Manual service should not be affected by timesheet on their created project.")
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation')
@@ -35,7 +35,7 @@ class TestSaleTimesheet(TestSale):
'default_code': 'SERV-DELI',
'service_type': 'timesheet',
'service_tracking': 'task_global_project',
'project_id': self.project.id
'project_id': self.project.id,
})
self.product_order = self.env['product.product'].create({
@@ -49,7 +49,7 @@ class TestSaleTimesheet(TestSale):
'default_code': 'SERV-ORDER',
'service_type': 'timesheet',
'service_tracking': 'task_global_project',
'project_id': self.project.id
'project_id': self.project.id,
})
# pricelists
@@ -65,7 +65,6 @@ class TestSaleTimesheet(TestSale):
'currency_id': self.env.ref('base.EUR').id,
'company_id': self.env.user.company_id.id,
})
# partners
self.partner_usd = self.env['res.partner'].create({
'name': 'Cool Partner in USD',