From 45e4fd2d52f7437b08729dd2f857b43bc58ebc90 Mon Sep 17 00:00:00 2001 From: Atte Isopuro Date: Thu, 12 Apr 2018 13:47:28 +0300 Subject: [PATCH] [11.0][IMP] stock_request: create request order from products This commit allows the user to select multiple products from a product list view and to create a Stock Request Order with an action. If the user selects templates, all of those templates' variants will be added to the order. This is useful as it allows the user to filter products beforehand based on their stocks, and to create a request order for all those products whose stock is too low. --- stock_request/README.rst | 1 + stock_request/__manifest__.py | 3 +- stock_request/models/stock_request_order.py | 40 ++++++++- stock_request/tests/test_stock_request.py | 99 +++++++++++++++++++-- stock_request/views/product.xml | 26 ++++++ 5 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 stock_request/views/product.xml diff --git a/stock_request/README.rst b/stock_request/README.rst index 34182e2ed..518182be6 100644 --- a/stock_request/README.rst +++ b/stock_request/README.rst @@ -72,6 +72,7 @@ Contributors * Jordi Ballester (EFICENT) . * Enric Tobella +* Atte Isopuro Maintainer ---------- diff --git a/stock_request/__manifest__.py b/stock_request/__manifest__.py index e1461e73d..abdd8def7 100644 --- a/stock_request/__manifest__.py +++ b/stock_request/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Stock Request", "summary": "Internal request for stock", - "version": "11.0.2.1.0", + "version": "11.0.2.2.0", "license": "LGPL-3", "website": "https://github.com/stock-logistics-warehouse", "author": "Eficent, " @@ -16,6 +16,7 @@ "data": [ "security/stock_request_security.xml", "security/ir.model.access.csv", + "views/product.xml", "views/stock_request_views.xml", "views/stock_request_allocation_views.xml", "views/stock_move_views.xml", diff --git a/stock_request/models/stock_request_order.py b/stock_request/models/stock_request_order.py index 771cccfd8..a41239404 100644 --- a/stock_request/models/stock_request_order.py +++ b/stock_request/models/stock_request_order.py @@ -2,7 +2,7 @@ # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). from odoo import api, fields, models, _ -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import UserError, ValidationError, AccessError REQUEST_STATES = [ ('draft', 'Draft'), @@ -271,3 +271,41 @@ class StockRequestOrder(models.Model): raise ValidationError( _('The company of the stock request must match with ' 'that of the location.')) + + @api.model + def _create_from_product_multiselect(self, products): + if not products: + return False + if products._name not in ('product.product', 'product.template'): + raise ValidationError( + "This action only works in the context of products") + if products._name == 'product.template': + # search instead of mapped so we don't include archived variants + products = self.env['product.product'].search([ + ('product_tmpl_id', 'in', products.ids) + ]) + expected = self.default_get(['expected_date'])['expected_date'] + try: + order = self.env['stock.request.order'].create(dict( + expected_date=expected, + stock_request_ids=[(0, 0, dict( + product_id=product.id, + product_uom_id=product.uom_id.id, + product_uom_qty=0.0, + expected_date=expected, + )) for product in products] + )) + except AccessError: + # TODO: if there is a nice way to hide the action from the + # Action-menu if the user doesn't have the necessary rights, + # that would be a better way of doing this + raise UserError(_( + "Unfortunately it seems you do not have the necessary rights " + "for creating stock requests. Please contact your " + "administrator.")) + action = self.env.ref('stock_request.stock_request_order_action' + ).read()[0] + action['views'] = [( + self.env.ref('stock_request.stock_request_order_form').id, 'form')] + action['res_id'] = order.id + return action diff --git a/stock_request/tests/test_stock_request.py b/stock_request/tests/test_stock_request.py index b2999079e..2664590a5 100644 --- a/stock_request/tests/test_stock_request.py +++ b/stock_request/tests/test_stock_request.py @@ -3,6 +3,7 @@ from odoo.tests import common from odoo import fields, exceptions +from collections import Counter class TestStockRequest(common.TransactionCase): @@ -109,14 +110,15 @@ class TestStockRequest(common.TransactionCase): 'company_ids': [(6, 0, company_ids)] }) - def _create_product(self, default_code, name, company_id): - return self.env['product.product'].create({ - 'name': name, - 'default_code': default_code, - 'uom_id': self.env.ref('product.product_uom_unit').id, - 'company_id': company_id, - 'type': 'product', - }) + def _create_product(self, default_code, name, company_id, **vals): + return self.env['product.product'].create(dict( + name=name, + default_code=default_code, + uom_id=self.env.ref('product.product_uom_unit').id, + company_id=company_id, + type='product', + **vals + )) def test_defaults(self): vals = { @@ -735,3 +737,84 @@ class TestStockRequest(common.TransactionCase): # Cannot assign a route that belongs to another company with self.assertRaises(exceptions.ValidationError): stock_request.route_id = self.route_2 + + def test_stock_request_order_from_products(self): + product_A1 = self._create_product('CODEA1', 'Product A1', + self.main_company.id) + template_A = product_A1.product_tmpl_id + product_A2 = self._create_product( + 'CODEA2', 'Product A2', self.main_company.id, + product_tmpl_id=template_A.id) + product_A3 = self._create_product( + 'CODEA3', 'Product A3', self.main_company.id, + product_tmpl_id=template_A.id) + product_B1 = self._create_product('CODEB1', 'Product B1', + self.main_company.id) + template_B = product_B1.product_tmpl_id + # One archived variant of B + self._create_product( + 'CODEB2', 'Product B2', self.main_company.id, + product_tmpl_id=template_B.id, active=False) + Order = self.env['stock.request.order'] + + # Selecting some variants and creating an order + preexisting = Order.search([]) + wanted_products = product_A1 + product_A2 + product_B1 + action = Order._create_from_product_multiselect(wanted_products) + new_order = Order.search([]) - preexisting + self.assertEqual(len(new_order), 1) + self.assertEqual(action['res_id'], new_order.id, + msg="Returned action references the wrong record") + self.assertEqual( + Counter(wanted_products), + Counter(new_order.stock_request_ids.mapped('product_id')), + msg="Not all wanted products were ordered" + ) + + # Selecting a template and creating an order + preexisting = Order.search([]) + action = Order._create_from_product_multiselect(template_A) + new_order = Order.search([]) - preexisting + self.assertEqual(len(new_order), 1) + self.assertEqual(action['res_id'], new_order.id, + msg="Returned action references the wrong record") + self.assertEqual( + Counter(product_A1 + product_A2 + product_A3), + Counter(new_order.stock_request_ids.mapped('product_id')), + msg="Not all of the template's variants were ordered" + ) + + # Selecting a template + preexisting = Order.search([]) + action = Order._create_from_product_multiselect( + template_A + template_B) + new_order = Order.search([]) - preexisting + self.assertEqual(len(new_order), 1) + self.assertEqual(action['res_id'], new_order.id, + msg="Returned action references the wrong record") + self.assertEqual( + Counter(product_A1 + product_A2 + product_A3 + product_B1), + Counter(new_order.stock_request_ids.mapped('product_id')), + msg="Inactive variant was ordered though it shouldn't have been" + ) + + # If a user does not have stock request rights, they can still trigger + # the action from the products, so test that they get a friendlier + # error message. + self.stock_request_user.groups_id -= self.stock_request_user_group + with self.assertRaisesRegexp( + exceptions.UserError, + "Unfortunately it seems you do not have the necessary rights " + "for creating stock requests. Please contact your " + "administrator."): + Order.sudo( + self.stock_request_user + )._create_from_product_multiselect(template_A + template_B) + + # Empty recordsets should just return False + self.assertFalse(Order._create_from_product_multiselect( + self.env['product.product'])) + + # Wrong model should just raise ValidationError + with self.assertRaises(exceptions.ValidationError): + Order._create_from_product_multiselect(self.stock_request_user) diff --git a/stock_request/views/product.xml b/stock_request/views/product.xml new file mode 100644 index 000000000..ac60d5cf5 --- /dev/null +++ b/stock_request/views/product.xml @@ -0,0 +1,26 @@ + + + + + Request Stock + ir.actions.server + code + + + +action = records.env['stock.request.order']._create_from_product_multiselect(records) + + + + + Request Stock + ir.actions.server + code + + + +action = records.env['stock.request.order']._create_from_product_multiselect(records) + + + +