forked from Mapan/odoo17e
334 lines
18 KiB
Python
334 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from datetime import date
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import Command
|
|
from odoo.tests import tagged
|
|
|
|
from odoo.addons.mail.tests.common import mail_new_test_user
|
|
from odoo.addons.sale_timesheet.tests.common import TestCommonSaleTimesheet
|
|
|
|
|
|
@tagged('-at_install', 'post_install')
|
|
class TestSaleValidatedTimesheet(TestCommonSaleTimesheet):
|
|
""" Test timesheet invoicing of "Approved Timesheets Only" with 2 service products that create a task in a new project.
|
|
1. Create SO, add two SO line ordered service and delivered service and confirm it
|
|
2. log some timesheet on task and validate it.
|
|
3. create invoice
|
|
4. log other timesheets
|
|
5. create a second invoice
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref=None):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
|
|
# create SO
|
|
cls.sale_order = cls.env['sale.order'].with_context(tracking_disable=True).create({
|
|
'company_id': cls.env.company.id,
|
|
'partner_id': cls.partner_a.id,
|
|
'partner_invoice_id': cls.partner_a.id,
|
|
'partner_shipping_id': cls.partner_a.id,
|
|
'pricelist_id': cls.company_data['default_pricelist'].id,
|
|
})
|
|
cls.ordered_so_line = cls.env['sale.order.line'].with_context(tracking_disable=True).create({
|
|
'product_id': cls.product_order_timesheet3.id,
|
|
'product_uom_qty': 10,
|
|
'order_id': cls.sale_order.id,
|
|
})
|
|
cls.delivered_so_line = cls.env['sale.order.line'].with_context(tracking_disable=True).create({
|
|
'product_id': cls.product_delivery_timesheet3.id,
|
|
'product_uom_qty': 10,
|
|
'order_id': cls.sale_order.id,
|
|
})
|
|
|
|
def test_sale_validated_timesheet(self):
|
|
# set invoiced_timesheet as "Approved timesheet only"
|
|
self.env['ir.config_parameter'].sudo().set_param('sale.invoiced_timesheet', 'approved')
|
|
# confirm SO
|
|
self.sale_order.action_confirm()
|
|
|
|
project_1 = self.env['project.project'].search([('sale_order_id', '=', self.sale_order.id)])
|
|
ordered_task = self.env['project.task'].search([('sale_line_id', '=', self.ordered_so_line.id)])
|
|
delivered_task = self.env['project.task'].search([('sale_line_id', '=', self.delivered_so_line.id)])
|
|
# check project, task and analytic account
|
|
self.assertEqual(self.sale_order.tasks_count, 2, "Two task should have been created on SO confirmation")
|
|
self.assertEqual(len(self.sale_order.project_ids), 1, "One project should have been created on SO confirmation")
|
|
self.assertEqual(self.sale_order.analytic_account_id, project_1.analytic_account_id, "The created project should be linked to the analytic account of the SO")
|
|
|
|
yesterday = date.today() - relativedelta(days=1)
|
|
week_before = date.today() + relativedelta(weeks=-1)
|
|
# log timesheet on task of delivered So line
|
|
delivered_timesheet1 = self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet delivered 1',
|
|
'project_id': delivered_task.project_id.id,
|
|
'task_id': delivered_task.id,
|
|
'unit_amount': 6,
|
|
'employee_id': self.employee_user.id,
|
|
'date': week_before,
|
|
})
|
|
delivered_timesheet2 = self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet delivered 2',
|
|
'project_id': delivered_task.project_id.id,
|
|
'task_id': delivered_task.id,
|
|
'unit_amount': 4,
|
|
'employee_id': self.employee_user.id,
|
|
'date': yesterday,
|
|
})
|
|
# log timesheet on task of ordered so line
|
|
ordered_timesheet1 = self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet ordered 1',
|
|
'project_id': ordered_task.project_id.id,
|
|
'task_id': ordered_task.id,
|
|
'unit_amount': 8,
|
|
'employee_id': self.employee_user.id,
|
|
'date': week_before,
|
|
})
|
|
ordered_timesheet2 = self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet ordered 2',
|
|
'project_id': ordered_task.project_id.id,
|
|
'task_id': ordered_task.id,
|
|
'unit_amount': 2,
|
|
'employee_id': self.employee_user.id,
|
|
'date': yesterday,
|
|
})
|
|
|
|
# check not any timesheet should be validated
|
|
self.assertFalse(any([delivered_timesheet1.validated, delivered_timesheet2.validated, ordered_timesheet1.validated, ordered_timesheet2.validated]), 'Timesheet should not be validated')
|
|
|
|
# Validate ordered and delivered some Timesheet
|
|
timesheet_to_validate = delivered_timesheet1 | ordered_timesheet1
|
|
timesheet_to_validate.action_validate_timesheet()
|
|
# check if timesheets are validated
|
|
self.assertTrue(delivered_timesheet1.validated)
|
|
self.assertTrue(ordered_timesheet1.validated)
|
|
|
|
self.assertTrue(any([delivered_timesheet1.validated, ordered_timesheet1.validated]), 'Timesheet should be validated')
|
|
# check timesheet is linked to SOL
|
|
self.assertEqual(delivered_timesheet1.so_line.id, self.delivered_so_line.id, "The delivered timesheet should be linked to Delivered SOL")
|
|
self.assertEqual(ordered_timesheet1.so_line.id, self.ordered_so_line.id, "The ordered timesheet should be linked to ordered SOL")
|
|
# check delivered quantity on SOL
|
|
self.assertEqual(self.ordered_so_line.qty_delivered, 8, 'Delivered quantity should be 8 as some timesheet is validated')
|
|
self.assertEqual(self.delivered_so_line.qty_delivered, 6, 'Delivered quantity should be 6 as some timesheet is validated')
|
|
|
|
# invoice SO
|
|
invoice1 = self.sale_order._create_invoices()
|
|
# check invoiced amount
|
|
self.assertEqual(invoice1.amount_total, self.ordered_so_line.price_unit * 10 + self.delivered_so_line.price_unit * 6, 'Invoiced amount should be equal to Ordered SOL + Delivered SOL unit price * 6')
|
|
# check timesheet is linked to invoice
|
|
self.assertEqual(delivered_timesheet1.timesheet_invoice_id, invoice1, "The delivered timesheet should be linked to the invoice")
|
|
self.assertFalse(ordered_timesheet1.timesheet_invoice_id, "The ordered timesheet should not be linked to the invoice, since we are in ordered quantity")
|
|
|
|
# check invoiced quantity on sale order and on invoice
|
|
ordered_invoice_line = self.ordered_so_line.invoice_lines.filtered(lambda line: line.move_id == invoice1)
|
|
self.assertEqual(self.ordered_so_line.qty_invoiced, ordered_invoice_line.quantity, "The invoiced quantity should be same on sales order line and invoice line")
|
|
delivered_invoice_line = self.delivered_so_line.invoice_lines.filtered(lambda line: line.move_id == invoice1)
|
|
self.assertEqual(self.delivered_so_line.qty_invoiced, delivered_invoice_line.quantity, "The invoiced quantity should be same on sales order line and invoice line")
|
|
|
|
# Validate remaining Timesheet
|
|
timesheet_to_validate = delivered_timesheet2 | ordered_timesheet2
|
|
timesheet_to_validate.action_validate_timesheet()
|
|
|
|
self.assertTrue(any([delivered_timesheet2.validated, ordered_timesheet2.validated]), 'Timesheet should be validated')
|
|
# check remaining timesheet is linked to SOL
|
|
self.assertEqual(delivered_timesheet2.so_line.id, self.delivered_so_line.id, "The delivered timesheet should be linked to Delivered SOL")
|
|
self.assertEqual(ordered_timesheet2.so_line.id, self.ordered_so_line.id, "The ordered timesheet should be linked to ordered SOL")
|
|
# check delivered quantity on SOL
|
|
self.assertEqual(self.ordered_so_line.qty_delivered, 10, 'All quantity should be delivered')
|
|
self.assertEqual(self.delivered_so_line.qty_delivered, 10, 'All quantity should be delivered')
|
|
|
|
# invoice remaining SO
|
|
invoice2 = self.sale_order._create_invoices()
|
|
|
|
# check invoiced amount
|
|
self.assertEqual(invoice2.amount_total, self.delivered_so_line.price_unit * 4, 'Invoiced amount should be equal to Delivered SOL unit price * 4')
|
|
self.assertEqual(invoice1.amount_total+invoice2.amount_total, self.ordered_so_line.price_unit * 10 + self.delivered_so_line.price_unit * 10, 'Invoiced amount should be equal to Ordered SOL + Delivered SOL')
|
|
# check invoiced quantity on sale order and on invoice
|
|
ordered_invoice_line2 = self.ordered_so_line.invoice_lines.filtered(lambda line: line.move_id == invoice2)
|
|
self.assertFalse(ordered_invoice_line2, "For ordered quantity so line we already invoiced full quantity on previous invoice so it should not be invoied now")
|
|
delivered_invoice_line2 = self.delivered_so_line.invoice_lines.filtered(lambda line: line.move_id == invoice2)
|
|
self.assertEqual(self.delivered_so_line.qty_invoiced, delivered_invoice_line.quantity + delivered_invoice_line2.quantity, "The invoiced quantity should be same on sales order line and invoice line")
|
|
# order should be fully invoiced
|
|
self.assertEqual(self.sale_order.invoice_status, 'invoiced', "The SO invoice status should be fully invoiced")
|
|
|
|
def test_project_sharing_with_validated_timesheet_invoicing(self):
|
|
""" This test check if the portal user in project sharing see only the totals based on the timesheets
|
|
are computed on the validated timesheets only and not all the timesheets related to a task.
|
|
Because the portal user will only see the validated timesheets and not all ones.
|
|
|
|
Test Case:
|
|
=========
|
|
|
|
"""
|
|
# set invoiced_timesheet as "Approved timesheet only"
|
|
self.env['ir.config_parameter'].sudo().set_param('sale.invoiced_timesheet', 'approved')
|
|
# confirm SO
|
|
self.sale_order.action_confirm()
|
|
|
|
project_1 = self.env['project.project'].search([('sale_order_id', '=', self.sale_order.id)])
|
|
ordered_task = self.env['project.task'].search([('sale_line_id', '=', self.ordered_so_line.id)])
|
|
portal_user = mail_new_test_user(self.env, 'Portal user', groups='base.group_portal')
|
|
|
|
self.env['project.share.wizard'].create({
|
|
'access_mode': 'edit',
|
|
'res_model': 'project.project',
|
|
'res_id': project_1.id,
|
|
'partner_ids': [
|
|
Command.link(portal_user.partner_id.id),
|
|
],
|
|
}).action_send_mail()
|
|
|
|
fields_to_fetch = ['portal_remaining_hours', 'portal_effective_hours', 'portal_total_hours_spent', 'portal_subtask_effective_hours', 'portal_progress']
|
|
basic_task_read = ordered_task.read(fields_to_fetch)[0]
|
|
portal_task_read = ordered_task.with_user(portal_user).read(fields_to_fetch)[0]
|
|
|
|
self.assertDictEqual(basic_task_read, portal_task_read)
|
|
|
|
today = date.today()
|
|
self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet ordered 1',
|
|
'project_id': ordered_task.project_id.id,
|
|
'task_id': ordered_task.id,
|
|
'unit_amount': 8,
|
|
'employee_id': self.employee_user.id,
|
|
'date': today,
|
|
})
|
|
|
|
ordered_task.invalidate_recordset(fields_to_fetch)
|
|
basic_task_read = ordered_task.read(fields_to_fetch)[0]
|
|
ordered_task.invalidate_recordset(fields_to_fetch)
|
|
portal_task_read = ordered_task.with_user(portal_user).read(fields_to_fetch)[0]
|
|
|
|
self.assertEqual(basic_task_read['portal_remaining_hours'], 2)
|
|
self.assertEqual(basic_task_read['portal_effective_hours'], 8)
|
|
self.assertEqual(basic_task_read['portal_subtask_effective_hours'], 0)
|
|
self.assertEqual(basic_task_read['portal_total_hours_spent'], 8)
|
|
self.assertEqual(basic_task_read['portal_progress'], 80)
|
|
|
|
self.assertEqual(portal_task_read['portal_remaining_hours'], 0)
|
|
self.assertEqual(portal_task_read['portal_effective_hours'], 0)
|
|
self.assertEqual(portal_task_read['portal_subtask_effective_hours'], 0)
|
|
self.assertEqual(portal_task_read['portal_total_hours_spent'], 0)
|
|
self.assertEqual(portal_task_read['portal_progress'], 0)
|
|
|
|
self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet ordered 2',
|
|
'project_id': ordered_task.project_id.id,
|
|
'task_id': ordered_task.id,
|
|
'unit_amount': 2,
|
|
'employee_id': self.employee_user.id,
|
|
'date': today,
|
|
'validated': True
|
|
})
|
|
|
|
ordered_task.invalidate_recordset(fields_to_fetch)
|
|
basic_task_read = ordered_task.read(fields_to_fetch)[0]
|
|
ordered_task.invalidate_recordset(fields_to_fetch)
|
|
portal_task_read = ordered_task.with_user(portal_user).read(fields_to_fetch)[0]
|
|
|
|
self.assertEqual(basic_task_read['portal_remaining_hours'], 0)
|
|
self.assertEqual(basic_task_read['portal_effective_hours'], 10)
|
|
self.assertEqual(basic_task_read['portal_subtask_effective_hours'], 0)
|
|
self.assertEqual(basic_task_read['portal_total_hours_spent'], 10)
|
|
self.assertEqual(basic_task_read['portal_progress'], 100)
|
|
|
|
self.assertEqual(portal_task_read['portal_remaining_hours'], 8)
|
|
self.assertEqual(portal_task_read['portal_effective_hours'], 2)
|
|
self.assertEqual(portal_task_read['portal_subtask_effective_hours'], 0)
|
|
self.assertEqual(portal_task_read['portal_total_hours_spent'], 2)
|
|
self.assertEqual(portal_task_read['portal_progress'], 20)
|
|
|
|
def test_block_edit_so_line_validated_timesheet(self):
|
|
"""
|
|
The purpose is to check that it is not possible to modify the sale order line
|
|
if the timesheet is validated when we call the logic that updates the
|
|
timesheet sale order lines.
|
|
"""
|
|
self.sale_order.action_confirm()
|
|
ordered_task = self.env['project.task'].search([('sale_line_id', '=', self.ordered_so_line.id)])
|
|
today = date.today()
|
|
not_validated_timesheet, validated_timesheet = self.env['account.analytic.line'].create([
|
|
{
|
|
'name': 'Timesheet ordered not validated',
|
|
'project_id': ordered_task.project_id.id,
|
|
'task_id': ordered_task.id,
|
|
'unit_amount': 2,
|
|
'employee_id': self.employee_user.id,
|
|
'date': today,
|
|
},
|
|
{
|
|
'name': 'Timesheet ordered validated',
|
|
'project_id': ordered_task.project_id.id,
|
|
'task_id': ordered_task.id,
|
|
'unit_amount': 2,
|
|
'employee_id': self.employee_user.id,
|
|
'date': today,
|
|
}
|
|
])
|
|
employee_map = self.env['project.sale.line.employee.map'].create(
|
|
{
|
|
'project_id': ordered_task.project_id.id,
|
|
'employee_id': self.employee_user.id,
|
|
'sale_line_id': ordered_task.sale_line_id.id,
|
|
})
|
|
employee_map.sale_line_id = ordered_task.sale_line_id.id
|
|
validated_timesheet.validated = True
|
|
|
|
other_sale_line = self.sale_order.order_line.filtered(lambda sl: sl.id != ordered_task.sale_line_id.id)[-1]
|
|
employee_map.sale_line_id = other_sale_line
|
|
ordered_task.project_id._update_timesheets_sale_line_id()
|
|
|
|
self.assertEqual(not_validated_timesheet.so_line, other_sale_line) # sale order line is updated
|
|
self.assertEqual(validated_timesheet.so_line, ordered_task.sale_line_id) # sale order line is not updated
|
|
|
|
def test_create_invoice_for_past_validated_timesheet(self):
|
|
self.env['ir.config_parameter'].sudo().set_param('sale.invoiced_timesheet', 'approved')
|
|
self.employee_user.timesheet_manager_id = self.user_manager_company_B
|
|
sale_order_2 = self.env['sale.order'].with_context(tracking_disable=True).create({
|
|
'company_id': self.env.company.id,
|
|
'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,
|
|
})
|
|
|
|
delivered_so_line = self.env['sale.order.line'].with_context(tracking_disable=True).create({
|
|
'product_id': self.product_delivery_timesheet3.id,
|
|
'product_uom_qty': 10,
|
|
'order_id': sale_order_2.id,
|
|
})
|
|
sale_order_2.action_confirm()
|
|
|
|
delivered_task = self.env['project.task'].search([('sale_line_id', '=', delivered_so_line.id)])
|
|
month_before = date.today() + relativedelta(months=-1)
|
|
start_of_month_before = month_before.replace(day=1)
|
|
end_of_month_before = date.today().replace(day=1) - relativedelta(days=1)
|
|
|
|
delivered_timesheet1 = self.env['account.analytic.line'].create({
|
|
'name': 'Timesheet delivered 1',
|
|
'project_id': delivered_task.project_id.id,
|
|
'task_id': delivered_task.id,
|
|
'unit_amount': 6,
|
|
'employee_id': self.employee_user.id,
|
|
'date': month_before,
|
|
})
|
|
delivered_timesheet1.action_validate_timesheet()
|
|
self.employee_user.last_validated_timesheet_date = date.today()
|
|
user = self.env['res.users'].create({
|
|
'name': 'Basic User',
|
|
'login': 'basic_user',
|
|
'password': 'password',
|
|
'groups_id': [(6, 0, [
|
|
self.env.ref('project.group_project_user').id,
|
|
self.env.ref('hr_timesheet.group_hr_timesheet_approver').id,
|
|
self.env.ref('sales_team.group_sale_manager').id,
|
|
self.env.ref('account.group_account_user').id, # Add the accounting accountant group
|
|
])],
|
|
})
|
|
invoice1 = sale_order_2.with_user(user).with_context(
|
|
timesheet_start_date=start_of_month_before,
|
|
timesheet_end_date=end_of_month_before
|
|
)._create_invoices()
|
|
self.assertTrue(invoice1, 'Invoice should be created')
|