mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
Migrate stock_reserve_rule to 13.0
This commit is contained in:
committed by
Sébastien Alix
parent
fdab54b432
commit
dfc97bb24d
@@ -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',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user