Initial commit: Core packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:45 +02:00
commit 12c29a983b
9512 changed files with 8379910 additions and 0 deletions

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_purchase
from . import test_purchase_order_report
from . import test_purchase_invoice
from . import test_access_rights
from . import test_accrued_purchase_orders
from . import test_purchase_tax_totals
from . import test_purchase_dashboard

View file

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import Form, tagged
from odoo.exceptions import AccessError
@tagged('post_install', '-at_install')
class TestPurchaseInvoice(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
# Create a users
group_purchase_user = cls.env.ref('purchase.group_purchase_user')
group_employee = cls.env.ref('base.group_user')
group_partner_manager = cls.env.ref('base.group_partner_manager')
cls.purchase_user = cls.env['res.users'].with_context(
no_reset_password=True
).create({
'name': 'Purchase user',
'login': 'purchaseUser',
'email': 'pu@odoo.com',
'groups_id': [(6, 0, [group_purchase_user.id, group_employee.id, group_partner_manager.id])],
})
cls.vendor = cls.env['res.partner'].create({
'name': 'Supplier',
'email': 'supplier.serv@supercompany.com',
})
cls.account_expense_product = cls.env['account.account'].create({
'code': 'EXPENSE.PROD111',
'name': 'Expense - Test Account',
'account_type': 'expense',
})
# Create category
cls.product_category = cls.env['product.category'].create({
'name': 'Product Category with Expense account',
'property_account_expense_categ_id': cls.account_expense_product.id
})
cls.product = cls.env['product.product'].create({
'name': "Product",
'standard_price': 200.0,
'list_price': 180.0,
'type': 'service',
})
def test_create_purchase_order(self):
"""Check a purchase user can create a vendor bill from a purchase order but not post it"""
purchase_order_form = Form(self.env['purchase.order'].with_user(self.purchase_user))
purchase_order_form.partner_id = self.vendor
with purchase_order_form.order_line.new() as line:
line.name = self.product.name
line.product_id = self.product
line.product_qty = 4
line.price_unit = 5
purchase_order = purchase_order_form.save()
purchase_order.button_confirm()
purchase_order.order_line.qty_received = 4
purchase_order.action_create_invoice()
invoice = purchase_order.invoice_ids
with self.assertRaises(AccessError):
invoice.action_post()
def test_read_purchase_order(self):
""" Check that a purchase user can read all purchase order and 'in' invoices"""
purchase_user_2 = self.purchase_user.copy({
'name': 'Purchase user 2',
'login': 'purchaseUser2',
'email': 'pu2@odoo.com',
})
purchase_order_form = Form(self.env['purchase.order'].with_user(purchase_user_2))
purchase_order_form.partner_id = self.vendor
with purchase_order_form.order_line.new() as line:
line.name = self.product.name
line.product_id = self.product
line.product_qty = 4
line.price_unit = 5
purchase_order_user2 = purchase_order_form.save()
purchase_order_user2.button_confirm()
purchase_order_user2.order_line.qty_received = 4
purchase_order_user2.action_create_invoice()
vendor_bill_user2 = purchase_order_user2.invoice_ids
# open purchase_order_user2 and vendor_bill_user2 with `self.purchase_user`
purchase_order_user1 = Form(purchase_order_user2.with_user(self.purchase_user))
purchase_order_user1 = purchase_order_user1.save()
vendor_bill_user1 = Form(vendor_bill_user2.with_user(self.purchase_user))
vendor_bill_user1 = vendor_bill_user1.save()
def test_double_validation(self):
"""Only purchase managers can approve a purchase order when double
validation is enabled"""
group_purchase_manager = self.env.ref('purchase.group_purchase_manager')
order = self.env['purchase.order'].create({
"partner_id": self.vendor.id,
"order_line": [
(0, 0, {
'product_id': self.product.id,
'name': f'{self.product.name} {1:05}',
'price_unit': 79.80,
'product_qty': 15.0,
}),
]})
company = order.sudo().company_id
company.po_double_validation = 'two_step'
company.po_double_validation_amount = 0
self.purchase_user.write({
'company_ids': [(4, company.id)],
'company_id': company.id,
'groups_id': [(3, group_purchase_manager.id)],
})
order.with_user(self.purchase_user).button_confirm()
self.assertEqual(order.state, 'to approve')
order.with_user(self.purchase_user).button_approve()
self.assertEqual(order.state, 'to approve')
self.purchase_user.groups_id += group_purchase_manager
order.with_user(self.purchase_user).button_approve()
self.assertEqual(order.state, 'purchase')

View file

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
from odoo import fields, Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
from odoo.exceptions import UserError
@tagged('post_install', '-at_install')
class TestAccruedPurchaseOrders(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.alt_exp_account = cls.company_data['default_account_expense'].copy()
# set 'type' to 'service' to allow manualy set 'qty_delivered' even with purchase_stock installed
cls.product_a.update({'type': 'service', 'purchase_method': 'receive'})
cls.product_b.update({'type': 'service', 'purchase_method': 'receive'})
#analytic distribution
cls.default_plan = cls.env['account.analytic.plan'].create({'name': 'Default', 'company_id': False})
cls.analytic_account_a = cls.env['account.analytic.account'].create({
'name': 'analytic_account_a',
'plan_id': cls.default_plan.id,
'company_id': False,
})
cls.analytic_account_b = cls.env['account.analytic.account'].create({
'name': 'analytic_account_b',
'plan_id': cls.default_plan.id,
'company_id': False,
})
cls.product_b.property_account_expense_id = cls.alt_exp_account
cls.purchase_order = cls.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': cls.partner_a.id,
'order_line': [
Command.create({
'name': cls.product_a.name,
'product_id': cls.product_a.id,
'product_qty': 10.0,
'product_uom': cls.product_a.uom_id.id,
'price_unit': cls.product_a.list_price,
'taxes_id': False,
'analytic_distribution': {
cls.analytic_account_a.id : 80.0,
cls.analytic_account_b.id : 20.0,
},
}),
Command.create({
'name': cls.product_b.name,
'product_id': cls.product_b.id,
'product_qty': 10.0,
'product_uom': cls.product_b.uom_id.id,
'price_unit': cls.product_b.list_price,
'taxes_id': False,
'analytic_distribution': {
cls.analytic_account_b.id : 100.0,
},
}),
],
})
cls.purchase_order.button_confirm()
cls.account_revenue = cls.company_data['default_account_revenue']
cls.account_expense = cls.company_data['default_account_expense']
cls.wizard = cls.env['account.accrued.orders.wizard'].with_context({
'active_model': 'purchase.order',
'active_ids': cls.purchase_order.ids
}).create({
'account_id': cls.account_revenue.id,
})
def test_accrued_order(self):
# nothing to bill : no entries to be created
with self.assertRaises(UserError):
self.wizard.create_entries()
# 5 qty of each product billeable
self.purchase_order.order_line.qty_received = 5
self.assertRecordValues(self.env['account.move'].search(self.wizard.create_entries()['domain']).line_ids, [
# reverse move lines
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 5000},
{'account_id': self.alt_exp_account.id, 'debit': 0, 'credit': 1000},
{'account_id': self.account_revenue.id, 'debit': 6000, 'credit': 0},
# move lines
{'account_id': self.account_expense.id, 'debit': 5000, 'credit': 0},
{'account_id': self.alt_exp_account.id, 'debit': 1000, 'credit': 0},
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 6000},
])
# received products billed, nothing to bill left
move = self.env['account.move'].browse(self.purchase_order.action_create_invoice()['res_id'])
move.invoice_date = '2020-01-01'
move.action_post()
with self.assertRaises(UserError):
self.wizard.create_entries()
def test_multi_currency_accrued_order(self):
# 5 qty of each product billeable
self.purchase_order.order_line.qty_received = 5
# set currency != company currency
self.purchase_order.currency_id = self.currency_data['currency']
moves = self.env['account.move'].search(self.wizard.create_entries()['domain'])
for move in moves:
self.assertEqual(move.currency_id, self.purchase_order.currency_id)
self.assertRecordValues(moves.line_ids, [
# reverse move lines
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 5000 / 2, 'amount_currency': -5000},
{'account_id': self.alt_exp_account.id, 'debit': 0, 'credit': 1000 / 2, 'amount_currency': -1000},
{'account_id': self.account_revenue.id, 'debit': 6000 / 2, 'credit': 0, 'amount_currency': 0.0},
# move lines
{'account_id': self.account_expense.id, 'debit': 5000 / 2, 'credit': 0, 'amount_currency': 5000},
{'account_id': self.alt_exp_account.id, 'debit': 1000 / 2, 'credit': 0, 'amount_currency': 1000},
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 6000 / 2, 'amount_currency': 0.0},
])
def test_analytic_account_accrued_order(self):
self.purchase_order.order_line.qty_received = 10
self.assertRecordValues(self.env['account.move'].search(self.wizard.create_entries()['domain']).line_ids, [
# reverse move lines
{'account_id': self.account_expense.id, 'debit': 0.0, 'credit': 10000.0, 'analytic_distribution': {str(self.analytic_account_a.id): 80.0, str(self.analytic_account_b.id): 20.0}},
{'account_id': self.alt_exp_account.id, 'debit': 0.0, 'credit': 2000.0, 'analytic_distribution': {str(self.analytic_account_b.id): 100.0}},
{'account_id': self.account_revenue.id, 'debit': 12000.0, 'credit': 0.0, 'analytic_distribution': {str(self.analytic_account_a.id): 66.67, str(self.analytic_account_b.id): 33.33}},
# move lines
{'account_id': self.account_expense.id, 'debit': 10000.0, 'credit': 0.0, 'analytic_distribution': {str(self.analytic_account_a.id): 80.0, str(self.analytic_account_b.id): 20.0}},
{'account_id': self.alt_exp_account.id, 'debit': 2000.0, 'credit': 0.0, 'analytic_distribution': {str(self.analytic_account_b.id): 100.0}},
{'account_id': self.account_revenue.id, 'debit': 0.0, 'credit': 12000.0, 'analytic_distribution': {str(self.analytic_account_a.id): 66.67, str(self.analytic_account_b.id): 33.33}},
])
def test_accrued_order_with_tax_included(self):
tax_10_included = self.env['account.tax'].create({
'name': 'Tax 10% included',
'amount': 10.0,
'type_tax_use': 'purchase',
'price_include': True,
})
self.purchase_order.order_line.taxes_id = tax_10_included
self.purchase_order.order_line.qty_received = 5
self.assertRecordValues(self.env['account.move'].search(self.wizard.create_entries()['domain']).line_ids, [
# reverse move lines
{'account_id': self.account_expense.id, 'debit': 0.0, 'credit': 4545.45},
{'account_id': self.alt_exp_account.id, 'debit': 0.0, 'credit': 909.09},
{'account_id': self.account_revenue.id, 'debit': 5454.54, 'credit': 0.0},
# move lines
{'account_id': self.account_expense.id, 'debit': 4545.45, 'credit': 0.0},
{'account_id': self.alt_exp_account.id, 'debit': 909.09, 'credit': 0.0},
{'account_id': self.account_revenue.id, 'debit': 0.0, 'credit': 5454.54},
])
def test_accrued_order_returned(self):
self.purchase_order.order_line.qty_received = 10
# received products billed, nothing to bill left
move = self.env['account.move'].browse(self.purchase_order.action_create_invoice()['res_id'])
move.invoice_date = '2020-01-01'
move.action_post()
with self.assertRaises(UserError):
self.wizard.create_entries()
self.purchase_order.order_line.qty_received = 5
res = self.env['account.move'].search(self.wizard.create_entries()['domain']).line_ids
self.assertRecordValues(res, [
# reverse move lines
{'account_id': self.account_expense.id, 'debit': 5000.0, 'credit': 0.0},
{'account_id': self.alt_exp_account.id, 'debit': 1000.0, 'credit': 0.0},
{'account_id': self.account_revenue.id, 'debit': 0.0, 'credit': 6000.0},
# move lines
{'account_id': self.account_expense.id, 'debit': 0.0, 'credit': 5000.0},
{'account_id': self.alt_exp_account.id, 'debit': 0.0, 'credit': 1000.0},
{'account_id': self.account_revenue.id, 'debit': 6000.0, 'credit': 0.0},
])
self.purchase_order.order_line.qty_received = 0
res = self.env['account.move'].search(self.wizard.create_entries()['domain']).line_ids
self.assertRecordValues(res, [
# reverse move lines
{'account_id': self.account_expense.id, 'debit': 5000.0, 'credit': 0.0},
{'account_id': self.alt_exp_account.id, 'debit': 1000.0, 'credit': 0.0},
{'account_id': self.account_revenue.id, 'debit': 0.0, 'credit': 6000.0},
# move lines
{'account_id': self.account_expense.id, 'debit': 0.0, 'credit': 5000.0},
{'account_id': self.alt_exp_account.id, 'debit': 0.0, 'credit': 1000.0},
{'account_id': self.account_revenue.id, 'debit': 6000.0, 'credit': 0.0},
])
def test_error_when_different_currencies_accrued(self):
"""
Tests that if two Purchase Orders with different currencies are selected for Accrued Expense Entry,
a UserError is raised.
"""
purchase_orders = self.env['purchase.order'].create([
{
'partner_id': self.partner_a.id,
'currency_id': self.company_data['currency'].id,
},
{
'partner_id': self.partner_a.id,
'currency_id': self.currency_data['currency'].id,
}
])
purchase_orders.button_confirm()
accrued_wizard = self.env['account.accrued.orders.wizard'].with_context(
active_model='purchase.order',
active_ids=purchase_orders.ids,
).new()
with self.assertRaises(UserError, msg="An error should be raised if two different currencies are used for Accrued Expense Entry."):
accrued_wizard._compute_move_vals()

View file

@ -0,0 +1,591 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged, Form
from odoo import Command, fields
from datetime import timedelta
import pytz
@tagged('-at_install', 'post_install')
class TestPurchase(AccountTestInvoicingCommon):
def test_date_planned(self):
"""Set a date planned on 2 PO lines. Check that the PO date_planned is the earliest PO line date
planned. Change one of the dates so it is even earlier and check that the date_planned is set to
this earlier date.
"""
po = Form(self.env['purchase.order'])
po.partner_id = self.partner_a
with po.order_line.new() as po_line:
po_line.product_id = self.product_a
po_line.product_qty = 1
po_line.price_unit = 100
with po.order_line.new() as po_line:
po_line.product_id = self.product_b
po_line.product_qty = 10
po_line.price_unit = 200
po = po.save()
# Check that the same date is planned on both PO lines.
self.assertNotEqual(po.order_line[0].date_planned, False)
self.assertAlmostEqual(po.order_line[0].date_planned, po.order_line[1].date_planned, delta=timedelta(seconds=10))
self.assertAlmostEqual(po.order_line[0].date_planned, po.date_planned, delta=timedelta(seconds=10))
orig_date_planned = po.order_line[0].date_planned
# Set an earlier date planned on a PO line and check that the PO expected date matches it.
new_date_planned = orig_date_planned - timedelta(hours=1)
po.order_line[0].date_planned = new_date_planned
self.assertAlmostEqual(po.order_line[0].date_planned, po.date_planned, delta=timedelta(seconds=10))
# Set an even earlier date planned on the other PO line and check that the PO expected date matches it.
new_date_planned = orig_date_planned - timedelta(hours=72)
po.order_line[1].date_planned = new_date_planned
self.assertAlmostEqual(po.order_line[1].date_planned, po.date_planned, delta=timedelta(seconds=10))
def test_purchase_order_sequence(self):
PurchaseOrder = self.env['purchase.order'].with_context(tracking_disable=True)
company = self.env.user.company_id
self.env['ir.sequence'].search([
('code', '=', 'purchase.order'),
]).write({
'use_date_range': True, 'prefix': 'PO/%(range_year)s/',
})
vals = {
'partner_id': self.partner_a.id,
'company_id': company.id,
'currency_id': company.currency_id.id,
'date_order': '2019-01-01',
}
purchase_order = PurchaseOrder.create(vals.copy())
self.assertTrue(purchase_order.name.startswith('PO/2019/'))
vals['date_order'] = '2020-01-01'
purchase_order = PurchaseOrder.create(vals.copy())
self.assertTrue(purchase_order.name.startswith('PO/2020/'))
# In EU/BXL tz, this is actually already 01/01/2020
vals['date_order'] = '2019-12-31 23:30:00'
purchase_order = PurchaseOrder.with_context(tz='Europe/Brussels').create(vals.copy())
self.assertTrue(purchase_order.name.startswith('PO/2020/'))
def test_reminder_1(self):
"""Set to send reminder tomorrow, check if a reminder can be send to the
partner.
"""
# set partner to send reminder in Company 2
self.partner_a.with_company(self.env.companies[1]).receipt_reminder_email = True
self.partner_a.with_company(self.env.companies[1]).reminder_date_before_receipt = 1
# Create the PO in Company 1
self.env.user.tz = 'Europe/Brussels'
po = Form(self.env['purchase.order'])
po.partner_id = self.partner_a
with po.order_line.new() as po_line:
po_line.product_id = self.product_a
po_line.product_qty = 1
po_line.price_unit = 100
with po.order_line.new() as po_line:
po_line.product_id = self.product_b
po_line.product_qty = 10
po_line.price_unit = 200
# set to send reminder today
date_planned = fields.Datetime.now().replace(hour=23, minute=0) + timedelta(days=2)
po.date_planned = date_planned
po = po.save()
po.button_confirm()
# Check that reminder is not set in Company 1 and the mail will not be sent
self.assertEqual(po.company_id, self.env.companies[0])
self.assertFalse(po.receipt_reminder_email)
self.assertEqual(po.reminder_date_before_receipt, 1, "The default value should be taken from the company")
old_messages = po.message_ids
po._send_reminder_mail()
messages_send = po.message_ids - old_messages
self.assertFalse(messages_send)
# Set to send reminder in Company 1
self.partner_a.receipt_reminder_email = True
self.partner_a.reminder_date_before_receipt = 2
# Invalidate the cache to ensure that the computed fields are recomputed
self.env.invalidate_all()
self.assertTrue(po.receipt_reminder_email)
self.assertEqual(po.reminder_date_before_receipt, 2)
# check date_planned is correctly set
self.assertEqual(po.date_planned, date_planned)
po_tz = pytz.timezone(po.user_id.tz)
localized_date_planned = po.date_planned.astimezone(po_tz)
self.assertEqual(localized_date_planned, po.get_localized_date_planned())
# Ensure that the function get_localized_date_planned can accept a date in string format
self.assertEqual(localized_date_planned, po.get_localized_date_planned(po.date_planned.strftime('%Y-%m-%d %H:%M:%S')))
# check vendor is a message recipient
self.assertTrue(po.partner_id in po.message_partner_ids)
# check reminder send
old_messages = po.message_ids
po._send_reminder_mail()
messages_send = po.message_ids - old_messages
self.assertTrue(messages_send)
self.assertTrue(po.partner_id in messages_send.mapped('partner_ids'))
# check confirm button + date planned localized in message
old_messages = po.message_ids
po.confirm_reminder_mail()
messages_send = po.message_ids - old_messages
self.assertTrue(po.mail_reminder_confirmed)
self.assertEqual(len(messages_send), 1)
self.assertIn(str(localized_date_planned.date()), messages_send.body)
def test_reminder_2(self):
"""Set to send reminder tomorrow, check if no reminder can be send.
"""
po = Form(self.env['purchase.order'])
po.partner_id = self.partner_a
with po.order_line.new() as po_line:
po_line.product_id = self.product_a
po_line.product_qty = 1
po_line.price_unit = 100
with po.order_line.new() as po_line:
po_line.product_id = self.product_b
po_line.product_qty = 10
po_line.price_unit = 200
# set to send reminder tomorrow
po.date_planned = fields.Datetime.now() + timedelta(days=2)
po = po.save()
self.partner_a.receipt_reminder_email = True
self.partner_a.reminder_date_before_receipt = 1
po.button_confirm()
# check vendor is a message recipient
self.assertTrue(po.partner_id in po.message_partner_ids)
old_messages = po.message_ids
po._send_reminder_mail()
messages_send = po.message_ids - old_messages
# check no reminder send
self.assertFalse(messages_send)
def test_update_date_planned(self):
po = Form(self.env['purchase.order'])
po.partner_id = self.partner_a
with po.order_line.new() as po_line:
po_line.product_id = self.product_a
po_line.product_qty = 1
po_line.price_unit = 100
po_line.date_planned = '2020-06-06 00:00:00'
with po.order_line.new() as po_line:
po_line.product_id = self.product_b
po_line.product_qty = 10
po_line.price_unit = 200
po_line.date_planned = '2020-06-06 00:00:00'
po = po.save()
po.button_confirm()
# update first line
po._update_date_planned_for_lines([(po.order_line[0], fields.Datetime.today())])
self.assertEqual(po.order_line[0].date_planned, fields.Datetime.today())
activity = self.env['mail.activity'].search([
('summary', '=', 'Date Updated'),
('res_model_id', '=', 'purchase.order'),
('res_id', '=', po.id),
])
self.assertTrue(activity)
self.assertIn(
'<p>partner_a modified receipt dates for the following products:</p>\n'
'<p> - product_a from 2020-06-06 to %s</p>' % fields.Date.today(),
activity.note,
)
# update second line
po._update_date_planned_for_lines([(po.order_line[1], fields.Datetime.today())])
self.assertEqual(po.order_line[1].date_planned, fields.Datetime.today())
self.assertIn(
'<p>partner_a modified receipt dates for the following products:</p>\n'
'<p> - product_a from 2020-06-06 to %(today)s</p>\n'
'<p> - product_b from 2020-06-06 to %(today)s</p>' % {'today': fields.Date.today()},
activity.note,
)
def test_compute_packaging_00(self):
"""Create a PO and use packaging. Check we suggested suitable packaging
according to the product_qty. Also check product_qty or product_packaging
are correctly calculated when one of them changed.
"""
# Required for `product_packaging_qty` to be visible in the view
self.env.user.groups_id += self.env.ref('product.group_stock_packaging')
packaging_single = self.env['product.packaging'].create({
'name': "I'm a packaging",
'product_id': self.product_a.id,
'qty': 1.0,
})
packaging_dozen = self.env['product.packaging'].create({
'name': "I'm also a packaging",
'product_id': self.product_a.id,
'qty': 12.0,
})
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
})
po_form = Form(po)
with po_form.order_line.new() as line:
line.product_id = self.product_a
line.product_qty = 1.0
po_form.save()
self.assertEqual(po.order_line.product_packaging_id, packaging_single)
self.assertEqual(po.order_line.product_packaging_qty, 1.0)
with po_form.order_line.edit(0) as line:
line.product_packaging_qty = 2.0
po_form.save()
self.assertEqual(po.order_line.product_qty, 2.0)
with po_form.order_line.edit(0) as line:
line.product_qty = 24.0
po_form.save()
self.assertEqual(po.order_line.product_packaging_id, packaging_dozen)
self.assertEqual(po.order_line.product_packaging_qty, 2.0)
with po_form.order_line.edit(0) as line:
line.product_packaging_qty = 1.0
po_form.save()
self.assertEqual(po.order_line.product_qty, 12)
# Do the same test but without form, to check the `product_packaging_id` and `product_packaging_qty` are set
# without manual call to compute
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({'product_id': self.product_a.id, 'product_qty': 1.0}),
]
})
self.assertEqual(po.order_line.product_packaging_id, packaging_single)
self.assertEqual(po.order_line.product_packaging_qty, 1.0)
po.order_line.product_packaging_qty = 2.0
self.assertEqual(po.order_line.product_qty, 2.0)
po.order_line.product_qty = 24.0
self.assertEqual(po.order_line.product_packaging_id, packaging_dozen)
self.assertEqual(po.order_line.product_packaging_qty, 2.0)
po.order_line.product_packaging_qty = 1.0
self.assertEqual(po.order_line.product_qty, 12)
def test_compute_packaging_01(self):
"""Create a PO and use packaging in a multicompany environment.
Ensure any suggested packaging matches the PO's.
"""
company1 = self.company_data['company']
company2 = self.company_data_2['company']
generic_single_pack = self.env['product.packaging'].create({
'name': "single pack",
'product_id': self.product_a.id,
'qty': 1.0,
'company_id': False,
})
company2_pack_of_10 = self.env['product.packaging'].create({
'name': "pack of 10 by Company 2",
'product_id': self.product_a.id,
'qty': 10.0,
'company_id': company2.id,
})
po1 = self.env['purchase.order'].with_company(company1).create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({'product_id': self.product_a.id, 'product_qty': 10.0}),
]
})
self.assertEqual(po1.order_line.product_packaging_id, generic_single_pack)
self.assertEqual(po1.order_line.product_packaging_qty, 10.0)
# verify that with the right company, we can get the other packaging
po2 = self.env['purchase.order'].with_company(company2).create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({'product_id': self.product_a.id, 'product_qty': 10.0}),
]
})
self.assertEqual(po2.order_line.product_packaging_id, company2_pack_of_10)
self.assertEqual(po2.order_line.product_packaging_qty, 1.0)
def test_with_different_uom(self):
""" This test ensures that the unit price is correctly computed"""
# Required for `product_uom` to be visibile in the view
self.env.user.groups_id += self.env.ref('uom.group_uom')
uom_units = self.env.ref('uom.product_uom_unit')
uom_dozens = self.env.ref('uom.product_uom_dozen')
uom_pairs = self.env['uom.uom'].create({
'name': 'Pairs',
'category_id': uom_units.category_id.id,
'uom_type': 'bigger',
'factor_inv': 2,
'rounding': 1,
})
product_data = {
'name': 'SuperProduct',
'type': 'consu',
'uom_id': uom_units.id,
'uom_po_id': uom_pairs.id,
'standard_price': 100
}
product_01 = self.env['product.product'].create(product_data)
product_02 = self.env['product.product'].create(product_data)
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line:
po_line.product_id = product_01
with po_form.order_line.new() as po_line:
po_line.product_id = product_02
po_line.product_uom = uom_dozens
po = po_form.save()
self.assertEqual(po.order_line[0].price_unit, 200)
self.assertEqual(po.order_line[1].price_unit, 1200)
def test_on_change_quantity_description(self):
"""
When a user changes the quantity of a product in a purchase order it
should not change the description if the descritpion was changed by
the user before
"""
self.env.user.write({'company_id': self.company_data['company'].id})
po = Form(self.env['purchase.order'])
po.partner_id = self.partner_a
with po.order_line.new() as pol:
pol.product_id = self.product_a
pol.product_qty = 1
pol.name = "New custom description"
pol.product_qty += 1
self.assertEqual(pol.name, "New custom description")
def test_purchase_multicurrency(self):
"""
Purchase order lines should keep unit price precision of products
Also the products having prices in different currencies should be
correctly handled when creating a purchase order i-e product having a price of 100 usd
and when purchasing in EUR company the correct conversion should be applied
"""
self.env['decimal.precision'].search([
('name', '=', 'Product Price'),
]).digits = 5
product = self.env['product.product'].create({
'name': 'product_test',
'uom_id': self.env.ref('uom.product_uom_unit').id,
'lst_price': 10.0,
'standard_price': 0.12345,
})
currency = self.env['res.currency'].create({
'name': 'Dark Chocolate Coin',
'symbol': '🍫',
'rounding': 0.001,
'position': 'after',
'currency_unit_label': 'Dark Choco',
'currency_subunit_label': 'Dark Cacao Powder',
})
currency_rate = self.env['res.currency.rate'].create({
'name': '2016-01-01',
'rate': 2,
'currency_id': currency.id,
'company_id': self.env.company.id,
})
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line:
po_line.product_id = product
purchase_order_usd = po_form.save()
self.assertEqual(purchase_order_usd.order_line.price_unit, product.standard_price, "Value shouldn't be rounded $")
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.partner_a
po_form.currency_id = currency
with po_form.order_line.new() as po_line:
po_line.product_id = product
purchase_order_coco = po_form.save()
self.assertEqual(purchase_order_coco.order_line.price_unit, currency_rate.rate * product.standard_price, "Value shouldn't be rounded 🍫")
#check if the correct currency is set on the purchase order by comparing the expected price and actual price
company_a = self.company_data['company']
company_b = self.company_data_2['company']
company_b.currency_id = currency
self.env['res.currency.rate'].create({
'name': '2023-01-01',
'rate': 2,
'currency_id': currency.id,
'company_id': company_b.id,
})
product_b = self.env['product.product'].with_company(company_a).create({
'name': 'product_2',
'uom_id': self.env.ref('uom.product_uom_unit').id,
'standard_price': 0.0,
})
self.assertEqual(product_b.cost_currency_id, company_a.currency_id, 'The cost currency should be the one set on'
' the company')
product_b = product_b.with_company(company_b)
self.assertEqual(product_b.cost_currency_id, currency, 'The cost currency should be the one set on the company,'
' as the product is now opened in another company')
product_b.supplier_taxes_id = False
product_b.update({'standard_price': 10.0})
#create a purchase order with the product from company B
order_b = self.env['purchase.order'].with_company(company_b).create({
'partner_id': self.partner_a.id,
'order_line': [(0, 0, {
'product_id': product_b.id,
'product_qty': 1,
'product_uom': self.env.ref('uom.product_uom_unit').id,
})],
})
self.assertEqual(order_b.order_line.price_unit, 10.0, 'The price unit should be 10.0')
def test_purchase_not_creating_useless_product_vendor(self):
""" This test ensures that the product vendor is not created when the
product is not set on the purchase order line.
"""
#create a contact of type contact
contact = self.env['res.partner'].create({
'name': 'Contact',
'type': 'contact',
})
#create a contact of type Delivery Address lnked to the contact
delivery_address = self.env['res.partner'].create({
'name': 'Delivery Address',
'type': 'delivery',
'parent_id': contact.id,
})
#create a product that use the delivery address as vendor
product = self.env['product.product'].create({
'name': 'Product A',
'seller_ids': [(0, 0, {
'partner_id': delivery_address.id,
'min_qty': 1.0,
'price': 1.0,
})]
})
#create a purchase order with the delivery address as partner
po_form = Form(self.env['purchase.order'])
po_form.partner_id = delivery_address
with po_form.order_line.new() as po_line:
po_line.product_id = product
po_line.product_qty = 1.0
po = po_form.save()
po.button_confirm()
self.assertEqual(po.order_line.product_id.seller_ids.mapped('partner_id'), delivery_address)
def test_supplier_list_in_product_with_multicompany(self):
"""
Check that a different supplier list can be added to a product for each company.
"""
company_a = self.company_data['company']
company_b = self.company_data_2['company']
product = self.env['product.product'].create({
'name': 'product_test',
})
# create a purchase order in the company A
self.env['purchase.order'].with_company(company_a).create({
'partner_id': self.partner_a.id,
'order_line': [(0, 0, {
'product_id': product.id,
'product_qty': 1,
'product_uom': self.env.ref('uom.product_uom_unit').id,
'price_unit': 1,
})],
}).button_confirm()
self.assertEqual(product.seller_ids[0].partner_id, self.partner_a)
self.assertEqual(product.seller_ids[0].company_id, company_a)
# switch to the company B
self.env['purchase.order'].with_company(company_b).create({
'partner_id': self.partner_b.id,
'order_line': [(0, 0, {
'product_id': product.id,
'product_qty': 1,
'product_uom': self.env.ref('uom.product_uom_unit').id,
'price_unit': 2,
})],
}).button_confirm()
product = product.with_company(company_b)
self.assertEqual(product.seller_ids[0].partner_id, self.partner_b)
self.assertEqual(product.seller_ids[0].company_id, company_b)
# Switch to the company A and check that the vendor list is still the same
product = product.with_company(company_a)
self.assertEqual(product.seller_ids[0].partner_id, self.partner_a)
self.assertEqual(product.seller_ids[0].company_id, company_a)
product._invalidate_cache()
self.assertEqual(product.seller_ids[0].partner_id, self.partner_a)
self.assertEqual(product.seller_ids[0].company_id, company_a)
def test_purchase_order_multi_company(self):
"""
Check that the unit price is correct in a multi company environment
when editing a PO with another company selected.
"""
company_a = self.env.user.company_ids[0]
company_b = self.env.user.company_ids[1]
product = self.env['product.product'].with_company(company_a).create({
'name': 'product_test',
'standard_price': 3.0
})
product.with_company(company_b).standard_price = 5.0
po_form = Form(self.env['purchase.order'].with_company(company_a))
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line:
po_line.product_id = product
po = po_form.save()
po_form = Form(po.with_company(company_b))
with po_form.order_line.edit(0) as po_line:
po_line.product_id = product
po = po_form.save()
self.assertEqual(po.order_line[0].price_unit, 3.0)
def test_action_view_po_when_product_template_archived(self):
"""
Test to ensure that the purchased_product_qty value remains the same
after archiving the product template. Also check that the purchased smart
button returns the correct purchase order lines.
"""
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({
'product_id': self.product_a.id,
'product_qty': 10,
'price_unit': 1,
}),
],
})
po.button_confirm()
product_tmpl = self.product_a.product_tmpl_id
self.assertEqual(product_tmpl.purchased_product_qty, 10)
product_tmpl.action_archive()
# Need to flush the recordsets to recalculate the purchased_product_qty after archiving
product_tmpl.invalidate_recordset()
self.assertEqual(product_tmpl.purchased_product_qty, 10)
action = product_tmpl.action_view_po()
action_record = self.env[action['res_model']].search(action['domain'])
self.assertEqual(action_record, po.order_line)

View file

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.addons.mail.tests.common import MailCase
from odoo.tests import tagged, Form, new_test_user
from odoo.tools import mute_logger, format_amount
from odoo import fields
@tagged('-at_install', 'post_install')
class TestPurchaseDashboard(AccountTestInvoicingCommon, MailCase):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
# Create two new users
cls.user_a = new_test_user(cls.env, login='purchaseusera', groups='purchase.group_purchase_user')
cls.user_b = new_test_user(cls.env, login='purchaseuserb', groups='purchase.group_purchase_user')
# Create two products.
product_data = {
'name': 'SuperProduct',
'type': 'consu',
}
cls.product_100 = cls.env['product.product'].create({**product_data, 'standard_price': 100})
cls.product_250 = cls.env['product.product'].create({**product_data, 'standard_price': 250})
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_purchase_dashboard(self):
'''
Test purchase dashboard values with multiple users.
'''
# Create 3 Request for Quotations with lines.
rfqs = self.env['purchase.order'].create([{
'partner_id': self.partner_a.id,
'company_id': self.user_a.company_id.id,
'currency_id': self.user_a.company_id.currency_id.id,
'date_order': fields.Date.today(),
} for i in range(3)])
for rfq, qty in zip(rfqs, [1, 2, 3]):
rfq_form = Form(rfq)
with rfq_form.order_line.new() as line_1:
line_1.product_id = self.product_100
line_1.product_qty = qty
with rfq_form.order_line.new() as line_2:
line_2.product_id = self.product_250
line_2.product_qty = qty
rfq_form.save()
# Create 1 late RFQ without line.
self.env['purchase.order'].create([{
'partner_id': self.partner_a.id,
'company_id': self.user_a.company_id.id,
'currency_id': self.user_a.company_id.currency_id.id,
'date_order': fields.Date.today() - timedelta(days=7)
}])
# Create 1 draft RFQ for user A.
self.env['purchase.order'].with_user(self.user_a).create([{
'partner_id': self.partner_a.id,
'company_id': self.user_a.company_id.id,
'currency_id': self.user_a.company_id.currency_id.id,
'date_order': fields.Date().today() + timedelta(days=7)
}])
self.flush_tracking()
with self.mock_mail_gateway():
rfqs[0].with_user(self.user_a).write({'state': 'sent'})
self.flush_tracking()
# Sanity checks for rfq state.
self.assertEqual(rfqs[0].state, 'sent')
with self.mock_mail_gateway():
rfqs[1].with_user(self.user_b).write({'state': 'sent'})
self.flush_tracking()
self.assertEqual(rfqs[1].state, 'sent')
# Confirm Orders with lines.
rfqs.button_confirm()
# Retrieve dashboard as User A to check 'my_{to_send, waiting, late}' values.
dashboard_result = rfqs.with_user(self.user_a).retrieve_dashboard()
# Check dashboard values
currency_id = self.env.company.currency_id
zero_value_keys = ['all_waiting', 'my_waiting', 'my_late']
self.assertListEqual([dashboard_result[key] for key in zero_value_keys], [0]*len(zero_value_keys))
self.assertEqual(dashboard_result['all_to_send'], 2)
self.assertEqual(dashboard_result['my_to_send'], 1)
self.assertEqual(dashboard_result['all_late'], 1)
self.assertEqual(dashboard_result['all_avg_order_value'], format_amount(self.env, self.tax_purchase_a.compute_all(700.0)['total_included'], currency_id))
self.assertEqual(dashboard_result['all_avg_days_to_purchase'], 0)
self.assertEqual(dashboard_result['all_total_last_7_days'], format_amount(self.env, self.tax_purchase_a.compute_all(2100.0)['total_included'], currency_id))
self.assertEqual(dashboard_result['all_sent_rfqs'], 2)

View file

@ -0,0 +1,886 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
from odoo.tests.common import Form
from odoo import Command, fields
class TestPurchaseToInvoiceCommon(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls):
super(TestPurchaseToInvoiceCommon, cls).setUpClass()
uom_unit = cls.env.ref('uom.product_uom_unit')
uom_hour = cls.env.ref('uom.product_uom_hour')
cls.product_order = cls.env['product.product'].create({
'name': "Zed+ Antivirus",
'standard_price': 235.0,
'list_price': 280.0,
'type': 'consu',
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
'purchase_method': 'purchase',
'default_code': 'PROD_ORDER',
'taxes_id': False,
})
cls.service_deliver = cls.env['product.product'].create({
'name': "Cost-plus Contract",
'standard_price': 200.0,
'list_price': 180.0,
'type': 'service',
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
'purchase_method': 'receive',
'default_code': 'SERV_DEL',
'taxes_id': False,
})
cls.service_order = cls.env['product.product'].create({
'name': "Prepaid Consulting",
'standard_price': 40.0,
'list_price': 90.0,
'type': 'service',
'uom_id': uom_hour.id,
'uom_po_id': uom_hour.id,
'purchase_method': 'purchase',
'default_code': 'PRE-PAID',
'taxes_id': False,
})
cls.product_deliver = cls.env['product.product'].create({
'name': "Switch, 24 ports",
'standard_price': 55.0,
'list_price': 70.0,
'type': 'consu',
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
'purchase_method': 'receive',
'default_code': 'PROD_DEL',
'taxes_id': False,
})
@classmethod
def init_purchase(cls, partner=None, confirm=False, products=None, taxes=None, company=False):
date_planned = fields.Datetime.now() - timedelta(days=1)
po_form = Form(cls.env['purchase.order'] \
.with_company(company or cls.env.company) \
.with_context(tracking_disable=True))
po_form.partner_id = partner or cls.partner_a
po_form.partner_ref = 'my_match_reference'
for product in (products or []):
with po_form.order_line.new() as line_form:
line_form.product_id = product
line_form.price_unit = product.list_price
line_form.product_qty = 1
line_form.product_uom = product.uom_id
line_form.date_planned = date_planned
if taxes:
line_form.tax_ids.clear()
for tax in taxes:
line_form.tax_ids.add(tax)
rslt = po_form.save()
if confirm:
rslt.button_confirm()
return rslt
@tagged('post_install', '-at_install')
class TestPurchaseToInvoice(TestPurchaseToInvoiceCommon):
def test_vendor_bill_delivered(self):
"""Test if a order of product invoiced by delivered quantity can be
correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_deliver = PurchaseOrderLine.create({
'name': self.product_deliver.name,
'product_id': self.product_deliver.id,
'product_qty': 10.0,
'product_uom': self.product_deliver.uom_id.id,
'price_unit': self.product_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_deliver = PurchaseOrderLine.create({
'name': self.service_deliver.name,
'product_id': self.service_deliver.id,
'product_qty': 10.0,
'product_uom': self.service_deliver.uom_id.id,
'price_unit': self.service_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
self.assertEqual(purchase_order.invoice_status, "no")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 5)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 5)
def test_vendor_bill_ordered(self):
"""Test if a order of product invoiced by ordered quantity can be
correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_order = PurchaseOrderLine.create({
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 10.0,
'product_uom': self.product_order.uom_id.id,
'price_unit': self.product_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_order = PurchaseOrderLine.create({
'name': self.service_order.name,
'product_id': self.service_order.id,
'product_qty': 10.0,
'product_uom': self.service_order.uom_id.id,
'price_unit': self.service_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 10)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 10)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
def test_vendor_bill_delivered_return(self):
"""Test when return product, a order of product invoiced by delivered
quantity can be correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_deliver = PurchaseOrderLine.create({
'name': self.product_deliver.name,
'product_id': self.product_deliver.id,
'product_qty': 10.0,
'product_uom': self.product_deliver.uom_id.id,
'price_unit': self.product_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_deliver = PurchaseOrderLine.create({
'name': self.service_deliver.name,
'product_id': self.service_deliver.id,
'product_qty': 10.0,
'product_uom': self.service_deliver.uom_id.id,
'price_unit': self.service_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
purchase_order.order_line.qty_received = 10
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, -5)
self.assertEqual(line.qty_invoiced, 10)
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 5)
def test_vendor_bill_ordered_return(self):
"""Test when return product, a order of product invoiced by ordered
quantity can be correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_order = PurchaseOrderLine.create({
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 10.0,
'product_uom': self.product_order.uom_id.id,
'price_unit': self.product_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_order = PurchaseOrderLine.create({
'name': self.service_order.name,
'product_id': self.service_order.id,
'product_qty': 10.0,
'product_uom': self.service_order.uom_id.id,
'price_unit': self.service_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
purchase_order.order_line.qty_received = 10
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
def test_vendor_severals_bills_and_multicurrency(self):
"""
This test ensures that, when adding several PO to a bill, if they are expressed with different
currency, the amount of each AML is converted to the bill's currency
"""
PurchaseOrderLine = self.env['purchase.order.line']
PurchaseBillUnion = self.env['purchase.bill.union']
ResCurrencyRate = self.env['res.currency.rate']
usd = self.env.ref('base.USD')
eur = self.env.ref('base.EUR')
purchase_orders = []
ResCurrencyRate.create({'currency_id': usd.id, 'rate': 1})
ResCurrencyRate.create({'currency_id': eur.id, 'rate': 2})
for currency in [usd, eur]:
po = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
'currency_id': currency.id,
})
pol_prod_order = PurchaseOrderLine.create({
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 1,
'product_uom': self.product_order.uom_id.id,
'price_unit': 1000,
'order_id': po.id,
'taxes_id': False,
})
po.button_confirm()
pol_prod_order.write({'qty_received': 1})
purchase_orders.append(po)
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[0].id)
move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[1].id)
move = move_form.save()
self.assertInvoiceValues(move, [
{
'display_type': 'product',
'amount_currency': 1000,
'balance': 1000,
}, {
'display_type': 'product',
'amount_currency': 500,
'balance': 500,
}, {
'display_type': 'payment_term',
'amount_currency': -1500,
'balance': -1500,
},
], {
'amount_total': 1500,
'currency_id': usd.id,
})
def test_product_price_decimal_accuracy(self):
self.env.ref('product.decimal_price').digits = 3
self.env.company.currency_id.rounding = 0.01
po = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
'order_line': [(0, 0, {
'name': self.product_a.name,
'product_id': self.product_a.id,
'product_qty': 12,
'product_uom': self.product_a.uom_id.id,
'price_unit': 0.001,
'taxes_id': False,
})]
})
po.button_confirm()
po.order_line.qty_received = 12
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
move = move_form.save()
self.assertEqual(move.amount_total, 0.01)
def test_vendor_bill_analytic_account_model_change(self):
""" Tests whether, when an analytic account rule is set, and user changes manually the analytic account on
the po, it is the same that is mentioned in the bill.
"""
# Required for `analytic.group_analytic_accounting` to be visible in the view
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan.id})
analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual', 'plan_id': analytic_plan.id})
self.env['account.analytic.distribution.model'].create({
'analytic_distribution': {analytic_account_default.id: 100},
'product_id': self.product_order.id,
})
analytic_distribution_manual = {str(analytic_account_manual.id): 100}
po_form = Form(self.env['purchase.order'].with_context(tracking_disable=True))
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line_form:
po_line_form.name = self.product_order.name
po_line_form.product_id = self.product_order
po_line_form.product_qty = 1.0
po_line_form.price_unit = 10
po_line_form.analytic_distribution = analytic_distribution_manual
purchase_order = po_form.save()
purchase_order.button_confirm()
purchase_order.action_create_invoice()
aml = self.env['account.move.line'].search([('purchase_line_id', '=', purchase_order.order_line.id)])
self.assertRecordValues(aml, [{'analytic_distribution': analytic_distribution_manual}])
def test_purchase_order_analytic_account_product_change(self):
self.env.user.groups_id += self.env.ref('account.group_account_readonly')
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
analytic_account_super = self.env['account.analytic.account'].create({'name': 'Super Account', 'plan_id': analytic_plan.id})
analytic_account_great = self.env['account.analytic.account'].create({'name': 'Great Account', 'plan_id': analytic_plan.id})
super_product = self.env['product.product'].create({'name': 'Super Product'})
great_product = self.env['product.product'].create({'name': 'Great Product'})
self.env['account.analytic.distribution.model'].create([
{
'analytic_distribution': {analytic_account_super.id: 100},
'product_id': super_product.id,
},
{
'analytic_distribution': {analytic_account_great.id: 100},
'product_id': great_product.id,
},
])
po_form = Form(self.env['purchase.order'].with_context(tracking_disable=True))
partner = self.env['res.partner'].create({'name': 'Test Partner'})
po_form.partner_id = partner
with po_form.order_line.new() as po_line_form:
po_line_form.name = super_product.name
po_line_form.product_id = super_product
purchase_order = po_form.save()
purchase_order_line = purchase_order.order_line
self.assertEqual(purchase_order_line.analytic_distribution, {str(analytic_account_super.id): 100}, "The analytic account should be set to 'Super Account'")
purchase_order_line.write({'product_id': great_product.id})
self.assertEqual(purchase_order_line.analytic_distribution, {str(analytic_account_great.id): 100}, "The analytic account should be set to 'Great Account'")
po_no_analytic_distribution = self.env['purchase.order'].create({
'partner_id': partner.id,
})
pol_no_analytic_distribution = self.env['purchase.order.line'].create({
'name': super_product.name,
'product_id': super_product.id,
'order_id': po_no_analytic_distribution.id,
'analytic_distribution': False,
})
po_no_analytic_distribution.button_confirm()
self.assertFalse(pol_no_analytic_distribution.analytic_distribution, "The compute should not overwrite what the user has set.")
def test_purchase_order_to_invoice_analytic_rule_with_account_prefix(self):
"""
Test whether, when an analytic plan is set within the scope (applicability) of purchase
and with an account prefix set in the distribution model,
the default analytic account is correctly set during the conversion from po to invoice
"""
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
analytic_plan_default = self.env['account.analytic.plan'].create({
'name': 'default',
'applicability_ids': [Command.create({
'business_domain': 'bill',
'applicability': 'optional',
})]
})
analytic_account_default = self.env['account.analytic.account'].create({'name': 'default', 'plan_id': analytic_plan_default.id})
analytic_distribution_model = self.env['account.analytic.distribution.model'].create({
'account_prefix': '600',
'analytic_distribution': {analytic_account_default.id: 100},
'product_id': self.product_a.id,
})
po = self.env['purchase.order'].create({'partner_id': self.partner_a.id})
self.env['purchase.order.line'].create({
'order_id': po.id,
'name': 'test',
'product_id': self.product_a.id
})
self.assertFalse(po.order_line.analytic_distribution, "There should be no analytic set.")
po.button_confirm()
po.order_line.qty_received = 1
po.action_create_invoice()
self.assertRecordValues(po.invoice_ids.invoice_line_ids,
[{'analytic_distribution': analytic_distribution_model.analytic_distribution}])
def test_sequence_invoice_lines_from_multiple_purchases(self):
"""Test if the invoice lines are sequenced by purchase order when creating an invoice
from multiple selected po's"""
purchase_orders = self.env['purchase.order']
for _ in range(3):
pol_vals = [
(0, 0, {
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 10.0,
'product_uom': self.product_order.uom_id.id,
'price_unit': self.product_order.list_price,
'taxes_id': False,
'sequence': sequence_number,
}) for sequence_number in range(10, 13)]
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
'order_line': pol_vals,
})
purchase_order.button_confirm()
purchase_orders |= purchase_order
action = purchase_orders.action_create_invoice()
invoice = self.env['account.move'].browse(action['res_id'])
expected_purchase = [
purchase_orders[0], purchase_orders[0], purchase_orders[0],
purchase_orders[1], purchase_orders[1], purchase_orders[1],
purchase_orders[2], purchase_orders[2], purchase_orders[2],
]
for line in invoice.invoice_line_ids.sorted('sequence'):
self.assertEqual(line.purchase_order_id, expected_purchase.pop(0))
def test_sequence_autocomplete_invoice(self):
"""Test if the invoice lines are sequenced by purchase order when using the autocomplete
feature on a bill to add lines from po's"""
purchase_orders = self.env['purchase.order']
for _ in range(3):
pol_vals = [
(0, 0, {
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 10.0,
'product_uom': self.product_order.uom_id.id,
'price_unit': self.product_order.list_price,
'taxes_id': False,
'sequence': sequence_number,
}) for sequence_number in range(10, 13)]
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
'order_line': pol_vals,
})
purchase_order.button_confirm()
purchase_orders |= purchase_order
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
PurchaseBillUnion = self.env['purchase.bill.union']
move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[0].id)
move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[1].id)
move_form.purchase_vendor_bill_id = PurchaseBillUnion.browse(-purchase_orders[2].id)
invoice = move_form.save()
expected_purchase = [
purchase_orders[0], purchase_orders[0], purchase_orders[0],
purchase_orders[1], purchase_orders[1], purchase_orders[1],
purchase_orders[2], purchase_orders[2], purchase_orders[2],
]
for line in invoice.invoice_line_ids.sorted('sequence'):
self.assertEqual(line.purchase_order_id, expected_purchase.pop(0))
def test_partial_billing_interaction_with_invoicing_switch_threshold(self):
""" Let's say you create a partial bill 'B' for a given PO. Now if you change the
'Invoicing Switch Threshold' such that the bill date of 'B' is before the new threshold,
the PO should still take bill 'B' into account.
"""
if not self.env['ir.module.module'].search([('name', '=', 'account_accountant'), ('state', '=', 'installed')]):
self.skipTest("This test requires the installation of the account_account module")
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
'order_line': [
Command.create({
'name': self.product_deliver.name,
'product_id': self.product_deliver.id,
'product_qty': 20.0,
'product_uom': self.product_deliver.uom_id.id,
'price_unit': self.product_deliver.list_price,
'taxes_id': False,
}),
],
})
line = purchase_order.order_line[0]
purchase_order.button_confirm()
line.qty_received = 10
purchase_order.action_create_invoice()
invoice = purchase_order.invoice_ids
invoice.invoice_date = invoice.date
invoice.action_post()
self.assertEqual(line.qty_invoiced, 10)
self.env['res.config.settings'].create({
'invoicing_switch_threshold': fields.Date.add(invoice.invoice_date, days=30),
}).execute()
invoice.invalidate_model(fnames=['payment_state'])
self.assertEqual(line.qty_invoiced, 10)
line.qty_received = 15
self.assertEqual(line.qty_invoiced, 10)
def test_on_change_quantity_price_unit(self):
""" When a user changes the quantity of a product in a purchase order it
should only update the unit price if PO line has no invoice line. """
supplierinfo_vals = {
'partner_id': self.partner_a.id,
'price': 10.0,
'min_qty': 1,
"product_id": self.product_order.id,
"product_tmpl_id": self.product_order.product_tmpl_id.id,
}
supplierinfo = self.env["product.supplierinfo"].create(supplierinfo_vals)
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line_form:
po_line_form.product_id = self.product_order
po_line_form.product_qty = 1
po = po_form.save()
po_line = po.order_line[0]
self.assertEqual(10.0, po_line.price_unit, "Unit price should be set to 10.0 for 1 quantity")
# Ensure price unit is updated when changing quantity on a un-confirmed PO
supplierinfo.write({'min_qty': 2, 'price': 20.0})
po_line.write({'product_qty': 2})
self.assertEqual(20.0, po_line.price_unit, "Unit price should be set to 20.0 for 2 quantity")
po.button_confirm()
# Ensure price unit is updated when changing quantity on a confirmed PO
supplierinfo.write({'min_qty': 3, 'price': 30.0})
po_line.write({'product_qty': 3})
self.assertEqual(30.0, po_line.price_unit, "Unit price should be set to 30.0 for 3 quantity")
po.action_create_invoice()
# Ensure price unit is NOT updated when changing quantity on PO confirmed and line linked to an invoice line
supplierinfo.write({'min_qty': 4, 'price': 40.0})
po_line.write({'product_qty': 4})
self.assertEqual(30.0, po_line.price_unit, "Unit price should be set to 30.0 for 3 quantity")
with po_form.order_line.new() as po_line_form:
po_line_form.product_id = self.product_order
po_line_form.product_qty = 1
po = po_form.save()
po_line = po.order_line[1]
self.assertEqual(235.0, po_line.price_unit, "Unit price should be reset to 235.0 since the supplier supplies minimum of 4 quantities")
# Ensure price unit is updated when changing quantity on PO confirmed and line NOT linked to an invoice line
po_line.write({'product_qty': 4})
self.assertEqual(40.0, po_line.price_unit, "Unit price should be set to 40.0 for 4 quantity")
@tagged('post_install', '-at_install')
class TestInvoicePurchaseMatch(TestPurchaseToInvoiceCommon):
def test_total_match_via_partner(self):
po = self.init_purchase(confirm=True, partner=self.partner_a, products=[self.product_order])
invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
invoice._find_and_set_purchase_orders(
[], invoice.partner_id.id, invoice.amount_total)
self.assertTrue(invoice.id in po.invoice_ids.ids)
self.assertEqual(invoice.amount_total, po.amount_total)
def test_total_match_via_po_reference(self):
po = self.init_purchase(confirm=True, products=[self.product_order])
invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
invoice._find_and_set_purchase_orders(
['my_match_reference'], invoice.partner_id.id, invoice.amount_total)
self.assertTrue(invoice.id in po.invoice_ids.ids)
self.assertEqual(invoice.amount_total, po.amount_total)
def test_subset_total_match_prefer_purchase(self):
po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
invoice._find_and_set_purchase_orders(
['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=True)
additional_unmatch_po_line = po.order_line.filtered(lambda l: l.product_id == self.service_order)
self.assertTrue(invoice.id in po.invoice_ids.ids)
self.assertTrue(additional_unmatch_po_line.id in invoice.line_ids.purchase_line_id.ids)
self.assertTrue(invoice.line_ids.filtered(lambda l: l.purchase_line_id == additional_unmatch_po_line).quantity == 0)
def test_subset_total_match_reject_purchase(self):
po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
invoice = self.init_invoice('in_invoice', partner=self.partner_a, products=[self.product_order])
invoice._find_and_set_purchase_orders(
['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=False)
additional_unmatch_po_line = po.order_line.filtered(lambda l: l.product_id == self.service_order)
self.assertTrue(invoice.id in po.invoice_ids.ids)
self.assertTrue(additional_unmatch_po_line.id not in invoice.line_ids.purchase_line_id.ids)
def test_po_match_prefer_purchase(self):
po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
invoice = self.init_invoice('in_invoice', products=[self.product_a])
invoice._find_and_set_purchase_orders(
['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=True)
self.assertTrue(invoice.id in po.invoice_ids.ids)
def test_po_match_reject_purchase(self):
po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
invoice = self.init_invoice('in_invoice', products=[self.product_a])
invoice._find_and_set_purchase_orders(
['my_match_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=False)
self.assertTrue(invoice.id not in po.invoice_ids.ids)
self.assertNotEqual(invoice.amount_total, po.amount_total)
def test_no_match(self):
po = self.init_purchase(confirm=True, products=[self.product_order, self.service_order])
invoice = self.init_invoice('in_invoice', products=[self.product_a])
invoice._find_and_set_purchase_orders(
['other_reference'], invoice.partner_id.id, invoice.amount_total, prefer_purchase_line=False)
self.assertTrue(invoice.id not in po.invoice_ids.ids)
def test_onchange_partner_currency(self):
"""
Test that the currency of the Bill is correctly set when the partner is changed
as well as the currency of the Bill lines
"""
vendor_eur = self.env['res.partner'].create({
'name': 'Vendor EUR',
'property_purchase_currency_id': self.env.ref('base.EUR').id,
})
vendor_us = self.env['res.partner'].create({
'name': 'Vendor USD',
'property_purchase_currency_id': self.env.ref('base.USD').id,
})
vendor_no_currency = self.env['res.partner'].create({
'name': 'Vendor No Currency',
})
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.partner_id = vendor_eur
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.product_order
line_form.quantity = 1
bill = move_form.save()
self.assertEqual(bill.currency_id, self.env.ref('base.EUR'), "The currency of the Bill should be the same as the currency of the partner")
self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.EUR'), "The currency of the Bill lines should be the same as the currency of the partner")
move_form.partner_id = vendor_us
bill = move_form.save()
self.assertEqual(bill.currency_id, self.env.ref('base.USD'), "The currency of the Bill should be the same as the currency of the partner")
self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.USD'), "The currency of the Bill lines should be the same as the currency of the partner")
move_form.partner_id = vendor_no_currency
bill = move_form.save()
self.assertEqual(bill.currency_id, self.env.company.currency_id, "The currency of the Bill should be the same as the currency of the company")
self.assertEqual(bill.invoice_line_ids.currency_id, self.env.company.currency_id, "The currency of the Bill lines should be the same as the currency of the company")
def test_onchange_partner_no_currency(self):
"""
Test that the currency of the Bill is correctly set when the partner is changed
as well as the currency of the Bill lines even if the partner has no property_purchase_currency_id set
or when and the `default_currency_id` is defined in the context
"""
vendor_a = self.env['res.partner'].create({
'name': 'Vendor A with No Currency',
})
vendor_b = self.env['res.partner'].create({
'name': 'Vendor B with No Currency',
})
ctx = {'default_move_type': 'in_invoice'}
move_form = Form(self.env['account.move'].with_context(ctx))
move_form.partner_id = vendor_a
move_form.currency_id = self.env.ref('base.EUR')
with move_form.invoice_line_ids.new() as line_form:
line_form.product_id = self.product_order
line_form.quantity = 1
bill = move_form.save()
self.assertEqual(bill.currency_id, self.env.ref('base.EUR'), "The currency of the Bill should be the one set on the Bill")
self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.EUR'), "The currency of the Bill lines should be the same as the currency of the Bill")
move_form.partner_id = vendor_b
bill = move_form.save()
self.assertEqual(bill.currency_id, self.env.ref('base.EUR'), "The currency of the Bill should be the one set on the Bill")
self.assertEqual(bill.invoice_line_ids.currency_id, self.env.ref('base.EUR'), "The currency of the Bill lines should be the same as the currency of the Bill")
ctx['default_currency_id'] = self.currency_data['currency'].id
move_form_currency_in_context = Form(self.env['account.move'].with_context(ctx))
move_form_currency_in_context.currency_id = self.env.ref('base.EUR')
with move_form_currency_in_context.invoice_line_ids.new() as line_form:
line_form.product_id = self.product_order
line_form.quantity = 1
move_form_currency_in_context.save()
move_form_currency_in_context.partner_id = vendor_a
bill = move_form_currency_in_context.save()
self.assertEqual(bill.currency_id, self.currency_data['currency'], "The currency of the Bill should be the one of the context")
self.assertEqual(bill.invoice_line_ids.currency_id, self.currency_data['currency'], "The currency of the Bill lines should be the same as the currency of the Bill")
def test_payment_reference_autocomplete_invoice(self):
"""
Test that the payment_reference field is not replaced when selected a purchase order
We test the flow for 8 use cases:
- Purchase order with partner ref:
- Bill with ref:
- Bill with payment_reference -> should not be replaced
- Bill without payment_reference -> should be the po.partner_ref
- Bill without ref:
- Bill with payment_reference -> should not be replaced
- Bill without payment_reference -> should be the po.partner_ref
- Purchase order without partner ref:
- Bill with ref
- Bill with payment_reference -> should not be replaced
- Bill without payment_reference -> should be the bill ref
- Bill with ref
- Bill with payment_reference -> should not be replaced
- Bill without payment_reference -> should be empty
"""
purchase_order_w_ref, purchase_order_wo_ref = self.env['purchase.order'].with_context(tracking_disable=True).create([
{
'partner_id': self.partner_a.id,
'partner_ref': partner_ref,
'order_line': [
Command.create({
'product_id': self.product_order.id,
'product_qty': 1.0,
'price_unit': self.product_order.list_price,
'taxes_id': False,
}),
]
} for partner_ref in ('PO-001', False)
])
(purchase_order_w_ref + purchase_order_wo_ref).button_confirm()
expected_values_dict = {
purchase_order_w_ref: {
'w_bill_ref': {'w_payment_reference': '222', 'wo_payment_reference': purchase_order_w_ref.partner_ref},
'wo_bill_ref': {'w_payment_reference': '222', 'wo_payment_reference': purchase_order_w_ref.partner_ref},
},
purchase_order_wo_ref: {
'w_bill_ref': {'w_payment_reference': '222', 'wo_payment_reference': '111'},
'wo_bill_ref': {'w_payment_reference': '222', 'wo_payment_reference': ''},
}
}
for purchase_order, purchase_expected_values in expected_values_dict.items():
for w_bill_ref, expected_values in purchase_expected_values.items():
for w_payment_reference, expected_value in expected_values.items():
with self.subTest(po_partner_ref=purchase_order.partner_ref, w_bill_ref=w_bill_ref, w_payment_reference=w_payment_reference, expected_value=expected_value):
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.ref = '111' if w_bill_ref == 'w_bill_ref' else ''
move_form.payment_reference = '222' if w_payment_reference == 'w_payment_reference' else ''
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-purchase_order.id).exists()
payment_reference = move_form._values['payment_reference']
self.assertEqual(payment_reference, expected_value, "The payment reference should be %s" % expected_value)
def test_invoice_user_id_on_bill(self):
"""
Test that the invoice_user_id field is False when creating a vendor bill from a PO
or when using Auto-Complete feature of a vendor bill.
"""
group_purchase_user = self.env.ref('purchase.group_purchase_user')
group_employee = self.env.ref('base.group_user')
group_partner_manager = self.env.ref('base.group_partner_manager')
purchase_user = self.env['res.users'].with_context(no_reset_password=True).create({
'name': 'Purchase user',
'login': 'purchaseUser',
'email': 'pu@odoo.com',
'groups_id': [Command.set([group_purchase_user.id, group_employee.id, group_partner_manager.id])],
})
po1 = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.partner_a.id,
'user_id': purchase_user.id,
'order_line': [
Command.create({
'product_id': self.product_order.id,
'product_qty': 1.0,
'price_unit': self.product_order.list_price,
'taxes_id': False,
}),
]
})
po2 = po1.copy()
po1.button_confirm()
po2.button_confirm()
# creating bill from PO
po1.order_line.qty_received = 1
po1.action_create_invoice()
invoice1 = po1.invoice_ids
self.assertFalse(invoice1.invoice_user_id)
# creating bill with Auto_complete feature
move_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
move_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po2.id)
invoice2 = move_form.save()
self.assertFalse(invoice2.invoice_user_id)

View file

@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import Form, tagged
from datetime import datetime, timedelta
@tagged('post_install', '-at_install')
class TestPurchaseOrderReport(AccountTestInvoicingCommon):
def test_00_purchase_order_report(self):
uom_dozen = self.env.ref('uom.product_uom_dozen')
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'currency_id': self.currency_data['currency'].id,
'order_line': [
(0, 0, {
'name': self.product_a.name,
'product_id': self.product_a.id,
'product_qty': 1.0,
'product_uom': uom_dozen.id,
'price_unit': 100.0,
'date_planned': datetime.today(),
'taxes_id': False,
}),
(0, 0, {
'name': self.product_b.name,
'product_id': self.product_b.id,
'product_qty': 1.0,
'product_uom': uom_dozen.id,
'price_unit': 200.0,
'date_planned': datetime.today(),
'taxes_id': False,
}),
],
})
po.button_confirm()
f = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
f.invoice_date = f.date
f.partner_id = po.partner_id
# <field name="invoice_vendor_bill_id" position="after">
# <field name="purchase_id" invisible="1"/>
# <label for="purchase_vendor_bill_id" string="Auto-Complete" class="oe_edit_only"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}" />
# <field name="purchase_vendor_bill_id" nolabel="1"
# attrs="{'invisible': ['|', ('state','!=','draft'), ('move_type', '!=', 'in_invoice')]}"
# class="oe_edit_only"
# domain="partner_id and [('company_id', '=', company_id), ('partner_id.commercial_partner_id', '=', commercial_partner_id)] or [('company_id', '=', company_id)]"
# placeholder="Select a purchase order or an old bill"
# context="{'show_total_amount': True}"
# options="{'no_create': True, 'no_open': True}"/>
# </field>
# @api.onchange('purchase_vendor_bill_id', 'purchase_id')
# def _onchange_purchase_auto_complete(self):
# ...
# elif self.purchase_vendor_bill_id.purchase_order_id:
# self.purchase_id = self.purchase_vendor_bill_id.purchase_order_id
# self.purchase_vendor_bill_id = False
# purchase_vendor_bill_id = fields.Many2one('purchase.bill.union'
# class PurchaseBillUnion(models.Model):
# _name = 'purchase.bill.union'
# ...
# def init(self):
# self.env.cr.execute("""
# ...
# SELECT
# -id, name, ...
# id as purchase_order_id
# FROM purchase_order
# ...
# )""")
# ...
f.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id)
invoice = f.save()
invoice.action_post()
po.flush_model()
res_product1 = self.env['purchase.report'].search([
('order_id', '=', po.id),
('product_id', '=', self.product_a.id),
('company_id', '=', self.company_data['company'].id),
])
# check that report will convert dozen to unit or not
self.assertEqual(res_product1.qty_ordered, 12.0, 'UoM conversion is not working')
# report should show in company currency (amount/rate) = (100/2)
self.assertEqual(res_product1.price_total, 50.0, 'Currency conversion is not working')
res_product2 = self.env['purchase.report'].search([
('order_id', '=', po.id),
('product_id', '=', self.product_b.id),
('company_id', '=', self.company_data['company'].id),
])
self.assertEqual(res_product2.qty_ordered, 1.0, 'No conversion needed since product_b is already a dozen')
# report should show in company currency (amount/rate) = (200/2)
self.assertEqual(res_product2.price_total, 100.0, 'Currency conversion is not working')
def test_01_delay_and_delay_pass(self):
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.partner_a
po_form.date_order = datetime.now() + timedelta(days=10)
with po_form.order_line.new() as line:
line.product_id = self.product_a
with po_form.order_line.new() as line:
line.product_id = self.product_b
po_form.date_planned = datetime.now() + timedelta(days=15)
po = po_form.save()
po.button_confirm()
po.flush_model()
report = self.env['purchase.report'].read_group(
[('order_id', '=', po.id)],
['order_id', 'delay', 'delay_pass'],
['order_id'],
)
self.assertEqual(round(report[0]['delay']), -10, msg="The PO has been confirmed 10 days in advance")
self.assertEqual(round(report[0]['delay_pass']), 5, msg="There are 5 days between the order date and the planned date")
def test_02_po_report_note_section_filter(self):
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'currency_id': self.currency_data['currency'].id,
'order_line': [
(0, 0, {
'name': 'This is a note',
'display_type': 'line_note',
'product_id': False,
'product_qty': 0.0,
'product_uom': False,
'price_unit': 0.0,
'taxes_id': False,
}),
(0, 0, {
'name': 'This is a section',
'display_type': 'line_section',
'product_id': False,
'product_qty': 0.0,
'product_uom': False,
'price_unit': 0.0,
'taxes_id': False,
}),
],
})
po.button_confirm()
result_po = self.env['purchase.report'].search([('order_id', '=', po.id)])
self.assertFalse(result_po, "The report should ignore the notes and sections")
def test_po_report_currency(self):
"""
Check that the currency of the report is the one of the current company
"""
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'currency_id': self.currency_data['currency'].id,
'order_line': [
(0, 0, {
'product_id': self.product_a.id,
'product_qty': 10.0,
'price_unit': 50.0,
}),
],
})
po_2 = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'currency_id': self.env['res.currency'].search([('name', '=', 'EUR')], limit=1).id,
'order_line': [
(0, 0, {
'product_id': self.product_a.id,
'product_qty': 10.0,
'price_unit': 50.0,
}),
],
})
# flush the POs to make sure the report is up to date
po.flush_model()
po_2.flush_model()
report = self.env['purchase.report'].search([('product_id', "=", self.product_a.id)])
self.assertEqual(report.currency_id, self.env.company.currency_id)
def test_avg_price_calculation(self):
"""
Check that the average price is calculated based on the quantity ordered in each line
PO:
- 10 unit of product A -> price $50
- 1 unit of product A -> price $10
Total qty_ordered: 11
avergae price: 46.36 = ((10 * 50) + (10 * 1)) / 11
"""
po = self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
(0, 0, {
'product_id': self.product_a.id,
'product_qty': 10.0,
'price_unit': 50.0,
}),
(0, 0, {
'product_id': self.product_a.id,
'product_qty': 1.0,
'price_unit': 10.0,
}),
],
})
po.button_confirm()
po.flush_model()
report = self.env['purchase.report'].read_group(
[('product_id', '=', self.product_a.id)],
['qty_ordered', 'price_average:avg'],
['product_id'],
)
self.assertEqual(report[0]['qty_ordered'], 11)
self.assertEqual(round(report[0]['price_average'], 2), 46.36)

View file

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from odoo.addons.account.tests.test_invoice_tax_totals import TestTaxTotals
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class PurchaseTestTaxTotals(TestTaxTotals):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.po_product = cls.env['product.product'].create({
'name': 'Odoo course',
'type': 'service',
})
def _create_document_for_tax_totals_test(self, lines_data):
# Overridden in order to run the inherited tests with purchase.order's
# tax_totals field instead of account.move's
lines_vals = [
(0, 0, {
'name': 'test',
'product_id': self.po_product.id,
'product_qty': 1,
'product_uom': self.po_product.uom_po_id.id,
'price_unit': amount,
'taxes_id': [(6, 0, taxes.ids)],
})
for amount, taxes in lines_data]
return self.env['purchase.order'].create({
'partner_id': self.partner_a.id,
'order_line': lines_vals,
})
def test_archived_tax_totals(self):
tax_10 = self.env['account.tax'].create({
'name': "tax_10",
'amount_type': 'percent',
'amount': 10.0,
'tax_group_id': self.tax_group1.id,
})
po = self._create_document_for_tax_totals_test([
(100.0, tax_10),
])
po.button_confirm()
po.order_line.qty_received = 1
po.action_create_invoice()
invoice = po.invoice_ids
invoice.invoice_date = '2020-01-01'
invoice.action_post()
old_ammount = po.amount_total
tax_10.active = False
self.assertEqual(po.amount_total, old_ammount)