stock_available_unreserved: allow searching by unreserved quantities

This commit is contained in:
Atte Isopuro
2018-04-09 12:22:14 +03:00
committed by Lois Rilo
parent 30a14dab0f
commit 9858cd5d4b
4 changed files with 198 additions and 1 deletions

View File

@@ -48,6 +48,7 @@ Contributors
* Jordi Ballester Alomar <jordi.ballester@eficent.com>
* Stefan Rijnhart <stefan@opener.amsterdam>
* Mykhailo Panarin <m.panarin@mobilunity.com>
* Atte Isopuro <atte.isopuro@avoin.systems>
Maintainer

View File

@@ -4,9 +4,11 @@
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.addons.stock.models.product import OPERATORS
from odoo.tools.float_utils import float_round
from odoo.exceptions import UserError
UNIT = dp.get_precision('Product Unit of Measure')
@@ -18,6 +20,7 @@ class ProductTemplate(models.Model):
string='Quantity On Hand Unreserved',
digits=UNIT,
compute='_compute_product_available_not_res',
search='_search_quantity_unreserved',
)
@api.multi
@@ -47,6 +50,11 @@ class ProductTemplate(models.Model):
}
return result
def _search_quantity_unreserved(self, operator, value):
domain = [('qty_available_not_res', operator, value)]
product_variant_ids = self.env['product.product'].search(domain)
return [('product_variant_ids', 'in', product_variant_ids.ids)]
class ProductProduct(models.Model):
_inherit = 'product.product'
@@ -55,6 +63,7 @@ class ProductProduct(models.Model):
string='Qty Available Not Reserved',
digits=UNIT,
compute='_compute_qty_available_not_reserved',
search="_search_quantity_unreserved",
)
@api.multi
@@ -101,3 +110,15 @@ class ProductProduct(models.Model):
qty = res[prod.id]['qty_available_not_res']
prod.qty_available_not_res = qty
return res
def _search_quantity_unreserved(self, operator, value):
if operator not in OPERATORS:
raise UserError(_('Invalid domain operator %s') % operator)
if not isinstance(value, (float, int)):
raise UserError(_('Invalid domain right operand %s') % value)
ids = []
for product in self.search([]):
if OPERATORS[operator](product.qty_available_not_res, value):
ids.append(product.id)
return [('id', 'in', ids)]

View File

@@ -43,6 +43,8 @@ class TestStockLogisticsWarehouse(SavepointCase):
'uom_id': cls.uom_unit.id,
})
cls.productC = cls.templateAB.product_variant_ids
# Create product A and B
cls.productA = cls.productObj.create({
'name': 'product A',
@@ -175,3 +177,165 @@ class TestStockLogisticsWarehouse(SavepointCase):
'product_id': self.productA.id,
'quantity': 60.0})
self.compare_qty_available_not_res(self.productA, 80)
def check_variants_found_correctly(self, operator, value, expected):
domain = [('id', 'in', self.templateAB.product_variant_ids.ids)]
return self.check_found_correctly(self.env['product.product'],
domain, operator, value, expected)
def check_template_found_correctly(self, operator, value, expected):
# There may be other products already in the system: ignore those
domain = [('id', 'in', self.templateAB.ids)]
return self.check_found_correctly(self.env['product.template'],
domain, operator, value, expected)
def check_found_correctly(self, model, domain, operator, value, expected):
found = model.search(domain + [
('qty_available_not_res', operator, value)]
)
if found != expected:
self.fail(
"Searching for products failed: search for unreserved "
"quantity {operator} {value}; expected to find "
"{expected}, but found {found}".format(
operator=operator,
value=value,
expected=expected or "no products",
found=found,
)
)
def test_stock_search(self):
all_variants = self.templateAB.product_variant_ids
a_and_b = self.productA + self.productB
b_and_c = self.productB + self.productC
a_and_c = self.productA + self.productC
no_variants = self.env['product.product']
no_template = self.env['product.template']
# Start: one template with three variants.
# All variants have zero unreserved stock
self.check_variants_found_correctly('=', 0, all_variants)
self.check_variants_found_correctly('>=', 0, all_variants)
self.check_variants_found_correctly('<=', 0, all_variants)
self.check_variants_found_correctly('>', 0, no_variants)
self.check_variants_found_correctly('<', 0, no_variants)
self.check_variants_found_correctly('!=', 0, no_variants)
self.check_template_found_correctly('=', 0, self.templateAB)
self.check_template_found_correctly('>=', 0, self.templateAB)
self.check_template_found_correctly('<=', 0, self.templateAB)
self.check_template_found_correctly('>', 0, no_template)
self.check_template_found_correctly('<', 0, no_template)
self.check_template_found_correctly('!=', 0, no_template)
self.pickingInA.action_confirm()
# All variants still have zero unreserved stock
self.check_variants_found_correctly('=', 0, all_variants)
self.check_variants_found_correctly('>=', 0, all_variants)
self.check_variants_found_correctly('<=', 0, all_variants)
self.check_variants_found_correctly('>', 0, no_variants)
self.check_variants_found_correctly('<', 0, no_variants)
self.check_variants_found_correctly('!=', 0, no_variants)
self.check_template_found_correctly('=', 0, self.templateAB)
self.check_template_found_correctly('>=', 0, self.templateAB)
self.check_template_found_correctly('<=', 0, self.templateAB)
self.check_template_found_correctly('>', 0, no_template)
self.check_template_found_correctly('<', 0, no_template)
self.check_template_found_correctly('!=', 0, no_template)
self.pickingInA.action_assign()
# All variants still have zero unreserved stock
self.check_variants_found_correctly('=', 0, all_variants)
self.check_variants_found_correctly('>=', 0, all_variants)
self.check_variants_found_correctly('<=', 0, all_variants)
self.check_variants_found_correctly('>', 0, no_variants)
self.check_variants_found_correctly('<', 0, no_variants)
self.check_variants_found_correctly('!=', 0, no_variants)
self.check_template_found_correctly('=', 0, self.templateAB)
self.check_template_found_correctly('>=', 0, self.templateAB)
self.check_template_found_correctly('<=', 0, self.templateAB)
self.check_template_found_correctly('>', 0, no_template)
self.check_template_found_correctly('<', 0, no_template)
self.check_template_found_correctly('!=', 0, no_template)
self.pickingInA.button_validate()
# product A has 2 unreserved stock, other variants have 0
self.check_variants_found_correctly('=', 2, self.productA)
self.check_variants_found_correctly('=', 0, b_and_c)
self.check_variants_found_correctly('>', 0, self.productA)
self.check_variants_found_correctly('<', 0, no_variants)
self.check_variants_found_correctly('!=', 0, self.productA)
self.check_variants_found_correctly('!=', 1, all_variants)
self.check_variants_found_correctly('!=', 2, b_and_c)
self.check_variants_found_correctly('<=', 0, b_and_c)
self.check_variants_found_correctly('<=', 1, b_and_c)
self.check_variants_found_correctly('>=', 0, all_variants)
self.check_variants_found_correctly('>=', 1, self.productA)
self.check_template_found_correctly('=', 0, self.templateAB)
self.check_template_found_correctly('=', 1, no_template)
self.check_template_found_correctly('=', 2, self.templateAB)
self.check_template_found_correctly('!=', 0, self.templateAB)
self.check_template_found_correctly('!=', 1, self.templateAB)
self.check_template_found_correctly('!=', 2, self.templateAB)
self.check_template_found_correctly('>', -1, self.templateAB)
self.check_template_found_correctly('>', 0, self.templateAB)
self.check_template_found_correctly('>', 1, self.templateAB)
self.check_template_found_correctly('>', 2, no_template)
self.check_template_found_correctly('<', 3, self.templateAB)
self.check_template_found_correctly('<', 2, self.templateAB)
self.check_template_found_correctly('<', 1, self.templateAB)
self.check_template_found_correctly('<', 0, no_template)
self.check_template_found_correctly('>=', 0, self.templateAB)
self.check_template_found_correctly('>=', 1, self.templateAB)
self.check_template_found_correctly('>=', 2, self.templateAB)
self.check_template_found_correctly('>=', 3, no_template)
self.check_template_found_correctly('<=', 3, self.templateAB)
self.check_template_found_correctly('<=', 2, self.templateAB)
self.check_template_found_correctly('<=', 1, self.templateAB)
self.check_template_found_correctly('<=', 0, self.templateAB)
self.check_template_found_correctly('<=', -1, no_template)
self.pickingInB.action_done()
# product A has 2 unreserved, product B has 3 unreserved and
# the remaining variant has 0
self.check_variants_found_correctly('=', 2, self.productA)
self.check_variants_found_correctly('=', 3, self.productB)
self.check_variants_found_correctly('=', 0, self.productC)
self.check_variants_found_correctly('>', 0, a_and_b)
self.check_variants_found_correctly('<', 0, no_variants)
self.check_variants_found_correctly('!=', 0, a_and_b)
self.check_variants_found_correctly('!=', 1, all_variants)
self.check_variants_found_correctly('!=', 2, b_and_c)
self.check_variants_found_correctly('!=', 3, a_and_c)
self.check_variants_found_correctly('<=', 0, self.productC)
self.check_variants_found_correctly('<=', 1, self.productC)
self.check_variants_found_correctly('>=', 0, all_variants)
self.check_variants_found_correctly('>=', 1, a_and_b)
self.check_variants_found_correctly('>=', 2, a_and_b)
self.check_variants_found_correctly('>=', 3, self.productB)
self.check_variants_found_correctly('>=', 4, no_variants)
self.check_template_found_correctly('=', 0, self.templateAB)
self.check_template_found_correctly('=', 1, no_template)
self.check_template_found_correctly('=', 2, self.templateAB)
self.check_template_found_correctly('=', 3, self.templateAB)
self.check_template_found_correctly('!=', 0, self.templateAB)
self.check_template_found_correctly('!=', 2, self.templateAB)
self.check_template_found_correctly('!=', 3, self.templateAB)
self.check_template_found_correctly('>', 1, self.templateAB)
self.check_template_found_correctly('>', 2, self.templateAB)
# This part may seem a bit unintuitive, but this is the
# way it works in the Odoo core
# Searches are "deferred" to the variants, so while the template says
# it has a stock of 5, searching for a stock greater than 3 will not
# find anything because no singular variant has a higher stock
self.check_template_found_correctly('>', 3, no_template)
self.check_template_found_correctly('<', 3, self.templateAB)
self.check_template_found_correctly('<', 2, self.templateAB)
self.check_template_found_correctly('<', 1, self.templateAB)
self.check_template_found_correctly('<', 0, no_template)

View File

@@ -14,6 +14,17 @@
</field>
</record>
<record id="product_template_search_form_view_stock" model="ir.ui.view">
<field name="name">product.template.search.stock.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.product_template_search_form_view_stock"/>
<field name="arch" type="xml">
<filter name="real_stock_available" position="after">
<filter name="real_stock_unreserved" string="Reservable Products" domain="[('qty_available_not_res','&gt;',0)]"/>
</filter>
</field>
</record>
<record model="ir.ui.view" id="product_template_kanban_stock_view">
<field name="name">Product Template Kanban Stock</field>
<field name="model">product.template</field>