mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-28 05:12:04 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,14 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo import Command
|
||||
from odoo.fields import Date
|
||||
from odoo.tools import float_is_zero
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.addons.hr_timesheet.tests.test_timesheet import TestCommonTimesheet
|
||||
from odoo.addons.sale_timesheet.tests.common import TestCommonSaleTimesheet
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.tests import Form, tagged, new_test_user
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestSaleTimesheet(TestCommonSaleTimesheet):
|
||||
|
|
@ -20,6 +19,45 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
quantities changes, ...
|
||||
"""
|
||||
|
||||
def test_compute_commercial_partner(self):
|
||||
"""Ensure user without project access can compute commercial partner without AccessError.
|
||||
Steps:
|
||||
1. Create a commercial partner and a sub-partner.
|
||||
2. Create a project assigned to the sub-partner and a task under that project. Link both to a timesheet.
|
||||
3. Create a restricted user with no access to the Project module but with Timesheet Administrator access.
|
||||
4. Compute the commercial partner as the restricted user and verify it's derived from the project partner.
|
||||
5. Set the task partner, recompute, and verify the commercial partner updates accordingly.
|
||||
"""
|
||||
commercial_partner = self.env['res.partner'].create({'name': 'Commercial Partner', 'is_company': True})
|
||||
sub_partner = self.env['res.partner'].create({'name': 'Sub Partner', 'parent_id': commercial_partner.id})
|
||||
project = self.env['project.project'].create({
|
||||
'name': 'Test Project',
|
||||
'partner_id': sub_partner.id,
|
||||
'privacy_visibility': 'followers',
|
||||
'task_ids': [Command.create({'name': 'Test Task'})]
|
||||
})
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Test Timesheet',
|
||||
'project_id': project.id,
|
||||
'task_id': project.task_ids[0].id,
|
||||
'employee_id': self.employee_user.id,
|
||||
})
|
||||
timesheet_manager_no_project_user = new_test_user(self.env, login='no_project_user', groups='hr_timesheet.group_timesheet_manager')
|
||||
|
||||
timesheet.with_user(timesheet_manager_no_project_user)._compute_commercial_partner()
|
||||
self.assertEqual(
|
||||
timesheet.commercial_partner_id,
|
||||
commercial_partner,
|
||||
"The commercial partner should match the partner linked to the project."
|
||||
)
|
||||
project.task_ids[0].partner_id = sub_partner.id
|
||||
timesheet.with_user(timesheet_manager_no_project_user)._compute_commercial_partner()
|
||||
self.assertEqual(
|
||||
timesheet.commercial_partner_id,
|
||||
commercial_partner,
|
||||
"The commercial partner should match the partner linked to the task."
|
||||
)
|
||||
|
||||
def test_timesheet_order(self):
|
||||
""" Test timesheet invoicing with 'invoice on order' timetracked products
|
||||
1. create SO with 2 ordered product and confirm
|
||||
|
|
@ -33,7 +71,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
so_line_ordered_project_only = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_order_timesheet4.id,
|
||||
|
|
@ -51,7 +88,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
|
||||
self.assertEqual(sale_order.tasks_count, 1, "One task should have been created on SO confirmation")
|
||||
self.assertEqual(len(sale_order.project_ids), 2, "One project should have been created by the SO, when confirmed + the one from SO line 2 'task in global project'")
|
||||
self.assertEqual(sale_order.analytic_account_id, project_serv1.analytic_account_id, "The created project should be linked to the analytic account of the SO")
|
||||
self.assertEqual(sale_order.project_account_id, project_serv1.account_id, "The created project should be linked to the analytic account of the SO")
|
||||
|
||||
# create invoice
|
||||
invoice1 = sale_order._create_invoices()[0]
|
||||
|
|
@ -160,7 +197,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
so_line_deliver_global_project = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
|
|
@ -183,10 +219,10 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
self.assertTrue(task_serv1, "Sale Timesheet: on SO confirmation, a task should have been created in global project")
|
||||
self.assertTrue(task_serv2, "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_serv2.project_id.analytic_account_id, "SO should have create a project")
|
||||
self.assertEqual(sale_order.project_account_id, task_serv2.project_id.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")
|
||||
self.assertEqual(len(sale_order.project_ids), 2, "One project should have been created by the SO, when confirmed + the one from SO line 1 'task in global project'")
|
||||
self.assertEqual(sale_order.analytic_account_id, project_serv2.analytic_account_id, "The created project should be linked to the analytic account of the SO")
|
||||
self.assertEqual(sale_order.project_account_id, project_serv2.account_id, "The created project should be linked to the analytic account of the SO")
|
||||
|
||||
# let's log some timesheets
|
||||
timesheet1 = self.env['account.analytic.line'].create({
|
||||
|
|
@ -283,7 +319,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
so_line_manual_global_project = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_manual2.id,
|
||||
|
|
@ -303,7 +338,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
|
||||
project_serv2 = so_line_manual_only_project.project_id
|
||||
self.assertTrue(project_serv2, "A second project is created when selling 'project only' after SO confirmation.")
|
||||
self.assertEqual(sale_order.analytic_account_id, project_serv2.analytic_account_id, "The created project should be linked to the analytic account of the SO")
|
||||
self.assertEqual(sale_order.project_account_id, project_serv2.account_id, "The created project should be linked to the analytic account of the SO")
|
||||
|
||||
# let's log some timesheets (on task and project)
|
||||
timesheet1 = self.env['account.analytic.line'].create({
|
||||
|
|
@ -364,7 +399,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
# Section Line
|
||||
so_line_ordered_project_only = self.env['sale.order.line'].create({
|
||||
|
|
@ -382,6 +416,11 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'product_uom_qty': 20,
|
||||
'order_id': sale_order.id,
|
||||
})
|
||||
so_line_deliver_timesheet = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_timesheet1.id,
|
||||
'product_uom_qty': 5,
|
||||
'order_id': sale_order.id,
|
||||
})
|
||||
|
||||
# confirm SO
|
||||
sale_order.action_confirm()
|
||||
|
|
@ -423,6 +462,22 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'unit_amount': 30,
|
||||
'employee_id': self.employee_manager.id
|
||||
})
|
||||
|
||||
with self.assertRaises(AccessError, msg="The user should not have access to the SOL"):
|
||||
so_line_deliver_timesheet.with_user(self.user_employee_without_sales_access).read(['name'])
|
||||
|
||||
# invalidate cache to make sure the SOL set on the timesheet is not in the cache since the user
|
||||
# should not be able to access on the SOL.
|
||||
self.env['sale.order.line'].invalidate_model()
|
||||
timesheet5 = self.env['account.analytic.line'].with_user(self.user_employee_without_sales_access).create({
|
||||
'name': 'Test Line 5',
|
||||
'project_id': task_serv2.project_id.id,
|
||||
'task_id': task_serv2.id,
|
||||
'unit_amount': 10,
|
||||
'employee_id': self.employee_without_sales_access.id,
|
||||
'so_line': so_line_deliver_timesheet.id,
|
||||
})
|
||||
|
||||
self.assertEqual(so_line_deliver_global_project.invoice_status, 'to invoice')
|
||||
self.assertEqual(so_line_deliver_task_project.invoice_status, 'to invoice')
|
||||
self.assertEqual(sale_order.invoice_status, 'to invoice')
|
||||
|
|
@ -501,7 +556,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
task = Task.with_context(default_project_id=self.project_template.id).create({
|
||||
'name': 'first task',
|
||||
'partner_id': self.partner_b.id,
|
||||
'planned_hours': 10,
|
||||
'allocated_hours': 10,
|
||||
'sale_line_id': self.so.order_line[0].id
|
||||
})
|
||||
|
||||
|
|
@ -518,7 +573,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
self.assertEqual(timesheet_count2, 1, "One timesheet in project_template")
|
||||
self.assertEqual(len(task.timesheet_ids), 1, "The timesheet should be linked to task")
|
||||
|
||||
# change project of task, as the timesheet is not yet invoiced, the timesheet will change his project
|
||||
# change project of task, non-validated timesheets will follow the project of task
|
||||
task.write({
|
||||
'project_id': self.project_global.id
|
||||
})
|
||||
|
|
@ -552,14 +607,14 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
|
||||
self.assertEqual(Timesheet.search_count([('project_id', '=', self.project_global.id)]), 2, "2 timesheets in project_global")
|
||||
|
||||
# change project of task, the timesheet not yet invoiced will change its project. The timesheet already invoiced will not change his project.
|
||||
# change project of task, only the timesheet not billed gets its project changed
|
||||
task.write({
|
||||
'project_id': self.project_template.id
|
||||
})
|
||||
|
||||
timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_global.id)])
|
||||
timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_template.id)])
|
||||
self.assertEqual(timesheet_count1, 1, "Still one timesheet in project_global")
|
||||
self.assertEqual(timesheet_count1, 1, "One timesheet in project_global")
|
||||
self.assertEqual(timesheet_count2, 1, "One timesheet in project_template")
|
||||
self.assertEqual(len(task.timesheet_ids), 2, "The 2 timesheets still should be linked to task")
|
||||
|
||||
|
|
@ -568,13 +623,11 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
sale_order2 = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_b.id,
|
||||
'partner_invoice_id': self.partner_b.id,
|
||||
'partner_shipping_id': self.partner_b.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
so1_product_global_project_so_line = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
|
|
@ -632,7 +685,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
def test_timesheet_upsell(self):
|
||||
""" Test timesheet upselling and email """
|
||||
|
||||
sale_order = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
|
|
@ -643,7 +696,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order.id,
|
||||
'product_id': self.product_order_timesheet3.id,
|
||||
'product_uom': uom_days.id,
|
||||
'product_uom_id': uom_days.id,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
task = sale_order_line.task_id
|
||||
|
|
@ -670,7 +723,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
self.assertEqual(sale_order.invoice_status, 'upselling', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('subject', 'like', 'To-Do'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
|
@ -687,7 +740,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('subject', 'like', 'To-Do'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
|
@ -696,7 +749,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
def test_timesheet_upsell_copied_so(self):
|
||||
""" Test that copying a SO which had an upsell activity still create an upsell activity on the copy. """
|
||||
|
||||
sale_order = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
|
|
@ -707,7 +760,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order.id,
|
||||
'product_id': self.product_order_timesheet3.id,
|
||||
'product_uom': uom_days.id,
|
||||
'product_uom_id': uom_days.id,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
task = sale_order_line.task_id
|
||||
|
|
@ -734,7 +787,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
self.assertEqual(sale_order.invoice_status, 'upselling', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('subject', 'like', 'To-Do'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
|
@ -751,7 +804,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('subject', 'like', 'To-Do'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
|
@ -783,7 +836,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
self.assertEqual(sale_order.invoice_status, 'upselling', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so')
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('subject', 'like', 'To-Do'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
|
@ -800,7 +853,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
|
||||
message_sent = self.env['mail.message'].search([
|
||||
('id', '>', last_message_id),
|
||||
('subject', 'like', 'Upsell'),
|
||||
('subject', 'like', 'To-Do'),
|
||||
('model', '=', 'sale.order'),
|
||||
('res_id', '=', sale_order.id),
|
||||
])
|
||||
|
|
@ -811,13 +864,11 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
})
|
||||
so_line = 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,
|
||||
})
|
||||
|
|
@ -839,9 +890,37 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
move.with_context(check_move_validity=False).line_ids[0].unlink()
|
||||
self.assertFalse(analytic_line.timesheet_invoice_id, "The timesheet should have been unlinked from move")
|
||||
|
||||
def test_update_sol_price(self):
|
||||
""" This test ensure that when the price of a sol is updated, the project_profitability panel from the project linked to the SO of that sol is correctly updated too.
|
||||
1) create new SO
|
||||
2) add a sol with a service product with 'invoice on prepaid' and 'create project & task' setting.
|
||||
3) confirm SO and check the project_profitability panel
|
||||
4) update the price of the sol and check the project_profitability panel
|
||||
"""
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
product_price = self.product_order_timesheet3.list_price
|
||||
so_line = self.env['sale.order.line'].create({
|
||||
'name': self.product_order_timesheet3.name,
|
||||
'product_id': self.product_order_timesheet3.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': product_price,
|
||||
'order_id': sale_order.id,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
project = sale_order.project_ids[0]
|
||||
|
||||
items = project._get_profitability_items(with_action=False)
|
||||
self.assertEqual(items['revenues']['data'][0]['to_invoice'], product_price, "The quantity to_invoice should be equal to the price of the product")
|
||||
|
||||
so_line.price_unit = 2*product_price
|
||||
items = project._get_profitability_items(with_action=False)
|
||||
self.assertEqual(items['revenues']['data'][0]['to_invoice'], 2*product_price, "The quantity to_invoice should be equal to twice the price of the product")
|
||||
|
||||
def test_sale_order_with_multiple_project_templates(self):
|
||||
"""Test when creating multiple projects for one sale order every project has its own allocated hours"""
|
||||
sale_order = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
|
|
@ -860,7 +939,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'type': 'service',
|
||||
'invoice_policy': 'order',
|
||||
'uom_id': self.uom_hour.id,
|
||||
'uom_po_id': self.uom_hour.id,
|
||||
'default_code': 'c1',
|
||||
'service_tracking': 'task_in_project',
|
||||
'project_id': False, # will create a project,
|
||||
|
|
@ -872,7 +950,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'type': 'service',
|
||||
'invoice_policy': 'order',
|
||||
'uom_id': self.uom_hour.id,
|
||||
'uom_po_id': self.uom_hour.id,
|
||||
'default_code': 'c2',
|
||||
'service_tracking': 'task_in_project',
|
||||
'project_id': False, # will create a project,
|
||||
|
|
@ -886,14 +963,12 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
'name': product_1.name,
|
||||
'product_id': product_1.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': product_1.uom_id.id,
|
||||
'price_unit': product_1.list_price,
|
||||
}, {
|
||||
'order_id': sale_order.id,
|
||||
'name': product_2.name,
|
||||
'product_id': product_2.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': product_2.uom_id.id,
|
||||
'price_unit': product_2.list_price,
|
||||
},
|
||||
])
|
||||
|
|
@ -902,10 +977,349 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
|
|||
self.assertEqual(10, sale_order_line_template_1.project_id.allocated_hours)
|
||||
self.assertEqual(5, sale_order_line_template_2.project_id.allocated_hours)
|
||||
|
||||
def test_onchange_uom_service_product(self):
|
||||
uom_unit = self.env.ref('uom.product_uom_unit')
|
||||
uom_kg = self.env.ref('uom.product_uom_kgm')
|
||||
|
||||
class TestSaleTimesheetView(TestCommonTimesheet):
|
||||
def test_get_view_timesheet_encode_uom(self):
|
||||
""" Test the label of timesheet time spent fields according to the company encoding timesheet uom """
|
||||
self.assert_get_view_timesheet_encode_uom([
|
||||
('sale_timesheet.project_project_view_form', '//field[@name="display_cost"]', [None, 'Daily Cost']),
|
||||
# Create product (consumable that will be switch to service)
|
||||
product_1 = self.env['product.template'].create([
|
||||
{
|
||||
'name': "Consumable to convert to service 1",
|
||||
'standard_price': 10,
|
||||
},
|
||||
])
|
||||
product_2 = self.env['product.product'].create({
|
||||
'name': "Consumable to convert to service 2",
|
||||
'standard_price': 15,
|
||||
})
|
||||
|
||||
# Initial uom should be unit
|
||||
self.assertEqual([product_1.uom_id.id, product_2.uom_id.id], [uom_unit.id]*2)
|
||||
products = [product_1, product_2] #perform the tests for both product and variants
|
||||
for product in products:
|
||||
# 1. product.template form: [uom: unit] --> change to service --> [uom: hour]
|
||||
with Form(product, view="sale_timesheet.view_product_timesheet_form") as product_form:
|
||||
product_form.type = 'service'
|
||||
product_form.service_policy = 'delivered_timesheet'
|
||||
self.assertEqual(product_form.uom_id.id, self.uom_hour.id)
|
||||
|
||||
# 2. product.template form: [uom: kgm] --> change to service --> [uom: hour] --> change to consumable --> [uom: kgm]
|
||||
product.write({
|
||||
'type': 'consu',
|
||||
'uom_id': uom_kg.id,
|
||||
})
|
||||
with Form(product, view="sale_timesheet.view_product_timesheet_form") as product_form:
|
||||
product_form.type = 'service'
|
||||
product_form.service_policy = 'delivered_timesheet'
|
||||
self.assertEqual(product_form.uom_id.id, self.uom_hour.id)
|
||||
product_form.type = 'consu'
|
||||
self.assertEqual(product_form.uom_id.id, uom_kg.id)
|
||||
|
||||
def test_allocated_hours_copy(self):
|
||||
""" This test ensures that the generated project's allocated_hours field is copied from the project template when it is set."""
|
||||
project_template = self.env['project.project'].create({
|
||||
'name': 'Template',
|
||||
'allocated_hours': 65,
|
||||
})
|
||||
product = self.env['product.product'].create({
|
||||
'name': "Service with template",
|
||||
'standard_price': 10,
|
||||
'list_price': 20,
|
||||
'type': 'service',
|
||||
'invoice_policy': 'order',
|
||||
'uom_id': self.uom_hour.id,
|
||||
'default_code': 'c1',
|
||||
'service_tracking': 'task_in_project',
|
||||
'project_id': False, # will create a project,
|
||||
'project_template_id': project_template.id,
|
||||
})
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order.id,
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 10,
|
||||
'price_unit': product.list_price,
|
||||
})
|
||||
project = sale_order_line._timesheet_create_project()
|
||||
self.assertTrue(
|
||||
project.allocated_hours == project_template.allocated_hours != sale_order_line.product_uom_qty,
|
||||
"The project's allocated hours should have been copied from its template, rather than the sale order line",
|
||||
)
|
||||
|
||||
def test_non_consolidated_billing_service_timesheet(self):
|
||||
"""
|
||||
When consolidated_billing is set to False, an invoice is created for each sale order
|
||||
Makes sure it works with sales orders linked to timesheets
|
||||
"""
|
||||
|
||||
sale_orders = self.env['sale.order'].create([{
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
})],
|
||||
}, {
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
})],
|
||||
}])
|
||||
sale_orders.action_confirm()
|
||||
|
||||
self.env['account.analytic.line'].create([{
|
||||
'name': 'Timesheet',
|
||||
'task_id': task.id,
|
||||
'project_id': task.project_id.id,
|
||||
'unit_amount': 2,
|
||||
'employee_id': self.employee_user.id,
|
||||
} for task in sale_orders.tasks_ids])
|
||||
|
||||
advance_payment = self.env['sale.advance.payment.inv'].with_context(active_ids=sale_orders.ids).create({
|
||||
'consolidated_billing': False,
|
||||
})
|
||||
|
||||
invoices = advance_payment._create_invoices(sale_orders)
|
||||
|
||||
self.assertEqual(len(invoices), 2, "The number of invoices created should be equal to the number of sales orders.")
|
||||
|
||||
def test_timesheet_with_negative_time_spent(self):
|
||||
""" Check the billable type of a timesheet with negative time spent """
|
||||
sale_order = self.env['sale.order'].create([{
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
})],
|
||||
}])
|
||||
sale_order.action_confirm()
|
||||
task1 = sale_order.tasks_ids
|
||||
timesheet = self.env['account.analytic.line'].create([
|
||||
{
|
||||
'name': 'Timesheet',
|
||||
'task_id': task1.id,
|
||||
'project_id': task1.project_id.id,
|
||||
'unit_amount': -1,
|
||||
'employee_id': self.employee_user.id,
|
||||
},
|
||||
])
|
||||
self.assertEqual(timesheet.timesheet_invoice_type, 'billable_time')
|
||||
|
||||
def test_linked_timesheet_after_invoice_reversal(self):
|
||||
"""Test that uneditable timesheet entries aren't linked to a reversed invoice form"""
|
||||
|
||||
# Full refund credit note
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
})
|
||||
so_line = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
'product_uom_qty': 1,
|
||||
'order_id': sale_order.id,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
task = so_line.task_id
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Test Invoice Reversal',
|
||||
'project_id': task.project_id.id,
|
||||
'task_id': task.id,
|
||||
'unit_amount': 5,
|
||||
'employee_id': self.employee_user.id,
|
||||
})
|
||||
invoice = sale_order._create_invoices()[0]
|
||||
invoice.action_post()
|
||||
self.assertEqual(timesheet.timesheet_invoice_id, invoice, "Timesheet should be linked to the invoice")
|
||||
reversal_wizard = self.env['account.move.reversal'].with_context(
|
||||
active_model='account.move',
|
||||
active_ids=invoice.ids
|
||||
).create({
|
||||
'reason': 'full refund',
|
||||
'journal_id': invoice.journal_id.id,
|
||||
})
|
||||
reversal_wizard.modify_moves()
|
||||
self.assertFalse(timesheet.timesheet_invoice_id, "Timesheet should not be linked to the invoice after reversal")
|
||||
timesheet.write({'unit_amount': 7})
|
||||
self.assertEqual(timesheet.unit_amount, 7, "It Should be possible to edit timesheet after invoice reversal")
|
||||
|
||||
# Partial refund credit note
|
||||
sale_order2 = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
})
|
||||
so_line1 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_timesheet2.id,
|
||||
'product_uom_qty': 1,
|
||||
'order_id': sale_order2.id,
|
||||
})
|
||||
so_line2 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_delivery_timesheet3.id,
|
||||
'product_uom_qty': 1,
|
||||
'order_id': sale_order2.id,
|
||||
})
|
||||
sale_order2.action_confirm()
|
||||
task1 = so_line1.task_id
|
||||
task2 = so_line2.task_id
|
||||
timesheet1 = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet Task 1',
|
||||
'project_id': task1.project_id.id,
|
||||
'task_id': task1.id,
|
||||
'unit_amount': 5,
|
||||
'employee_id': self.employee_user.id,
|
||||
})
|
||||
timesheet2 = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet Task 2',
|
||||
'project_id': task2.project_id.id,
|
||||
'task_id': task2.id,
|
||||
'unit_amount': 5,
|
||||
'employee_id': self.employee_user.id,
|
||||
})
|
||||
invoice2 = sale_order2._create_invoices()[0]
|
||||
invoice2.action_post()
|
||||
self.assertEqual(timesheet1.timesheet_invoice_id, invoice2, "Timesheet1 should be linked to the invoice")
|
||||
self.assertEqual(timesheet2.timesheet_invoice_id, invoice2, "Timesheet2 should be linked to the invoice")
|
||||
|
||||
refund_wizard = self.env['account.move.reversal'].with_context(
|
||||
active_model='account.move',
|
||||
active_ids=invoice2.ids
|
||||
).create({
|
||||
'reason': 'partial refund',
|
||||
'journal_id': invoice2.journal_id.id,
|
||||
})
|
||||
refund_action = refund_wizard.refund_moves()
|
||||
credit_note = self.env['account.move'].browse(refund_action['res_id'])
|
||||
invoice_line_to_remove = credit_note.invoice_line_ids.filtered(
|
||||
lambda line: line.sale_line_ids.id == so_line2.id
|
||||
)
|
||||
invoice_line_to_remove.unlink()
|
||||
credit_note.action_post()
|
||||
self.assertFalse(timesheet1.timesheet_invoice_id, "Timesheet1 should be cleared after partial refund of its task")
|
||||
self.assertEqual(timesheet2.timesheet_invoice_id, invoice2, "Timesheet2 should still be linked to the original invoice")
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestSaleTimesheetAnalyticPlan(TestCommonSaleTimesheet):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.plan_b, cls.plan_c = cls.env['account.analytic.plan'].create([
|
||||
{'name': 'plan 2'},
|
||||
{'name': 'plan 3'},
|
||||
])
|
||||
|
||||
def test_timesheet_get_accounts_from_sol(self):
|
||||
project_analytic_plan, _other_plans = self.env['account.analytic.plan']._get_all_plans()
|
||||
other_analytic_plan2 = self.plan_b
|
||||
analytic_account1, analytic_account2 = self.env['account.analytic.account'].create([
|
||||
{
|
||||
'name': 'Analytic Account 1',
|
||||
'plan_id': project_analytic_plan.id,
|
||||
},
|
||||
{
|
||||
'name': 'Analytic Account 2',
|
||||
'plan_id': other_analytic_plan2.id,
|
||||
},
|
||||
])
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'name': 'SO Test',
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
so_line1 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_order_timesheet4.id,
|
||||
'product_uom_qty': 10,
|
||||
'order_id': sale_order.id,
|
||||
'analytic_distribution': {f'{analytic_account1.id}, {analytic_account2.id}': 100},
|
||||
})
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Test Line',
|
||||
'project_id': self.project_global.id,
|
||||
'unit_amount': 50,
|
||||
'employee_id': self.employee_manager.id,
|
||||
'so_line': so_line1.id,
|
||||
})
|
||||
self.assertEqual(timesheet.account_id, analytic_account1)
|
||||
self.assertEqual(timesheet[other_analytic_plan2._column_name()], analytic_account2)
|
||||
|
||||
# Create another analytic account and a new SOL to assign it to the timesheet
|
||||
other_analytic_plan3 = self.plan_c
|
||||
analytic_account3 = self.env['account.analytic.account'].create({
|
||||
'name': 'Analytic Account 3',
|
||||
'plan_id': other_analytic_plan3.id,
|
||||
})
|
||||
so_line2 = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_order_timesheet4.id,
|
||||
'product_uom_qty': 10,
|
||||
'order_id': sale_order.id,
|
||||
'analytic_distribution': {f'{analytic_account1.id}, {analytic_account3.id}': 100},
|
||||
})
|
||||
timesheet.so_line = so_line2
|
||||
self.assertEqual(timesheet.account_id, analytic_account1)
|
||||
self.assertFalse(timesheet[other_analytic_plan2._column_name()])
|
||||
self.assertEqual(timesheet[other_analytic_plan3._column_name()], analytic_account3)
|
||||
|
||||
def test_timesheet_get_accounts_from_sol_fallback_on_project(self):
|
||||
_project_analytic_plan, other_plans = self.env['account.analytic.plan']._get_all_plans()
|
||||
other_analytic_plan2 = self.plan_b
|
||||
analytic_account2 = self.env['account.analytic.account'].create({
|
||||
'name': 'Analytic Account 2',
|
||||
'plan_id': other_analytic_plan2.id,
|
||||
})
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'name': 'SO Test',
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
so_line = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_order_timesheet3.id,
|
||||
'order_id': sale_order.id,
|
||||
})
|
||||
sale_order.action_confirm()
|
||||
so_project = sale_order.project_id
|
||||
so_line.analytic_distribution = {str(analytic_account2.id): 100}
|
||||
timesheet = self.env['account.analytic.line'].create({
|
||||
'name': 'Timesheet',
|
||||
'project_id': so_project.id,
|
||||
'unit_amount': 1,
|
||||
'employee_id': self.employee_manager.id,
|
||||
'so_line': so_line.id,
|
||||
})
|
||||
self.assertEqual(
|
||||
timesheet._get_analytic_accounts(),
|
||||
so_project.account_id | analytic_account2,
|
||||
"The analytic accounts should be the account_id from the project and the accounts from the SOL's distribution",
|
||||
)
|
||||
|
||||
def test_mandatory_plan_timesheet_applicability_from_sol(self):
|
||||
plan_a = self.analytic_plan
|
||||
plan_b = self.plan_b
|
||||
analytic_account, _dummy = self.env['account.analytic.account'].create([{
|
||||
'name': 'account',
|
||||
'plan_id': plan.id,
|
||||
} for plan in (plan_a, plan_b)])
|
||||
self.env['account.analytic.applicability'].create({
|
||||
'business_domain': 'timesheet',
|
||||
'applicability': 'mandatory',
|
||||
'analytic_plan_id': plan_b.id,
|
||||
})
|
||||
sale_order = self.env['sale.order'].create({
|
||||
'name': 'SO Test',
|
||||
'partner_id': self.partner_a.id,
|
||||
})
|
||||
so_line = self.env['sale.order.line'].create({
|
||||
'product_id': self.product_order_timesheet4.id,
|
||||
'product_uom_qty': 10,
|
||||
'order_id': sale_order.id,
|
||||
'analytic_distribution': {f'{analytic_account.id}': 100},
|
||||
})
|
||||
with self.assertRaises(ValidationError):
|
||||
# The analytic plan 'other_analytic_plan' is mandatory on the sale order line linked to the timesheet
|
||||
self.env['account.analytic.line'].create({
|
||||
'name': 'Test Line',
|
||||
'project_id': self.project_global.id,
|
||||
'unit_amount': 50,
|
||||
'employee_id': self.employee_manager.id,
|
||||
'so_line': so_line.id,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue