Migrate stock_reserve_rule to 13.0

This commit is contained in:
Guewen Baconnier
2019-12-16 08:30:12 +01:00
committed by Sébastien Alix
parent fdab54b432
commit dfc97bb24d
8 changed files with 63 additions and 75 deletions

View File

@@ -1,29 +1,26 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Stock Reservation Rules', "name": "Stock Reservation Rules",
'summary': 'Configure reservation rules by location', "summary": "Configure reservation rules by location",
'version': '12.0.1.0.0', "version": "13.0.1.0.0",
'author': "Camptocamp, Odoo Community Association (OCA)", "author": "Camptocamp, Odoo Community Association (OCA)",
'website': "https://github.com/OCA/stock-logistics-warehouse", "website": "https://github.com/OCA/stock-logistics-warehouse",
'category': 'Stock Management', "category": "Stock Management",
'depends': [ "depends": ["stock", "product_packaging_type"], # OCA/product-attribute
'stock', "demo": [
'product_packaging_type', # OCA/product-attribute "demo/product_demo.xml",
"demo/stock_location_demo.xml",
"demo/stock_reserve_rule_demo.xml",
"demo/stock_inventory_demo.xml",
"demo/stock_picking_demo.xml",
], ],
'demo': [ "data": [
'demo/product_demo.xml', "views/stock_reserve_rule_views.xml",
'demo/stock_location_demo.xml', "security/ir.model.access.csv",
'demo/stock_reserve_rule_demo.xml', "security/stock_reserve_rule_security.xml",
'demo/stock_inventory_demo.xml',
'demo/stock_picking_demo.xml',
], ],
'data': [ "installable": True,
'views/stock_reserve_rule_views.xml', "development_status": "Alpha",
'security/ir.model.access.csv', "license": "AGPL-3",
'security/stock_reserve_rule_security.xml',
],
'installable': True,
'development_status': 'Alpha',
'license': 'AGPL-3',
} }

View File

@@ -12,7 +12,6 @@
<field name="tracking">none</field> <field name="tracking">none</field>
<field name="uom_id" ref="uom.product_uom_unit"/> <field name="uom_id" ref="uom.product_uom_unit"/>
<field name="uom_po_id" ref="uom.product_uom_unit"/> <field name="uom_po_id" ref="uom.product_uom_unit"/>
<field name="property_stock_inventory" ref="stock.location_inventory"/>
</record> </record>
</odoo> </odoo>

View File

@@ -27,8 +27,11 @@
<field name="location_id" ref="stock_location_zone_c_bin_1_demo"/> <field name="location_id" ref="stock_location_zone_c_bin_1_demo"/>
</record> </record>
<function model="stock.inventory" name="action_validate"> <function model="stock.inventory" name="_action_start">
<function eval="[[('state','=','draft'),('id', '=', ref('stock_reserve_rule.stock_inventory_1_demo'))]]" model="stock.inventory" name="search"/> <function eval="[[('state','=','draft'),('id', '=', ref('stock_reserve_rule.stock_inventory_1_demo'))]]" model="stock.inventory" name="search"/>
</function> </function>
<function model="stock.inventory" name="action_validate">
<function eval="[[('state','=','confirm'),('id', '=', ref('stock_reserve_rule.stock_inventory_1_demo'))]]" model="stock.inventory" name="search"/>
</function>
</odoo> </odoo>

View File

@@ -84,8 +84,7 @@ class StockMove(models.Model):
break break
need_zero = ( need_zero = (
float_compare(still_need, 0, precision_rounding=rounding) float_compare(still_need, 0, precision_rounding=rounding) != 1
!= 1
) )
if need_zero: if need_zero:
# useless to eval the other rules when still_need <= 0 # useless to eval the other rules when still_need <= 0

View File

@@ -27,4 +27,4 @@ class StockQuant(models.Model):
seen[location] = seen[location] | quant seen[location] = seen[location] | quant
else: else:
seen[location] = quant seen[location] = quant
return [(location, quants) for location, quants in seen.items()] return [(loc, quants) for loc, quants in seen.items()]

View File

@@ -1,9 +1,9 @@
# Copyright 2019 Camptocamp SA # Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models from odoo import fields, models
from odoo.tools.safe_eval import safe_eval
from odoo.osv import expression from odoo.osv import expression
from odoo.tools.float_utils import float_compare from odoo.tools.float_utils import float_compare
from odoo.tools.safe_eval import safe_eval
def _default_sequence(record): def _default_sequence(record):
@@ -33,8 +33,7 @@ class StockReserveRule(models.Model):
sequence = fields.Integer(default=lambda s: _default_sequence(s)) sequence = fields.Integer(default=lambda s: _default_sequence(s))
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
company_id = fields.Many2one( company_id = fields.Many2one(
comodel_name="res.company", comodel_name="res.company", default=lambda self: self.env.user.company_id.id
default=lambda self: self.env.user.company_id.id,
) )
location_id = fields.Many2one(comodel_name="stock.location", required=True) location_id = fields.Many2one(comodel_name="stock.location", required=True)
@@ -143,9 +142,7 @@ class StockReserveRuleRemoval(models.Model):
def _eval_quant_domain(self, quants, domain): def _eval_quant_domain(self, quants, domain):
quant_domain = [("id", "in", quants.ids)] quant_domain = [("id", "in", quants.ids)]
return self.env["stock.quant"].search( return self.env["stock.quant"].search(expression.AND([quant_domain, domain]))
expression.AND([quant_domain, domain])
)
def _filter_quants(self, move, quants): def _filter_quants(self, move, quants):
domain = safe_eval(self.quant_domain) or [] domain = safe_eval(self.quant_domain) or []
@@ -206,7 +203,6 @@ class StockReserveRuleRemoval(models.Model):
( (
sum(quants.mapped("quantity")) sum(quants.mapped("quantity"))
- sum(quants.mapped("reserved_quantity")), - sum(quants.mapped("reserved_quantity")),
quants,
location, location,
) )
for location, quants in quants_per_bin for location, quants in quants_per_bin
@@ -217,7 +213,7 @@ class StockReserveRuleRemoval(models.Model):
# Propose the largest quants first, so we have as less operations # Propose the largest quants first, so we have as less operations
# as possible. We take goods only if we empty the bin. # as possible. We take goods only if we empty the bin.
rounding = fields.first(quants).product_id.uom_id.rounding rounding = fields.first(quants).product_id.uom_id.rounding
for location_quantity, quants, location in bins: for location_quantity, location in bins:
if location_quantity <= 0: if location_quantity <= 0:
continue continue
@@ -253,9 +249,7 @@ class StockReserveRuleRemoval(models.Model):
rounding = product.uom_id.rounding rounding = product.uom_id.rounding
def is_greater_eq(value, other): def is_greater_eq(value, other):
return ( return float_compare(value, other, precision_rounding=rounding) >= 0
float_compare(value, other, precision_rounding=rounding) >= 0
)
for pack_quantity in packaging_quantities: for pack_quantity in packaging_quantities:
# Get quants quantity on each loop because they may change. # Get quants quantity on each loop because they may change.
@@ -266,7 +260,6 @@ class StockReserveRuleRemoval(models.Model):
( (
sum(quants.mapped("quantity")) sum(quants.mapped("quantity"))
- sum(quants.mapped("reserved_quantity")), - sum(quants.mapped("reserved_quantity")),
quants,
location, location,
) )
for location, quants in quants_per_bin for location, quants in quants_per_bin
@@ -274,12 +267,10 @@ class StockReserveRuleRemoval(models.Model):
reverse=True, reverse=True,
) )
for location_quantity, quants, location in bins: for location_quantity, location in bins:
if location_quantity <= 0: if location_quantity <= 0:
continue continue
enough_for_packaging = is_greater_eq( enough_for_packaging = is_greater_eq(location_quantity, pack_quantity)
location_quantity, pack_quantity
)
asked_more_than_packaging = is_greater_eq(need, pack_quantity) asked_more_than_packaging = is_greater_eq(need, pack_quantity)
if enough_for_packaging and asked_more_than_packaging: if enough_for_packaging and asked_more_than_packaging:
# compute how much packaging we can get # compute how much packaging we can get

View File

@@ -101,9 +101,7 @@ class TestReserveRule(common.SavepointCase):
return picking return picking
def _update_qty_in_location(self, location, product, quantity): def _update_qty_in_location(self, location, product, quantity):
self.env["stock.quant"]._update_available_quantity( self.env["stock.quant"]._update_available_quantity(product, location, quantity)
product, location, quantity
)
def _create_rule(self, rule_values, removal_values): def _create_rule(self, rule_values, removal_values):
rule_config = { rule_config = {
@@ -113,6 +111,8 @@ class TestReserveRule(common.SavepointCase):
} }
rule_config.update(rule_values) rule_config.update(rule_values)
self.env["stock.reserve.rule"].create(rule_config) self.env["stock.reserve.rule"].create(rule_config)
# workaround for https://github.com/odoo/odoo/pull/41900
self.env["stock.reserve.rule"].invalidate_cache()
def _setup_packagings(self, product, packagings): def _setup_packagings(self, product, packagings):
"""Create packagings on a product """Create packagings on a product
@@ -219,7 +219,7 @@ class TestReserveRule(common.SavepointCase):
], ],
) )
self.assertEqual(move.state, "partially_available") self.assertEqual(move.state, "partially_available")
self.assertEqual(move.reserved_availability, 300.) self.assertEqual(move.reserved_availability, 300.0)
def test_rule_fallback(self): def test_rule_fallback(self):
reserve = self.env["stock.location"].create( reserve = self.env["stock.location"].create(
@@ -254,7 +254,7 @@ class TestReserveRule(common.SavepointCase):
], ],
) )
self.assertEqual(move.state, "assigned") self.assertEqual(move.state, "assigned")
self.assertEqual(move.reserved_availability, 400.) self.assertEqual(move.reserved_availability, 400.0)
def test_rule_domain(self): def test_rule_domain(self):
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100) self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 100)
@@ -355,9 +355,9 @@ class TestReserveRule(common.SavepointCase):
self.assertRecordValues( self.assertRecordValues(
ml, ml,
[ [
{"location_id": self.loc_zone1_bin2.id, "product_qty": 150.}, {"location_id": self.loc_zone1_bin2.id, "product_qty": 150.0},
{"location_id": self.loc_zone2_bin1.id, "product_qty": 50.}, {"location_id": self.loc_zone2_bin1.id, "product_qty": 50.0},
{"location_id": self.loc_zone3_bin1.id, "product_qty": 50.}, {"location_id": self.loc_zone3_bin1.id, "product_qty": 50.0},
], ],
) )
self.assertEqual(move.state, "assigned") self.assertEqual(move.state, "assigned")
@@ -389,8 +389,8 @@ class TestReserveRule(common.SavepointCase):
self.assertRecordValues( self.assertRecordValues(
ml, ml,
[ [
{"location_id": self.loc_zone1_bin1.id, "product_qty": 50.}, {"location_id": self.loc_zone1_bin1.id, "product_qty": 50.0},
{"location_id": self.loc_zone2_bin1.id, "product_qty": 30.}, {"location_id": self.loc_zone2_bin1.id, "product_qty": 30.0},
], ],
) )
self.assertEqual(move.state, "assigned") self.assertEqual(move.state, "assigned")
@@ -424,8 +424,8 @@ class TestReserveRule(common.SavepointCase):
self.assertRecordValues( self.assertRecordValues(
ml, ml,
[ [
{"location_id": self.loc_zone1_bin2.id, "product_qty": 60.}, {"location_id": self.loc_zone1_bin2.id, "product_qty": 60.0},
{"location_id": self.loc_zone2_bin1.id, "product_qty": 20.}, {"location_id": self.loc_zone2_bin1.id, "product_qty": 20.0},
], ],
) )
self.assertEqual(move.state, "assigned") self.assertEqual(move.state, "assigned")
@@ -433,10 +433,7 @@ class TestReserveRule(common.SavepointCase):
def test_rule_packaging(self): def test_rule_packaging(self):
self._setup_packagings( self._setup_packagings(
self.product1, self.product1,
[ [("Pallet", 500, self.pallet), ("Retail Box", 50, self.retail_box)],
("Pallet", 500, self.pallet),
("Retail Box", 50, self.retail_box),
],
) )
self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 40) self._update_qty_in_location(self.loc_zone1_bin1, self.product1, 40)
self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 510) self._update_qty_in_location(self.loc_zone1_bin2, self.product1, 510)
@@ -470,9 +467,9 @@ class TestReserveRule(common.SavepointCase):
self.assertRecordValues( self.assertRecordValues(
ml, ml,
[ [
{"location_id": self.loc_zone1_bin2.id, "product_qty": 500.}, {"location_id": self.loc_zone1_bin2.id, "product_qty": 500.0},
{"location_id": self.loc_zone2_bin1.id, "product_qty": 50.}, {"location_id": self.loc_zone2_bin1.id, "product_qty": 50.0},
{"location_id": self.loc_zone3_bin1.id, "product_qty": 40.}, {"location_id": self.loc_zone3_bin1.id, "product_qty": 40.0},
], ],
) )
self.assertEqual(move.state, "assigned") self.assertEqual(move.state, "assigned")
@@ -553,9 +550,9 @@ class TestReserveRule(common.SavepointCase):
self.assertRecordValues( self.assertRecordValues(
ml, ml,
[ [
{"location_id": self.loc_zone1_bin2.id, "product_qty": 500.}, {"location_id": self.loc_zone1_bin2.id, "product_qty": 500.0},
{"location_id": self.loc_zone2_bin2.id, "product_qty": 50.}, {"location_id": self.loc_zone2_bin2.id, "product_qty": 50.0},
{"location_id": self.loc_zone3_bin1.id, "product_qty": 10.}, {"location_id": self.loc_zone3_bin1.id, "product_qty": 10.0},
], ],
) )
self.assertEqual(move.state, "assigned") self.assertEqual(move.state, "assigned")

View File

@@ -7,17 +7,20 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Stock Reservation Rule"> <form string="Stock Reservation Rule">
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
</button>
</div> </div>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<label for="name" class="oe_edit_only"/> <label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1> <h1><field name="name"/></h1>
<group string="Rule Applicability" name="configuration"> <group string="Rule Applicability" name="configuration">
<field name="location_id"/> <group>
<field name="fallback_location_id"/> <field name="active" invisible="1"/>
<field name="rule_domain" widget="domain" options="{'model': 'stock.move'}" /> <field name="location_id"/>
<field name="company_id" groups="base.group_multi_company"/> <field name="fallback_location_id"/>
</group>
<group>
<field name="rule_domain" widget="domain" options="{'model': 'stock.move', 'in_dialog': true}" />
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group> </group>
<group string="Removal Rules" name="rule"> <group string="Removal Rules" name="rule">
<field name="rule_removal_ids" nolabel="1"> <field name="rule_removal_ids" nolabel="1">
@@ -36,7 +39,7 @@
widget="many2many_tags" widget="many2many_tags"
attrs="{'invisible': [('removal_strategy', '!=', 'packaging')]}" attrs="{'invisible': [('removal_strategy', '!=', 'packaging')]}"
/> />
<field name="quant_domain" widget="domain" options="{'model': 'stock.quant'}" /> <field name="quant_domain" widget="domain" options="{'model': 'stock.quant', 'in_dialog': true}" />
</group> </group>
</form> </form>
</field> </field>
@@ -74,7 +77,6 @@
<field name="name">Stock Reservation Rules</field> <field name="name">Stock Reservation Rules</field>
<field name="res_model">stock.reserve.rule</field> <field name="res_model">stock.reserve.rule</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_stock_reserve_rule_tree"/> <field name="view_id" ref="view_stock_reserve_rule_tree"/>
<field name="search_view_id" ref="view_stock_reserve_rule_search"/> <field name="search_view_id" ref="view_stock_reserve_rule_search"/>
<field name="context"></field> <field name="context"></field>