194 lines
7.3 KiB
Python
194 lines
7.3 KiB
Python
# Copyright 2022 Stéphan Sainléger (Elabore)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import Command
|
|
from odoo.tests import tagged
|
|
from odoo.addons.sale_project.tests.common import TestSaleProjectCommon
|
|
|
|
|
|
@tagged("post_install", "-at_install")
|
|
class TestProjectLinkFromInvoice(TestSaleProjectCommon):
|
|
"""Tests for the project_link_from_invoice module.
|
|
|
|
Verifies that invoices correctly expose related project(s) via
|
|
the computed fields project_ids, project_count and projects_name.
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
|
|
|
|
# Service product that creates a task in a new project on SO confirmation
|
|
cls.service_product = cls.env["product.product"].create(
|
|
{
|
|
"name": "Service with Project",
|
|
"type": "service",
|
|
"invoice_policy": "order",
|
|
"service_tracking": "task_in_project",
|
|
}
|
|
)
|
|
|
|
# Partner used for orders and invoices
|
|
cls.partner = cls.env["res.partner"].create({"name": "Test Partner"})
|
|
|
|
def _confirm_sale_order(self, product, qty=1):
|
|
"""Create and confirm a sale order with one service line."""
|
|
so = self.env["sale.order"].create(
|
|
{
|
|
"partner_id": self.partner.id,
|
|
"order_line": [
|
|
Command.create(
|
|
{
|
|
"product_id": product.id,
|
|
"product_uom_qty": qty,
|
|
}
|
|
)
|
|
],
|
|
}
|
|
)
|
|
so.action_confirm()
|
|
return so
|
|
|
|
def _invoice_sale_order(self, sale_order):
|
|
"""Create and post an invoice from a confirmed sale order."""
|
|
invoice_wizard = (
|
|
self.env["sale.advance.payment.inv"]
|
|
.with_context(
|
|
active_model="sale.order",
|
|
active_ids=sale_order.ids,
|
|
active_id=sale_order.id,
|
|
)
|
|
.create({"advance_payment_method": "delivered"})
|
|
)
|
|
invoices = invoice_wizard._create_invoices(sale_order)
|
|
invoices.action_post()
|
|
return invoices
|
|
|
|
# ------------------------------------------------------------------
|
|
# Tests
|
|
# ------------------------------------------------------------------
|
|
|
|
def test_no_project_on_unrelated_invoice(self):
|
|
"""An invoice not linked to any sale order has no related project."""
|
|
invoice = self.env["account.move"].create(
|
|
{
|
|
"move_type": "out_invoice",
|
|
"partner_id": self.partner.id,
|
|
"invoice_line_ids": [
|
|
Command.create(
|
|
{
|
|
"name": "Manual line",
|
|
"quantity": 1,
|
|
"price_unit": 100.0,
|
|
}
|
|
)
|
|
],
|
|
}
|
|
)
|
|
self.assertEqual(invoice.project_count, 0)
|
|
self.assertFalse(invoice.project_ids)
|
|
self.assertEqual(invoice.projects_name, "")
|
|
|
|
def test_single_project_linked_to_invoice(self):
|
|
"""An invoice from a SO that created a task has one related project."""
|
|
so = self._confirm_sale_order(self.service_product)
|
|
# The SO must have generated a task in a project
|
|
task = so.tasks_ids[:1]
|
|
self.assertTrue(task, "Sale order confirmation should have created a task")
|
|
project = task.project_id
|
|
self.assertTrue(project, "Task should belong to a project")
|
|
|
|
invoice = self._invoice_sale_order(so)
|
|
self.assertEqual(len(invoice), 1)
|
|
self.assertEqual(invoice.project_count, 1)
|
|
self.assertIn(project, invoice.project_ids)
|
|
self.assertIn(project.name, invoice.projects_name)
|
|
|
|
def test_multiple_projects_linked_to_invoice(self):
|
|
"""An invoice from two SOs with different projects shows both projects."""
|
|
so1 = self._confirm_sale_order(self.service_product)
|
|
so2 = self._confirm_sale_order(self.service_product)
|
|
|
|
project1 = so1.tasks_ids[:1].project_id
|
|
project2 = so2.tasks_ids[:1].project_id
|
|
self.assertTrue(project1 and project2)
|
|
# Ensure the two SOs created different projects
|
|
self.assertNotEqual(project1, project2)
|
|
|
|
# Create a single invoice covering both sale orders
|
|
invoice_wizard = (
|
|
self.env["sale.advance.payment.inv"]
|
|
.with_context(
|
|
active_model="sale.order",
|
|
active_ids=(so1 + so2).ids,
|
|
)
|
|
.create({"advance_payment_method": "delivered"})
|
|
)
|
|
invoices = invoice_wizard._create_invoices(so1 + so2)
|
|
invoices.action_post()
|
|
|
|
# Find the invoice that covers both SO lines
|
|
combined_invoice = invoices[:1]
|
|
self.assertGreaterEqual(combined_invoice.project_count, 2)
|
|
self.assertIn(project1, combined_invoice.project_ids)
|
|
self.assertIn(project2, combined_invoice.project_ids)
|
|
|
|
def test_action_open_projects_single(self):
|
|
"""action_open_projects returns a form view when there is one project."""
|
|
so = self._confirm_sale_order(self.service_product)
|
|
invoice = self._invoice_sale_order(so)
|
|
self.assertEqual(invoice.project_count, 1)
|
|
|
|
action = invoice.action_open_projects()
|
|
self.assertEqual(action["res_id"], invoice.project_ids.id)
|
|
view_types = [v[1] for v in action["views"]]
|
|
self.assertIn("form", view_types)
|
|
self.assertNotIn("list", view_types)
|
|
|
|
def test_action_open_projects_multiple(self):
|
|
"""action_open_projects returns a list view when there are multiple projects."""
|
|
so1 = self._confirm_sale_order(self.service_product)
|
|
so2 = self._confirm_sale_order(self.service_product)
|
|
|
|
invoice_wizard = (
|
|
self.env["sale.advance.payment.inv"]
|
|
.with_context(
|
|
active_model="sale.order",
|
|
active_ids=(so1 + so2).ids,
|
|
)
|
|
.create({"advance_payment_method": "delivered"})
|
|
)
|
|
invoices = invoice_wizard._create_invoices(so1 + so2)
|
|
invoices.action_post()
|
|
|
|
combined_invoice = invoices[:1]
|
|
if combined_invoice.project_count < 2:
|
|
self.skipTest("Combined invoice does not span multiple projects")
|
|
|
|
action = combined_invoice.action_open_projects()
|
|
view_types = [v[1] for v in action["views"]]
|
|
self.assertIn("list", view_types)
|
|
self.assertIn("form", view_types)
|
|
self.assertNotIn("target", action)
|
|
|
|
def test_projects_name_separator(self):
|
|
"""projects_name joins multiple project names with ' ; '."""
|
|
so1 = self._confirm_sale_order(self.service_product)
|
|
so2 = self._confirm_sale_order(self.service_product)
|
|
|
|
invoice_wizard = (
|
|
self.env["sale.advance.payment.inv"]
|
|
.with_context(
|
|
active_model="sale.order",
|
|
active_ids=(so1 + so2).ids,
|
|
)
|
|
.create({"advance_payment_method": "delivered"})
|
|
)
|
|
invoices = invoice_wizard._create_invoices(so1 + so2)
|
|
invoices.action_post()
|
|
|
|
combined_invoice = invoices[:1]
|
|
if combined_invoice.project_count >= 2:
|
|
self.assertIn(" ; ", combined_invoice.projects_name)
|