diff --git a/purchase_discount/tests/test_purchase_discount.py b/purchase_discount/tests/test_purchase_discount.py index 19940f3a398..646ea9509e4 100644 --- a/purchase_discount/tests/test_purchase_discount.py +++ b/purchase_discount/tests/test_purchase_discount.py @@ -1,64 +1,68 @@ # -*- coding: utf-8 -*- # Copyright 2016 ACSONE SA/NV () -# Copyright 2015-2017 Tecnativa - Pedro M. Baeza +# Copyright 2015-2018 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import odoo.tests.common as common from odoo import fields -class TestPurchaseOrder(common.SavepointCase): - @classmethod - def setUpClass(cls): - super(TestPurchaseOrder, cls).setUpClass() - cls.product_1 = cls.env['product.product'].create({ +class TestPurchaseOrder(common.HttpCase): + at_install = False + post_install = False + + def setUp(self): + super(TestPurchaseOrder, self).setUp() + self.company = self.env.user.company_id + self.company.tax_calculation_rounding_method = 'round_per_line' + self.product_1 = self.env['product.product'].create({ 'name': 'Test product 1', }) - cls.product_2 = cls.env['product.product'].create({ + self.product_2 = self.env['product.product'].create({ 'name': 'Test product 2', }) - po_model = cls.env['purchase.order.line'] + po_model = self.env['purchase.order.line'] # Make sure currency is EUR for not having troubles with rates - cls.env.user.company_id.currency_id = cls.env.ref('base.EUR') - cls.purchase_order = cls.env['purchase.order'].create({ - 'partner_id': cls.env.ref('base.res_partner_3').id, + self.env.user.company_id.currency_id = self.env.ref('base.EUR') + self.purchase_order = self.env['purchase.order'].create({ + 'partner_id': self.env.ref('base.res_partner_3').id, }) - cls.po_line_1 = po_model.create({ - 'order_id': cls.purchase_order.id, - 'product_id': cls.product_1.id, + self.po_line_1 = po_model.create({ + 'order_id': self.purchase_order.id, + 'product_id': self.product_1.id, 'date_planned': fields.Datetime.now(), 'name': 'Test', 'product_qty': 1.0, - 'product_uom': cls.product_1.uom_id.id, + 'product_uom': self.product_1.uom_id.id, 'discount': 50.0, 'price_unit': 10.0, }) - cls.tax = cls.env['account.tax'].create({ + self.tax = self.env['account.tax'].create({ 'name': 'Sample tax 15%', 'amount_type': 'percent', 'type_tax_use': 'purchase', 'amount': 15.0, }) - cls.po_line_2 = po_model.create({ - 'order_id': cls.purchase_order.id, - 'product_id': cls.product_2.id, + self.po_line_2 = po_model.create({ + 'order_id': self.purchase_order.id, + 'product_id': self.product_2.id, 'date_planned': fields.Datetime.now(), 'name': 'Test', 'product_qty': 10.0, - 'product_uom': cls.product_2.uom_id.id, + 'product_uom': self.product_2.uom_id.id, 'discount': 30, - 'taxes_id': [(6, 0, [cls.tax.id])], + 'taxes_id': [(6, 0, [self.tax.id])], 'price_unit': 230.0, }) - cls.po_line_3 = po_model.create({ - 'order_id': cls.purchase_order.id, - 'product_id': cls.product_2.id, + self.po_line_3 = po_model.create({ + 'order_id': self.purchase_order.id, + 'product_id': self.product_2.id, 'date_planned': fields.Datetime.now(), 'name': 'Test', 'product_qty': 1.0, - 'product_uom': cls.product_2.uom_id.id, + 'product_uom': self.product_2.uom_id.id, 'discount': 0, - 'taxes_id': [(6, 0, [cls.tax.id])], + 'taxes_id': [(6, 0, [self.tax.id])], 'price_unit': 10.0, }) @@ -82,3 +86,9 @@ def test_report_price_unit(self): ]) self.assertEqual(rec.price_total, 5) self.assertEqual(rec.discount, 50) + + +class TestPurchaseOrderRoundGlobally(TestPurchaseOrder): + def setUp(self): + super(TestPurchaseOrderRoundGlobally, self).setUp() + self.company.tax_calculation_rounding_method = 'round_globally' diff --git a/purchase_order_analytic_search/README.rst b/purchase_order_analytic_search/README.rst new file mode 100644 index 00000000000..659089c8a90 --- /dev/null +++ b/purchase_order_analytic_search/README.rst @@ -0,0 +1,68 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +============================== +Purchase Order Analytic Search +============================== + +Organizations often require to quickly find the purchase orders associated to +an analytic account, searching by it's code, name or project/account manager. + +This module introduces the possibility to search purchase orders by analytic +account. + +It also introduces a new menu entry in Purchasing to list purchase order lines. + + +Usage +===== + +To use this module, you need to: + +#. Set 'Analytic Accounting' and 'Analytic Accounting for +Purchases' user rights. + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/142/10.0 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Gisela Mora +* Serpent Consulting Services Pvt. Ltd. +* Alexandre Fayolle + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/purchase_order_analytic_search/__init__.py b/purchase_order_analytic_search/__init__.py new file mode 100644 index 00000000000..fe8596a2aaa --- /dev/null +++ b/purchase_order_analytic_search/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-17 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import models diff --git a/purchase_order_analytic_search/__manifest__.py b/purchase_order_analytic_search/__manifest__.py new file mode 100644 index 00000000000..8a7cf9c2f9e --- /dev/null +++ b/purchase_order_analytic_search/__manifest__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-17 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Purchase Order Analytic Search", + "summary": """Search purchase orders by analytic account. New menu entry in + Purchasing to list purchase order lines.""", + "version": "10.0.1.0.0", + "website": "https://odoo-community.org/", + "category": "Purchase Workflow", + "author": "Eficent, Camptocamp, Odoo Community Association (OCA)", + "license": "LGPL-3", + "installable": True, + "depends": [ + "analytic", + "purchase" + ], + "data": [ + "views/purchase_order_view.xml" + ], +} diff --git a/purchase_order_analytic_search/models/__init__.py b/purchase_order_analytic_search/models/__init__.py new file mode 100644 index 00000000000..076e65593af --- /dev/null +++ b/purchase_order_analytic_search/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-17 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import purchase_order diff --git a/purchase_order_analytic_search/models/purchase_order.py b/purchase_order_analytic_search/models/purchase_order.py new file mode 100644 index 00000000000..ff16bcad519 --- /dev/null +++ b/purchase_order_analytic_search/models/purchase_order.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 Eficent Business and IT Consulting Services S.L. +# Copyright 2018 Camptocamp +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo import api, fields, models + + +class PurchaseOrder(models.Model): + + _inherit = "purchase.order" + + @api.multi + @api.depends('order_line.account_analytic_id') + def _compute_analytic_accounts(self): + for purchase in self: + purchase.account_analytic_ids = purchase.mapped( + 'order_line.account_analytic_id' + ) + + @api.model + def _search_analytic_accounts(self, operator, value): + po_line_obj = self.env['purchase.order.line'] + if not value: + return [('id', '=', False)] + if isinstance(value, (tuple, list)): + # we are searching on a list of ids + domain = [('order_id', '!=', False), + ('account_analytic_id', 'in', value)] + else: + if isinstance(value, int): + # we are searching on the id of the analytic_account + domain = [('order_id', '!=', False), + ('account_analytic_id', '=', value)] + else: + # assume we are searching on the analytic account name + domain = [('order_id', '!=', False), + ('account_analytic_id.name', 'like', value), + ] + po_lines = po_line_obj.search(domain) + orders = po_lines.mapped('order_id') + if operator in ('!=', 'not in'): + return [('id', 'not in', orders.ids)] + else: + return [('id', 'in', orders.ids)] + + account_analytic_ids = fields.Many2many( + comodel_name='account.analytic.account', + string='Analytic Account', + compute='_compute_analytic_accounts', + search='_search_analytic_accounts', + readonly=True + ) diff --git a/purchase_order_analytic_search/static/description/icon.png b/purchase_order_analytic_search/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/purchase_order_analytic_search/static/description/icon.png differ diff --git a/purchase_order_analytic_search/tests/__init__.py b/purchase_order_analytic_search/tests/__init__.py new file mode 100644 index 00000000000..9e998982197 --- /dev/null +++ b/purchase_order_analytic_search/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-17 Eficent Business and IT Consulting Services S.L. +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from . import test_analytic_search diff --git a/purchase_order_analytic_search/tests/test_analytic_search.py b/purchase_order_analytic_search/tests/test_analytic_search.py new file mode 100644 index 00000000000..50676a31f61 --- /dev/null +++ b/purchase_order_analytic_search/tests/test_analytic_search.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# Copyright 2015-17 Eficent Business and IT Consulting Services S.L. +# Copyright 2018 Camptocamp +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +from odoo.tests.common import SavepointCase +from odoo.fields import Datetime + + +class TestAnalyticSearch(SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestAnalyticSearch, cls).setUpClass() + cls.purchase_order_model = cls.env['purchase.order'] + partner_model = cls.env['res.partner'] + prod_model = cls.env['product.product'] + analytic_account_model = cls.env['account.analytic.account'] + cls.product_uom_model = cls.env['product.uom'] + + pa_dict = { + 'name': 'Partner 1', + 'supplier': True, + } + cls.partner = partner_model.create(pa_dict) + uom_id = cls.product_uom_model.search([ + ('name', '=', 'Unit(s)')])[0].id + pr_dict = { + 'name': 'Product Test', + 'uom_id': uom_id, + } + cls.product = prod_model.create(pr_dict) + ac_dict = { + 'name': 'account 1', + } + cls.analytic_account_1 = \ + analytic_account_model.create(ac_dict) + ac_dict = { + 'name': 'dummyname', + } + cls.analytic_account_2 = \ + analytic_account_model.create(ac_dict) + # PURCHASE ORDER NUM 1 => account 1 + po_dict = { + 'partner_id': cls.partner.id, + 'order_line': [ + (0, 0, { + 'date_planned': Datetime.now(), + 'name': 'PO01', + 'product_id': cls.product.id, + 'product_uom': uom_id, + 'price_unit': 1, + 'product_qty': 5.0, + 'account_analytic_id': cls.analytic_account_1.id, + }), + (0, 0, { + 'date_planned': Datetime.now(), + 'name': 'PO01', + 'product_id': cls.product.id, + 'product_uom': uom_id, + 'price_unit': 1, + 'product_qty': 5.0, + 'account_analytic_id': cls.analytic_account_1.id, + })], + } + cls.purchase_order_1 = cls.purchase_order_model.create(po_dict) + + # PURCHASE ORDER NUM 2 => account 1 and 2 + pa_dict2 = { + 'name': 'Partner 2', + 'supplier': True, + } + cls.partner2 = partner_model.create(pa_dict2) + po_dict2 = { + 'partner_id': cls.partner2.id, + 'order_line': [ + (0, 0, { + 'date_planned': Datetime.now(), + 'name': 'PO01', + 'product_id': cls.product.id, + 'product_uom': uom_id, + 'price_unit': 1, + 'product_qty': 5.0, + 'account_analytic_id': cls.analytic_account_1.id, + }), + (0, 0, { + 'date_planned': Datetime.now(), + 'name': 'PO01', + 'product_id': cls.product.id, + 'product_uom': uom_id, + 'price_unit': 1, + 'product_qty': 5.0, + 'account_analytic_id': cls.analytic_account_2.id, + }), + ] + } + cls.purchase_order_2 = cls.purchase_order_model.create(po_dict2) + # PURCHASE ORDER NUM 3 => account 2 + po_dict3 = { + 'partner_id': cls.partner2.id, + 'order_line': [ + (0, 0, { + 'date_planned': Datetime.now(), + 'name': 'PO01', + 'product_id': cls.product.id, + 'product_uom': uom_id, + 'price_unit': 1, + 'product_qty': 5.0, + 'account_analytic_id': cls.analytic_account_2.id, + }), + (0, 0, { + 'date_planned': Datetime.now(), + 'name': 'PO01', + 'product_id': cls.product.id, + 'product_uom': uom_id, + 'price_unit': 1, + 'product_qty': 5.0, + 'account_analytic_id': cls.analytic_account_2.id, + }), + ] + } + + cls.purchase_order_3 = cls.purchase_order_model.create(po_dict3) + + def test_filter_analytic_accounts(self): + found = self.purchase_order_model.search([ + ('account_analytic_ids', '=', self.analytic_account_1.id)]) + self.assertEqual( + found, + self.purchase_order_1 + self.purchase_order_2 + ) + + def test_filter_analytic_accounts_by_name(self): + found = self.purchase_order_model.search([ + ('account_analytic_ids', '=', 'nt 1')]) + self.assertEqual( + found, + self.purchase_order_1 + self.purchase_order_2 + ) + + def test_filter_analytic_accounts_not_equal(self): + found = self.purchase_order_model.search([ + ('account_analytic_ids', '!=', self.analytic_account_1.id)]) + self.assertTrue( + self.purchase_order_3 in found + ) + self.assertTrue( + self.purchase_order_1 not in found + ) + self.assertTrue( + self.purchase_order_2 not in found + ) + + def test_compute_analytic_accounts(self): + self.assertEqual(self.purchase_order_1.account_analytic_ids, + self.analytic_account_1) + self.assertEqual(self.purchase_order_2.account_analytic_ids, + self.analytic_account_1 + self.analytic_account_2) diff --git a/purchase_order_analytic_search/views/purchase_order_view.xml b/purchase_order_analytic_search/views/purchase_order_view.xml new file mode 100644 index 00000000000..31de0aba554 --- /dev/null +++ b/purchase_order_analytic_search/views/purchase_order_view.xml @@ -0,0 +1,68 @@ + + + +#--------------------------------------------------------------------------------------------------------- +# Add analytic account id to purchase orders +#--------------------------------------------------------------------------------------------------------- + + + purchase.order.list.select + purchase.order + + + + + + + + + + purchase.order.line.tree + purchase.order.line + + + + + + + + + + + purchase.order.line.search + purchase.order.line + + + + + + + + +#--------------------------------------------------------------------------------------------------------- +# Extend menus +#--------------------------------------------------------------------------------------------------------- + + + Purchase Order Lines + ir.actions.act_window + purchase.order.line + form + tree,form + + + + + + tree + + + + + + +