[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:
Hiral Bhavsar
2018-05-22 11:52:51 +02:00
committed by jem-odoo
parent ef37686b2a
commit 0ccf63cae4
6 changed files with 557 additions and 95 deletions
+2
View File
@@ -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
+153
View File
@@ -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()
+32 -36
View File
@@ -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')
+40 -3
View File
@@ -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')
+178
View File
@@ -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')
+152 -56
View File
@@ -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')