[IMP] sale: new test cases for sale
This commit provides a test suite for sale flow and unit tests. We split tests in different files to try regrouping them by testing purpose: 1/ test_access_rights.py checks employee, sale person, sale manager and portal access rights. It is executes in post-install mode to be sure other addons does not break the security rules. 2/ test_onchange.py checks the onchange using the SFF mecanism in order to simulate what a sale person really does in its day-to-day job. 3/ test_sale_pricelist.py execute a sale flow with different pricelist, as this is a delicate aspect. 4/ test_sale_to_invoice.py contains tests about refund, discount and downpayment, as none of those were tested. Task #43928
This commit is contained in:
@@ -3,5 +3,7 @@
|
||||
|
||||
from . import test_sale_to_invoice
|
||||
from . import test_sale_order
|
||||
from . import test_sale_pricelist
|
||||
from . import test_onchange
|
||||
from . import test_reinvoice
|
||||
from . import test_access_rights
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tests import tagged
|
||||
from .test_sale_common import TestCommonSaleNoChart
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccessRights(TestCommonSaleNoChart):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccessRights, self).setUp()
|
||||
|
||||
Users = self.env['res.users'].with_context(no_reset_password=True)
|
||||
|
||||
group_user = self.env.ref('sales_team.group_sale_salesman')
|
||||
# Create a users
|
||||
self.user_manager = Users.create({
|
||||
'name': 'Andrew Manager',
|
||||
'login': 'manager',
|
||||
'email': 'a.m@example.com',
|
||||
'groups_id': [(6, 0, [self.env.ref('sales_team.group_sale_manager').id])]
|
||||
})
|
||||
self.user_salesperson = Users.create({
|
||||
'name': 'Mark User',
|
||||
'login': 'user',
|
||||
'email': 'm.u@example.com',
|
||||
'groups_id': [(6, 0, [group_user.id])]
|
||||
})
|
||||
self.user_salesperson_1 = Users.create({
|
||||
'name': 'Noemie User',
|
||||
'login': 'noemie',
|
||||
'email': 'n.n@example.com',
|
||||
'groups_id': [(6, 0, [group_user.id])]
|
||||
})
|
||||
self.user_portal = Users.create({
|
||||
'name': 'Chell Gladys',
|
||||
'login': 'chell',
|
||||
'email': 'chell@gladys.portal',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])]
|
||||
})
|
||||
self.user_employee = Users.create({
|
||||
'name': 'Bert Tartignole',
|
||||
'login': 'bert',
|
||||
'email': 'b.t@example.com',
|
||||
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])]
|
||||
})
|
||||
|
||||
# Create a sales channel
|
||||
self.sales_channel = self.env['crm.team'].with_context(tracking_disable=True).create({
|
||||
'name': 'Test Channel',
|
||||
})
|
||||
|
||||
# Create the SO with a specific salesperson
|
||||
self.order = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': self.partner_customer_usd.id,
|
||||
'user_id': self.user_salesperson.id
|
||||
})
|
||||
|
||||
def test_access_sales_manager(self):
|
||||
""" Test sales manager's access rights """
|
||||
SaleOrder = self.env['sale.order'].with_context(tracking_disable=True)
|
||||
# Manager can see the SO which is assigned to another salesperson
|
||||
self.order.sudo(self.user_manager).read()
|
||||
# Manager can change a salesperson of the SO
|
||||
self.order.sudo(self.user_manager).write({'user_id': self.user_salesperson_1.id})
|
||||
# Manager can create the SO for other salesperson
|
||||
sale_order = SaleOrder.sudo(self.user_manager).create({
|
||||
'partner_id': self.partner_customer_usd.id,
|
||||
'user_id': self.user_salesperson_1.id
|
||||
})
|
||||
self.assertIn(sale_order.id, SaleOrder.search([]).ids, 'Sales manager should be able to create the SO of other salesperson')
|
||||
# Manager can confirm the SO
|
||||
sale_order.sudo(self.user_manager).action_confirm()
|
||||
# Manager can not delete confirmed SO
|
||||
with self.assertRaises(UserError):
|
||||
sale_order.sudo(self.user_manager).unlink()
|
||||
# Manager can delete the SO of other salesperson if SO is in 'draft' or 'cancel' state
|
||||
self.order.sudo(self.user_manager).unlink()
|
||||
self.assertNotIn(self.order.id, SaleOrder.search([]).ids, 'Sales manager should be able to delete the SO')
|
||||
|
||||
# Manager can create a sales channel
|
||||
india_channel = self.env['crm.team'].with_context(tracking_disable=True).sudo(self.user_manager).create({
|
||||
'name': 'India',
|
||||
})
|
||||
self.assertIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to create a sales channel')
|
||||
# Manager can edit a sales channel
|
||||
india_channel.sudo(self.user_manager).write({'dashboard_graph_group': 'week'})
|
||||
self.assertEquals(india_channel.dashboard_graph_group, 'week', 'Sales manager should be able to edit a sales channel')
|
||||
# Manager can delete a sales channel
|
||||
india_channel.sudo(self.user_manager).unlink()
|
||||
self.assertNotIn(india_channel.id, self.env['crm.team'].search([]).ids, 'Sales manager should be able to delete a sales channel')
|
||||
|
||||
def test_access_sales_person(self):
|
||||
""" Test Salesperson's access rights """
|
||||
# Salesperson can see only their own sales order
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_salesperson_1).read()
|
||||
# Now assign the SO to themselves
|
||||
self.order.write({'user_id': self.user_salesperson_1.id})
|
||||
self.order.sudo(self.user_salesperson_1).read()
|
||||
# Salesperson can change a sales channel of SO
|
||||
self.order.sudo(self.user_salesperson_1).write({'team_id': self.sales_channel.id})
|
||||
# Salesperson can't create the SO of other salesperson
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['sale.order'].sudo(self.user_salesperson_1).create({
|
||||
'partner_id': self.partner_customer_usd.id,
|
||||
'user_id': self.user_salesperson.id
|
||||
})
|
||||
# Salesperson can't delete the SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_salesperson_1).unlink()
|
||||
# Salesperson can confirm the SO
|
||||
self.order.sudo(self.user_salesperson_1).action_confirm()
|
||||
|
||||
def test_access_portal_user(self):
|
||||
""" Test portal user's access rights """
|
||||
# Portal user can see the confirmed SO for which they are assigned as a customer
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_portal).read()
|
||||
|
||||
self.order.write({'partner_id': self.user_portal.partner_id.id})
|
||||
self.order.action_confirm()
|
||||
# Portal user can't edit the SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_portal).write({'team_id': self.sales_channel.id})
|
||||
# Portal user can't create the SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['sale.order'].sudo(self.user_portal).create({
|
||||
'partner_id': self.partner_customer_usd.id,
|
||||
})
|
||||
# Portal user can't delete the SO which is in 'draft' or 'cancel' state
|
||||
self.order.action_cancel()
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_portal).unlink()
|
||||
|
||||
def test_access_employee(self):
|
||||
""" Test classic employee's access rights """
|
||||
# Employee can't see any SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_employee).read()
|
||||
# Employee can't edit the SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_employee).write({'team_id': self.sales_channel.id})
|
||||
# Employee can't create the SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['sale.order'].sudo(self.user_employee).create({
|
||||
'partner_id': self.partner_customer_usd.id,
|
||||
})
|
||||
# Employee can't delete the SO
|
||||
with self.assertRaises(AccessError):
|
||||
self.order.sudo(self.user_employee).unlink()
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import Form
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@@ -48,26 +49,21 @@ class TestOnchangeProductId(TransactionCase):
|
||||
fp_tax_id = self.fiscal_position_tax_model.create(dict(position_id=fp_id.id,
|
||||
tax_src_id=tax_include_id.id,
|
||||
tax_dest_id=tax_exclude_id.id))
|
||||
so_vals = {
|
||||
'partner_id': partner_id.id,
|
||||
'pricelist_id': pricelist.id,
|
||||
'fiscal_position_id': fp_id.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': product_id.name,
|
||||
'product_id': product_id.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'product_uom': uom_id.id,
|
||||
'price_unit': 121.0
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
so = self.so_model.create(so_vals)
|
||||
# Create the SO with one SO line and apply a pricelist and fiscal position on it
|
||||
order_form = Form(self.env['sale.order'].with_context(tracking_disable=True))
|
||||
order_form.partner_id = partner_id
|
||||
order_form.pricelist_id = pricelist
|
||||
order_form.fiscal_position_id = fp_id
|
||||
with order_form.order_line.new() as line:
|
||||
line.name = product_id.name
|
||||
line.product_id = product_id
|
||||
line.product_uom_qty = 1.0
|
||||
line.product_uom = uom_id
|
||||
sale_order = order_form.save()
|
||||
|
||||
so_line = so.order_line[0]
|
||||
so_line.product_id_change()
|
||||
self.assertEquals(100, so_line.price_unit, "The included tax must be subtracted to the price")
|
||||
# Check the unit price of SO line
|
||||
self.assertEquals(100, sale_order.order_line[0].price_unit, "The included tax must be subtracted to the price")
|
||||
|
||||
def test_pricelist_application(self):
|
||||
""" Test different prices are correctly applied based on dates """
|
||||
@@ -96,23 +92,23 @@ class TestOnchangeProductId(TransactionCase):
|
||||
})]
|
||||
})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': partner.id,
|
||||
'date_order': '2017-12-20',
|
||||
'pricelist_id': christmas_pricelist.id,
|
||||
})
|
||||
# Create the SO with pricelist based on date
|
||||
order_form = Form(self.env['sale.order'].with_context(tracking_disable=True))
|
||||
order_form.partner_id = partner
|
||||
order_form.date_order = '2017-12-20'
|
||||
order_form.pricelist_id = christmas_pricelist
|
||||
with order_form.order_line.new() as line:
|
||||
line.product_id = support_product
|
||||
so = order_form.save()
|
||||
# Check the unit price and subtotal of SO line
|
||||
self.assertEqual(so.order_line[0].price_unit, 80, "First date pricelist rule not applied")
|
||||
self.assertEquals(so.order_line[0].price_subtotal, so.order_line[0].price_unit * so.order_line[0].product_uom_qty, 'Total of SO line should be a multiplication of unit price and ordered quantity')
|
||||
|
||||
order_line = self.env['sale.order.line'].new({
|
||||
'order_id': so.id,
|
||||
'product_id': support_product.id,
|
||||
})
|
||||
# Change order date of the SO and check the unit price and subtotal of SO line
|
||||
with Form(so) as order:
|
||||
order.date_order = '2017-12-30'
|
||||
with order.order_line.edit(0) as line:
|
||||
line.product_id = support_product
|
||||
|
||||
# force compute uom and prices
|
||||
order_line.product_id_change()
|
||||
order_line.product_uom_change()
|
||||
|
||||
self.assertEqual(order_line.price_unit, 80, "First date pricelist rule not applied")
|
||||
|
||||
so.date_order = '2017-12-30'
|
||||
order_line.product_id_change()
|
||||
self.assertEqual(order_line.price_unit, 50, "Second date pricelist rule not applied")
|
||||
self.assertEqual(so.order_line[0].price_unit, 50, "Second date pricelist rule not applied")
|
||||
self.assertEquals(so.order_line[0].price_subtotal, so.order_line[0].price_unit * so.order_line[0].product_uom_qty, 'Total of SO line should be a multiplication of unit price and ordered quantity')
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
import odoo
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from odoo.tests import Form
|
||||
from odoo.tools import float_compare
|
||||
|
||||
from .test_sale_common import TestCommonSaleNoChart
|
||||
|
||||
@@ -13,6 +15,8 @@ class TestSaleOrder(TestCommonSaleNoChart):
|
||||
def setUpClass(cls):
|
||||
super(TestSaleOrder, cls).setUpClass()
|
||||
|
||||
SaleOrder = cls.env['sale.order'].with_context(tracking_disable=True)
|
||||
|
||||
# set up users
|
||||
cls.setUpUsers()
|
||||
group_salemanager = cls.env.ref('sales_team.group_sale_manager')
|
||||
@@ -25,8 +29,8 @@ class TestSaleOrder(TestCommonSaleNoChart):
|
||||
cls.setUpClassicProducts()
|
||||
cls.setUpAccountJournal()
|
||||
|
||||
# create a generic Sale Order with all classical products
|
||||
cls.sale_order = cls.env['sale.order'].create({
|
||||
# create a generic Sale Order with all classical products and empty pricelist
|
||||
cls.sale_order = SaleOrder.create({
|
||||
'partner_id': cls.partner_customer_usd.id,
|
||||
'partner_invoice_id': cls.partner_customer_usd.id,
|
||||
'partner_shipping_id': cls.partner_customer_usd.id,
|
||||
@@ -59,7 +63,7 @@ class TestSaleOrder(TestCommonSaleNoChart):
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_prod_deliver = cls.env['sale.order.line'].create({
|
||||
cls.sol_product_deliver = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_deliver.name,
|
||||
'product_id': cls.product_deliver.id,
|
||||
'product_uom_qty': 2,
|
||||
@@ -196,3 +200,36 @@ class TestSaleOrder(TestCommonSaleNoChart):
|
||||
sol = so.order_line.filtered(lambda l: l.product_id == serv_cost)
|
||||
self.assertTrue(sol, 'Sale: cost invoicing does not add lines when confirming vendor invoice')
|
||||
self.assertEquals((sol.price_unit, sol.qty_delivered, sol.product_uom_qty, sol.qty_invoiced), (160, 2, 0, 0), 'Sale: line is wrong after confirming vendor invoice')
|
||||
|
||||
def test_sale_with_taxes(self):
|
||||
""" Test SO with taxes applied on its lines and check subtotal applied on its lines and total applied on the SO """
|
||||
# Create a tax with price included
|
||||
tax_include = self.env['account.tax'].create({
|
||||
'name': 'Tax with price include',
|
||||
'amount': 10,
|
||||
'price_include': True
|
||||
})
|
||||
# Create a tax with price not included
|
||||
tax_exclude = self.env['account.tax'].create({
|
||||
'name': 'Tax with no price include',
|
||||
'amount': 10,
|
||||
})
|
||||
|
||||
# Apply taxes on the sale order lines
|
||||
self.sol_product_order.write({'tax_id': [(4, tax_include.id)]})
|
||||
self.sol_serv_deliver.write({'tax_id': [(4, tax_include.id)]})
|
||||
self.sol_serv_order.write({'tax_id': [(4, tax_exclude.id)]})
|
||||
self.sol_product_deliver.write({'tax_id': [(4, tax_exclude.id)]})
|
||||
|
||||
# Trigger onchange to reset discount, unit price, subtotal, ...
|
||||
for line in self.sale_order.order_line:
|
||||
line.product_id_change()
|
||||
line._onchange_discount()
|
||||
|
||||
for line in self.sale_order.order_line:
|
||||
if line.tax_id.price_include:
|
||||
price = line.price_unit * line.product_uom_qty - line.price_tax
|
||||
self.assertEquals(float_compare(line.price_subtotal, price, precision_digits=2), 0, 'Tax should be included on an order line')
|
||||
else:
|
||||
self.assertEquals(line.price_subtotal, line.price_unit * line.product_uom_qty, 'Tax should not be included on an order line')
|
||||
self.assertEquals(self.sale_order.amount_total, self.sale_order.amount_untaxed + self.sale_order.amount_tax, 'Taxes should be applied')
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .test_sale_common import TestCommonSaleNoChart
|
||||
|
||||
|
||||
class TestSaleOrder(TestCommonSaleNoChart):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSaleOrder, cls).setUpClass()
|
||||
|
||||
Pricelist = cls.env['product.pricelist']
|
||||
PricelistItem = cls.env['product.pricelist.item']
|
||||
SaleOrder = cls.env['sale.order'].with_context(tracking_disable=True)
|
||||
SaleOrderLine = cls.env['sale.order.line'].with_context(tracking_disable=True)
|
||||
|
||||
# set up accounts and products and journals
|
||||
cls.setUpAdditionalAccounts()
|
||||
cls.setUpClassicProducts()
|
||||
cls.setUpAccountJournal()
|
||||
|
||||
# Create a product category
|
||||
cls.product_category_1 = cls.env['product.category'].create({
|
||||
'name': 'Product Category for pricelist',
|
||||
})
|
||||
|
||||
# Create a pricelist with discount policy: percentage on services, fixed price for product_order
|
||||
cls.pricelist_discount_incl = Pricelist.create({
|
||||
'name': 'Pricelist A',
|
||||
'discount_policy': 'with_discount',
|
||||
})
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_discount_incl.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.service_order.product_tmpl_id.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 10
|
||||
})
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_discount_incl.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.service_deliver.product_tmpl_id.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 20
|
||||
})
|
||||
cls.pricelist_discount_incl_item3 = PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_discount_incl.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.product_order.product_tmpl_id.id,
|
||||
'compute_price': 'fixed',
|
||||
'fixed_price': 211
|
||||
})
|
||||
|
||||
# Create a pricelist without discount policy: formula for product_category_1 category, percentage for service_order
|
||||
cls.pricelist_discount_excl = Pricelist.create({
|
||||
'name': 'Pricelist B',
|
||||
'discount_policy': 'without_discount',
|
||||
})
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_discount_excl.id,
|
||||
'applied_on': '2_product_category',
|
||||
'categ_id': cls.product_category_1.id,
|
||||
'compute_price': 'formula',
|
||||
'base': 'standard_price',
|
||||
'price_discount': 10
|
||||
})
|
||||
PricelistItem.create({
|
||||
'pricelist_id': cls.pricelist_discount_excl.id,
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': cls.service_order.product_tmpl_id.id,
|
||||
'compute_price': 'percentage',
|
||||
'percent_price': 20
|
||||
})
|
||||
|
||||
# create a generic Sale Order with all classical products and empty pricelist
|
||||
cls.sale_order = SaleOrder.create({
|
||||
'partner_id': cls.partner_customer_usd.id,
|
||||
'partner_invoice_id': cls.partner_customer_usd.id,
|
||||
'partner_shipping_id': cls.partner_customer_usd.id,
|
||||
'pricelist_id': cls.pricelist_usd.id,
|
||||
})
|
||||
cls.sol_product_order = SaleOrderLine.create({
|
||||
'name': cls.product_order.name,
|
||||
'product_id': cls.product_order.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': cls.product_order.uom_id.id,
|
||||
'price_unit': cls.product_order.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_serv_deliver = SaleOrderLine.create({
|
||||
'name': cls.service_deliver.name,
|
||||
'product_id': cls.service_deliver.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': cls.service_deliver.uom_id.id,
|
||||
'price_unit': cls.service_deliver.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_serv_order = SaleOrderLine.create({
|
||||
'name': cls.service_order.name,
|
||||
'product_id': cls.service_order.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': cls.service_order.uom_id.id,
|
||||
'price_unit': cls.service_order.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_prod_deliver = SaleOrderLine.create({
|
||||
'name': cls.product_deliver.name,
|
||||
'product_id': cls.product_deliver.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': cls.product_deliver.uom_id.id,
|
||||
'price_unit': cls.product_deliver.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
|
||||
def test_sale_with_pricelist_empty(self):
|
||||
# Trigger onchange to reset discount, unit price, subtotal, ...
|
||||
self.sale_order.onchange_partner_id()
|
||||
for line in self.sale_order.order_line:
|
||||
line.product_id_change()
|
||||
line._onchange_discount()
|
||||
# Check that pricelist of the SO has been applied on the sale order lines or not
|
||||
for line in self.sale_order.order_line:
|
||||
self.assertEquals(line.price_unit, line.product_id.list_price, 'Pricelist of the SO should not be applied on an order line %s' % (line.name,))
|
||||
|
||||
def test_sale_with_pricelist_discount_included(self):
|
||||
""" Test SO with the pricelist and check unit price appeared on its lines """
|
||||
# Change the pricelist
|
||||
self.sale_order.write({'pricelist_id': self.pricelist_discount_incl.id})
|
||||
# Trigger onchange to reset discount, unit price, subtotal, ...
|
||||
for line in self.sale_order.order_line:
|
||||
line.product_id_change()
|
||||
line._onchange_discount()
|
||||
# Check that pricelist of the SO has been applied on the sale order lines or not
|
||||
for line in self.sale_order.order_line:
|
||||
if self.sale_order.pricelist_id in line.product_id.item_ids.mapped('pricelist_id'): # concerned by pricelist (all except product_deliver)
|
||||
if line.product_id == self.product_order:
|
||||
self.assertEquals(line.price_unit, self.pricelist_discount_incl_item3.fixed_price, 'Price of product_order should be %s applied on the order line' % (self.pricelist_discount_incl_item3.fixed_price,))
|
||||
else: # only services (service_order and service_deliver)
|
||||
for item in self.sale_order.pricelist_id.item_ids.filtered(lambda l: l.product_tmpl_id == line.product_id.product_tmpl_id):
|
||||
price = item.percent_price
|
||||
self.assertEquals(price, (line.product_id.list_price - line.price_unit) / line.product_id.list_price * 100, 'Pricelist of the SO should be applied on an order line %s' % (line.product_id.name,))
|
||||
else:
|
||||
self.assertEquals(line.price_unit, line.product_id.list_price, 'Pricelist of the SO should not be applied on an order line')
|
||||
|
||||
def test_sale_with_pricelist_discount_excluded(self):
|
||||
""" Test SO with the pricelist 'discount displayed' and check discount and unit price appeared on its lines """
|
||||
# Add group 'Discount on Lines' to the user
|
||||
self.env.user.write({'groups_id': [(4, self.env.ref('sale.group_discount_per_so_line').id)]})
|
||||
|
||||
# Set product category on consumable products (for the pricelist item applying on this category)
|
||||
self.product_order.write({'categ_id': self.product_category_1.id})
|
||||
self.product_deliver.write({'categ_id': self.product_category_1.id})
|
||||
|
||||
# Change the pricelist
|
||||
self.sale_order.write({'pricelist_id': self.pricelist_discount_excl.id})
|
||||
# Trigger onchange to reset discount, unit price, subtotal, ...
|
||||
for line in self.sale_order.order_line:
|
||||
line.product_id_change()
|
||||
line._onchange_discount()
|
||||
|
||||
# Check pricelist of the SO apply or not on order lines where pricelist contains formula that add 15% on the cost price
|
||||
for line in self.sale_order.order_line:
|
||||
if line.product_id.categ_id in self.sale_order.pricelist_id.item_ids.mapped('categ_id'): # reduction per category (consummable only)
|
||||
for item in self.sale_order.pricelist_id.item_ids.filtered(lambda l: l.categ_id == line.product_id.categ_id):
|
||||
self.assertEquals(line.discount, item.price_discount, "Discount should be displayed on order line %s since its category get some discount" % (line.name,))
|
||||
self.assertEquals(line.price_unit, line.product_id.standard_price, "Price unit should be the cost price for product %s" % (line.name,))
|
||||
else:
|
||||
if line.product_id == self.service_order: # reduction for this product
|
||||
self.assertEquals(line.discount, 20.0, "Discount for product %s should be 20 percent with pricelist %s" % (line.name, self.pricelist_discount_excl.name))
|
||||
self.assertEquals(line.price_unit, line.product_id.list_price, 'Unit price of order line should be a sale price as the pricelist not applied on the other category\'s product')
|
||||
else: # no discount for the rest
|
||||
self.assertEquals(line.discount, 0.0, 'Pricelist of SO should not be applied on an order line')
|
||||
self.assertEquals(line.price_unit, line.product_id.list_price, 'Unit price of order line should be a sale price as the pricelist not applied on the other category\'s product')
|
||||
@@ -1,9 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools import pycompat
|
||||
from .test_sale_common import TestCommonSaleNoChart
|
||||
|
||||
|
||||
@@ -12,63 +10,161 @@ class TestSaleToInvoice(TestCommonSaleNoChart):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestSaleToInvoice, cls).setUpClass()
|
||||
|
||||
cls.setUpClassicProducts()
|
||||
cls.setUpAdditionalAccounts()
|
||||
cls.setUpAccountJournal()
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_model', 'odoo.osv.orm')
|
||||
def setUp(self):
|
||||
super(TestSaleToInvoice, self).setUp()
|
||||
|
||||
def test_sale_to_invoice(self):
|
||||
""" Testing for invoice create,validate and pay with invoicing and payment user."""
|
||||
# Usefull models
|
||||
IrModelData = self.env['ir.model.data']
|
||||
partner_obj = self.env['res.partner']
|
||||
journal_obj = self.env['account.journal']
|
||||
# Usefull record id
|
||||
group_id = IrModelData.xmlid_to_res_id('account.group_account_invoice') or False
|
||||
company_id = IrModelData.xmlid_to_res_id('base.main_company') or False
|
||||
|
||||
# Usefull accounts
|
||||
account_recv_id = self.account_receivable
|
||||
account_rev_id = self.account_revenue
|
||||
|
||||
# Add account to product
|
||||
product_template_id = self.env.ref('sale.advance_product_0').product_tmpl_id
|
||||
product_template_id.write({'property_account_income_id': account_rev_id})
|
||||
|
||||
# In order to test, I create new user and applied Invoicing & Payments group.
|
||||
user = self.env['res.users'].create({
|
||||
'name': 'Test User',
|
||||
'login': 'test@test.com',
|
||||
'company_id': 1,
|
||||
'groups_id': [(6, 0, [group_id])]})
|
||||
assert user, "User will not created."
|
||||
# I create partner for sales order.
|
||||
partner = partner_obj.create({
|
||||
'name': 'Test Customer',
|
||||
'email': 'testcustomer@test.com',
|
||||
'property_account_receivable_id': account_recv_id,
|
||||
# Create the SO with four order lines
|
||||
cls.sale_order = cls.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': cls.partner_customer_usd.id,
|
||||
'partner_invoice_id': cls.partner_customer_usd.id,
|
||||
'partner_shipping_id': cls.partner_customer_usd.id,
|
||||
'pricelist_id': cls.pricelist_usd.id,
|
||||
})
|
||||
SaleOrderLine = cls.env['sale.order.line'].with_context(tracking_disable=True)
|
||||
cls.sol_prod_order = SaleOrderLine.create({
|
||||
'name': cls.product_order.name,
|
||||
'product_id': cls.product_order.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': cls.product_order.uom_id.id,
|
||||
'price_unit': cls.product_order.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_serv_deliver = SaleOrderLine.create({
|
||||
'name': cls.service_deliver.name,
|
||||
'product_id': cls.service_deliver.id,
|
||||
'product_uom_qty': 4,
|
||||
'product_uom': cls.service_deliver.uom_id.id,
|
||||
'price_unit': cls.service_deliver.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_serv_order = SaleOrderLine.create({
|
||||
'name': cls.service_order.name,
|
||||
'product_id': cls.service_order.id,
|
||||
'product_uom_qty': 3,
|
||||
'product_uom': cls.service_order.uom_id.id,
|
||||
'price_unit': cls.service_order.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
cls.sol_prod_deliver = SaleOrderLine.create({
|
||||
'name': cls.product_deliver.name,
|
||||
'product_id': cls.product_deliver.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': cls.product_deliver.uom_id.id,
|
||||
'price_unit': cls.product_deliver.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
})
|
||||
|
||||
# In order to test I create sales order and confirmed it.
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': partner.id,
|
||||
'partner_invoice_id': partner.id,
|
||||
'partner_shipping_id': partner.id,
|
||||
'date_order': datetime.today(),
|
||||
'pricelist_id': self.env.ref('product.list0').id})
|
||||
assert order, "Sales order will not created."
|
||||
context = {"active_model": 'sale.order', "active_ids": [order.id], "active_id": order.id}
|
||||
order.with_context(context).action_confirm()
|
||||
# Now I create invoice.
|
||||
payment = self.env['sale.advance.payment.inv'].create({
|
||||
# Context
|
||||
cls.context = {
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [cls.sale_order.id],
|
||||
'active_id': cls.sale_order.id,
|
||||
'default_journal_id': cls.journal_sale
|
||||
}
|
||||
|
||||
def test_downpayment(self):
|
||||
""" Test invoice with a way of downpayment and check downpayment's SO line is created
|
||||
and also check a total amount of invoice is equal to a respective sale order's total amount
|
||||
"""
|
||||
# Confirm the SO
|
||||
self.sale_order.action_confirm()
|
||||
# Let's do an invoice for a deposit of 100
|
||||
payment = self.env['sale.advance.payment.inv'].with_context(self.context).create({
|
||||
'advance_payment_method': 'fixed',
|
||||
'amount': 5,
|
||||
'product_id': self.env.ref('sale.advance_product_0').id,
|
||||
'amount': 100,
|
||||
'deposit_account_id': self.account_income.id
|
||||
})
|
||||
invoice = payment.with_context(context).create_invoices()
|
||||
assert order.invoice_ids, "No any invoice is created for this sales order"
|
||||
# Now I validate pay invoice wihth Test User(invoicing and payment).
|
||||
for invoice in order.invoice_ids:
|
||||
invoice.with_context(context).invoice_validate()
|
||||
payment.create_invoices()
|
||||
|
||||
self.assertEquals(len(self.sale_order.invoice_ids), 1, 'Invoice should be created for the SO')
|
||||
downpayment_line = self.sale_order.order_line.filtered(lambda l: l.is_downpayment)
|
||||
self.assertEquals(len(downpayment_line), 1, 'SO line downpayment should be created on SO')
|
||||
|
||||
# Update delivered quantity of SO lines
|
||||
self.sol_serv_deliver.write({'qty_delivered': 4.0})
|
||||
self.sol_prod_deliver.write({'qty_delivered': 2.0})
|
||||
|
||||
# Let's do an invoice with refunds
|
||||
payment = self.env['sale.advance.payment.inv'].with_context(self.context).create({
|
||||
'advance_payment_method': 'all',
|
||||
'deposit_account_id': self.account_income.id
|
||||
})
|
||||
payment.create_invoices()
|
||||
|
||||
self.assertEquals(len(self.sale_order.invoice_ids), 2, 'Invoice should be created for the SO')
|
||||
|
||||
invoice = self.sale_order.invoice_ids[0]
|
||||
self.assertEquals(len(invoice.invoice_line_ids), len(self.sale_order.order_line), 'All lines should be invoiced')
|
||||
self.assertEquals(invoice.amount_total, self.sale_order.amount_total - downpayment_line.price_unit, 'Downpayment should be applied')
|
||||
|
||||
def test_invoice_with_discount(self):
|
||||
""" Test invoice with a discount and check discount applied on both SO lines and an invoice lines """
|
||||
# Update discount and delivered quantity on SO lines
|
||||
self.sol_prod_order.write({'discount': 20.0})
|
||||
self.sol_serv_deliver.write({'discount': 20.0, 'qty_delivered': 4.0})
|
||||
self.sol_serv_order.write({'discount': -10.0})
|
||||
self.sol_prod_deliver.write({'qty_delivered': 2.0})
|
||||
|
||||
for line in self.sale_order.order_line.filtered(lambda l: l.discount):
|
||||
product_price = line.price_unit * line.product_uom_qty
|
||||
self.assertEquals(line.discount, (product_price - line.price_subtotal) / product_price * 100, 'Discount should be applied on order line')
|
||||
|
||||
self.sale_order.action_confirm()
|
||||
|
||||
# Let's do an invoice with invoiceable lines
|
||||
payment = self.env['sale.advance.payment.inv'].with_context(self.context).create({
|
||||
'advance_payment_method': 'delivered'
|
||||
})
|
||||
payment.create_invoices()
|
||||
invoice = self.sale_order.invoice_ids[0]
|
||||
invoice.action_invoice_open()
|
||||
|
||||
# Check discount appeared on both SO lines and invoice lines
|
||||
for line, inv_line in pycompat.izip(self.sale_order.order_line, invoice.invoice_line_ids):
|
||||
self.assertEquals(line.discount, inv_line.discount, 'Discount on lines of order and invoice should be same')
|
||||
|
||||
def test_invoice_refund(self):
|
||||
""" Test invoice with a refund and check customer invoices credit note is created from respective invoice """
|
||||
# Confirm the SO
|
||||
self.sale_order.action_confirm()
|
||||
# Take only invoicable line
|
||||
order_line = self.sale_order.order_line.filtered(lambda l: l.product_id.invoice_policy == 'order')
|
||||
# Check ordered quantity, quantity to invoice and invoiced quantity of SO lines
|
||||
for line in order_line:
|
||||
self.assertEquals(line.qty_to_invoice, line.product_uom_qty, 'Quantity to invoice should be same as ordered quantity')
|
||||
self.assertEquals(line.qty_invoiced, 0.0, 'Invoiced quantity should be zero as no any invoice created for SO')
|
||||
|
||||
# Let's do an invoice with invoiceable lines
|
||||
payment = self.env['sale.advance.payment.inv'].with_context(self.context).create({
|
||||
'advance_payment_method': 'delivered'
|
||||
})
|
||||
payment.create_invoices()
|
||||
|
||||
invoice = self.sale_order.invoice_ids[0]
|
||||
|
||||
# Update quantity of an invoice lines
|
||||
invoice.invoice_line_ids[0].write({'quantity': 3.0})
|
||||
invoice.invoice_line_ids[1].write({'quantity': 2.0})
|
||||
invoice.action_invoice_open()
|
||||
|
||||
# Check quantity to invoice on SO lines
|
||||
for line in order_line:
|
||||
self.assertEquals(line.qty_to_invoice, line.product_uom_qty - line.qty_invoiced, 'Quantity to invoice should be a difference between ordered quantity and invoiced quantity')
|
||||
|
||||
# Make a credit note
|
||||
credit_note = self.env['account.invoice.refund'].with_context({'active_ids': [invoice.id], 'active_id': invoice.id}).create({
|
||||
'filter_refund': 'refund',
|
||||
'description': 'test'
|
||||
})
|
||||
credit_note.invoice_refund()
|
||||
invoice_credit_note = self.sale_order.invoice_ids[1]
|
||||
|
||||
# Check invoice's type and number
|
||||
self.assertEquals(invoice_credit_note.type, 'out_refund', 'Invoice type should be a customer credit note')
|
||||
self.assertEquals(invoice.number, invoice_credit_note.origin, 'Customer invoices credit note should be created from respective invoice')
|
||||
|
||||
Reference in New Issue
Block a user