mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[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.
This commit is contained in:
@@ -72,6 +72,7 @@ Contributors
|
||||
|
||||
* Jordi Ballester (EFICENT) <jordi.ballester@eficent.com>.
|
||||
* Enric Tobella <etobella@creublanca.es>
|
||||
* Atte Isopuro <atte.isopuro@avoin.systems>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
26
stock_request/views/product.xml
Normal file
26
stock_request/views/product.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_variant_generate_stock_request_orders" model="ir.actions.server">
|
||||
<field name="name">Request Stock</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="state">code</field>
|
||||
<field name="model_id" ref="product.model_product_product"/>
|
||||
<field name="binding_model_id" ref="product.model_product_product"/>
|
||||
<field name="code">
|
||||
action = records.env['stock.request.order']._create_from_product_multiselect(records)
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_template_generate_stock_request_orders" model="ir.actions.server">
|
||||
<field name="name">Request Stock</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="state">code</field>
|
||||
<field name="model_id" ref="product.model_product_template"/>
|
||||
<field name="binding_model_id" ref="product.model_product_template"/>
|
||||
<field name="code">
|
||||
action = records.env['stock.request.order']._create_from_product_multiselect(records)
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user