mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[MIG] stock_orderpoint_generator: Migration to 13.0
This commit is contained in:
committed by
sergiocorato
parent
8a23ede668
commit
1b95696248
@@ -5,7 +5,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Order point generator",
|
"name": "Order point generator",
|
||||||
"summary": "Mass configuration of stock order points",
|
"summary": "Mass configuration of stock order points",
|
||||||
"version": "12.0.1.0.0",
|
"version": "13.0.1.0.0",
|
||||||
"author": "Camptocamp, " "Tecnativa, " "Odoo Community Association (OCA)",
|
"author": "Camptocamp, " "Tecnativa, " "Odoo Community Association (OCA)",
|
||||||
"category": "Warehouse",
|
"category": "Warehouse",
|
||||||
"license": "AGPL-3",
|
"license": "AGPL-3",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2012-2016 Camptocamp SA
|
# Copyright 2012-2016 Camptocamp SA
|
||||||
# Copyright 2019 Tecnativa
|
# Copyright 2019 David Vidal - Tecnativa
|
||||||
|
# Copyright 2020 Víctor Martínez - Tecnativa
|
||||||
# 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).
|
||||||
|
|
||||||
|
|
||||||
@@ -157,6 +158,7 @@ class OrderpointTemplate(models.Model):
|
|||||||
to_date=record.auto_max_date_end,
|
to_date=record.auto_max_date_end,
|
||||||
criteria=record.auto_max_qty_criteria,
|
criteria=record.auto_max_qty_criteria,
|
||||||
)
|
)
|
||||||
|
vals_list = []
|
||||||
for data in record.copy_data():
|
for data in record.copy_data():
|
||||||
for discard_field in self._template_fields_to_discard():
|
for discard_field in self._template_fields_to_discard():
|
||||||
data.pop(discard_field)
|
data.pop(discard_field)
|
||||||
@@ -167,9 +169,9 @@ class OrderpointTemplate(models.Model):
|
|||||||
vals["product_min_qty"] = stock_min_qty.get(product_id.id, 0)
|
vals["product_min_qty"] = stock_min_qty.get(product_id.id, 0)
|
||||||
if record.auto_max_qty:
|
if record.auto_max_qty:
|
||||||
vals["product_max_qty"] = stock_max_qty.get(product_id.id, 0)
|
vals["product_max_qty"] = stock_max_qty.get(product_id.id, 0)
|
||||||
orderpoint_model.create(vals)
|
vals_list.append(vals)
|
||||||
|
orderpoint_model.create(vals_list)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def create_orderpoints(self, products):
|
def create_orderpoints(self, products):
|
||||||
""" Create orderpoint for *products* based on these templates.
|
""" Create orderpoint for *products* based on these templates.
|
||||||
:type products: recordset of products
|
:type products: recordset of products
|
||||||
@@ -177,7 +179,6 @@ class OrderpointTemplate(models.Model):
|
|||||||
self._disable_old_instances(products)
|
self._disable_old_instances(products)
|
||||||
self._create_instances(products)
|
self._create_instances(products)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def create_auto_orderpoints(self):
|
def create_auto_orderpoints(self):
|
||||||
for template in self:
|
for template in self:
|
||||||
if not template.auto_generate:
|
if not template.auto_generate:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2017 Camptocamp SA
|
# Copyright 2017 Camptocamp SA
|
||||||
# Copyright 2019 Tecnativa
|
# Copyright 2019 David Vidal - Tecnativa
|
||||||
|
# Copyright 2020 Víctor Martínez - Tecnativa
|
||||||
# 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 collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@@ -26,7 +27,6 @@ class ProductProduct(models.Model):
|
|||||||
record.auto_orderpoint_template_ids.create_orderpoints(record)
|
record.auto_orderpoint_template_ids.create_orderpoints(record)
|
||||||
return record
|
return record
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
result = super().write(vals)
|
result = super().write(vals)
|
||||||
if vals.get("auto_orderpoint_template_ids"):
|
if vals.get("auto_orderpoint_template_ids"):
|
||||||
@@ -34,45 +34,16 @@ class ProductProduct(models.Model):
|
|||||||
orderpoint_templates.create_orderpoints(self)
|
orderpoint_templates.create_orderpoints(self)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _compute_historic_quantities_dict(
|
def _get_stock_move_domain(self, domain_move=False, from_date=False, to_date=False):
|
||||||
self, location_id=False, from_date=False, to_date=False
|
domain = [("product_id", "in", self.ids), ("state", "=", "done")] + domain_move
|
||||||
):
|
|
||||||
"""Returns a dict of products with a dict of historic moves as for
|
|
||||||
a list of historic stock values resulting from those moves. If
|
|
||||||
a location_id is passed, we can restrict it to such location"""
|
|
||||||
location = location_id and location_id.id
|
|
||||||
domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self.with_context(
|
|
||||||
location=location
|
|
||||||
)._get_domain_locations()
|
|
||||||
if not to_date:
|
|
||||||
to_date = fields.Datetime.now()
|
|
||||||
domain_move_in = domain_move_out = [
|
|
||||||
("product_id", "in", self.ids),
|
|
||||||
("state", "=", "done"),
|
|
||||||
] + domain_move_in_loc
|
|
||||||
domain_move_out = [
|
|
||||||
("product_id", "in", self.ids),
|
|
||||||
("state", "=", "done"),
|
|
||||||
] + domain_move_out_loc
|
|
||||||
if from_date:
|
if from_date:
|
||||||
domain_move_in += [("date", ">=", from_date)]
|
domain += [("date", ">=", from_date)]
|
||||||
domain_move_out += [("date", ">=", from_date)]
|
domain += [("date", "<=", to_date)]
|
||||||
domain_move_in += [("date", "<=", to_date)]
|
return domain
|
||||||
domain_move_out += [("date", "<=", to_date)]
|
|
||||||
move_obj = self.env["stock.move"]
|
def _set_product_moves_dict(
|
||||||
# Positive moves
|
self, moves=False, location=False, from_date=False, to_date=False
|
||||||
moves_in = move_obj.search_read(
|
):
|
||||||
domain_move_in, ["product_id", "product_qty", "date"], order="date asc"
|
|
||||||
)
|
|
||||||
# We'll convert to negative these quantities to operate with them
|
|
||||||
# to obtain the stock snapshot in every moment
|
|
||||||
moves_out = move_obj.search_read(
|
|
||||||
domain_move_out, ["product_id", "product_qty", "date"], order="date asc"
|
|
||||||
)
|
|
||||||
for move in moves_out:
|
|
||||||
move["product_qty"] *= -1
|
|
||||||
# Merge both results and group them by product id as key
|
|
||||||
moves = moves_in + moves_out
|
|
||||||
# Obtain a dict with the stock snapshot for the relative date_from
|
# Obtain a dict with the stock snapshot for the relative date_from
|
||||||
# otherwise, the first move will counted as first stock value. We
|
# otherwise, the first move will counted as first stock value. We
|
||||||
# default the compute the stock value anyway to default the value
|
# default the compute the stock value anyway to default the value
|
||||||
@@ -85,7 +56,7 @@ class ProductProduct(models.Model):
|
|||||||
for move in moves:
|
for move in moves:
|
||||||
product_moves_dict.setdefault(move["product_id"][0], {})
|
product_moves_dict.setdefault(move["product_id"][0], {})
|
||||||
product_moves_dict[move["product_id"][0]].update(
|
product_moves_dict[move["product_id"][0]].update(
|
||||||
{move["date"]: {"prod_qty": move["product_qty"],}}
|
{move["date"]: {"prod_qty": move["product_qty"]}}
|
||||||
)
|
)
|
||||||
for product in self.with_context(prefetch_fields=False):
|
for product in self.with_context(prefetch_fields=False):
|
||||||
# If no there are no moves for a product we default the stock
|
# If no there are no moves for a product we default the stock
|
||||||
@@ -121,3 +92,37 @@ class ProductProduct(models.Model):
|
|||||||
v["stock"] for k, v in product_moves.items()
|
v["stock"] for k, v in product_moves.items()
|
||||||
]
|
]
|
||||||
return product_moves_dict
|
return product_moves_dict
|
||||||
|
|
||||||
|
def _compute_historic_quantities_dict(
|
||||||
|
self, location_id=False, from_date=False, to_date=False
|
||||||
|
):
|
||||||
|
"""Returns a dict of products with a dict of historic moves as for
|
||||||
|
a list of historic stock values resulting from those moves. If
|
||||||
|
a location_id is passed, we can restrict it to such location"""
|
||||||
|
location = location_id and location_id.id
|
||||||
|
domain_quant_loc, domain_move_in_loc, domain_move_out_loc = self.with_context(
|
||||||
|
location=location
|
||||||
|
)._get_domain_locations()
|
||||||
|
if not to_date:
|
||||||
|
to_date = fields.Datetime.now()
|
||||||
|
domain_move_in = self._get_stock_move_domain(
|
||||||
|
domain_move_in_loc, from_date, to_date
|
||||||
|
)
|
||||||
|
domain_move_out = self._get_stock_move_domain(
|
||||||
|
domain_move_out_loc, from_date, to_date
|
||||||
|
)
|
||||||
|
move_obj = self.env["stock.move"]
|
||||||
|
# Positive moves
|
||||||
|
moves_in = move_obj.search_read(
|
||||||
|
domain_move_in, ["product_id", "product_qty", "date"], order="date asc"
|
||||||
|
)
|
||||||
|
# We'll convert to negative these quantities to operate with them
|
||||||
|
# to obtain the stock snapshot in every moment
|
||||||
|
moves_out = move_obj.search_read(
|
||||||
|
domain_move_out, ["product_id", "product_qty", "date"], order="date asc"
|
||||||
|
)
|
||||||
|
for move in moves_out:
|
||||||
|
move["product_qty"] *= -1
|
||||||
|
# Merge both results and group them by product id as key
|
||||||
|
moves = moves_in + moves_out
|
||||||
|
return self._set_product_moves_dict(moves, location, from_date, to_date)
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
|
||||||
* `Tecnativa <https://www.tecnativa.com>`_:
|
* `Tecnativa <https://www.tecnativa.com>`_:
|
||||||
|
|
||||||
* Vicent Cubells <vicent@vcubells.net>
|
* Vicent Cubells
|
||||||
* David Vidal
|
* David Vidal
|
||||||
|
* Víctor Martínez
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright 2016 Cyril Gaudin (Camptocamp)
|
# Copyright 2016 Cyril Gaudin (Camptocamp)
|
||||||
# Copyright 2019 Tecnativa
|
# Copyright 2019 David Vidal - Tecnativa
|
||||||
|
# Copyright 2020 Víctor Martínez - Tecnativa
|
||||||
# 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 models
|
from odoo import models
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
@@ -14,17 +15,17 @@ class TestOrderpointGenerator(SavepointCase):
|
|||||||
cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"]
|
cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"]
|
||||||
cls.orderpoint_template_model = cls.env["stock.warehouse.orderpoint.template"]
|
cls.orderpoint_template_model = cls.env["stock.warehouse.orderpoint.template"]
|
||||||
cls.product_model = cls.env["product.product"]
|
cls.product_model = cls.env["product.product"]
|
||||||
cls.p1 = cls.product_model.create({"name": "Unittest P1", "type": "product",})
|
cls.p1 = cls.product_model.create({"name": "Unittest P1", "type": "product"})
|
||||||
cls.p2 = cls.product_model.create({"name": "Unittest P2", "type": "product",})
|
cls.p2 = cls.product_model.create({"name": "Unittest P2", "type": "product"})
|
||||||
cls.wh1 = cls.env["stock.warehouse"].create(
|
cls.wh1 = cls.env["stock.warehouse"].create(
|
||||||
{"name": "TEST WH1", "code": "TST1",}
|
{"name": "TEST WH1", "code": "TST1"}
|
||||||
)
|
)
|
||||||
location_obj = cls.env["stock.location"]
|
location_obj = cls.env["stock.location"]
|
||||||
cls.supplier_loc = location_obj.create(
|
cls.supplier_loc = location_obj.create(
|
||||||
{"name": "Test supplier location", "usage": "supplier",}
|
{"name": "Test supplier location", "usage": "supplier"}
|
||||||
)
|
)
|
||||||
cls.customer_loc = location_obj.create(
|
cls.customer_loc = location_obj.create(
|
||||||
{"name": "Test customer location", "usage": "customer",}
|
{"name": "Test customer location", "usage": "customer"}
|
||||||
)
|
)
|
||||||
cls.orderpoint_fields_dict = {
|
cls.orderpoint_fields_dict = {
|
||||||
"warehouse_id": cls.wh1.id,
|
"warehouse_id": cls.wh1.id,
|
||||||
@@ -325,39 +326,25 @@ class TestOrderpointGenerator(SavepointCase):
|
|||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict = self.orderpoint_fields_dict.copy()
|
orderpoint_auto_dict = self.orderpoint_fields_dict.copy()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_min_qty": 100.0})
|
||||||
{"product_min_qty": 100.0,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
# Min stock for p1: 45
|
# Min stock for p1: 45
|
||||||
self.template.write(
|
self.template.write({"auto_min_qty_criteria": "min"})
|
||||||
{"auto_min_qty_criteria": "min",}
|
|
||||||
)
|
|
||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_min_qty": 45.0})
|
||||||
{"product_min_qty": 45.0,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
# Median of stock for p1: 52
|
# Median of stock for p1: 52
|
||||||
self.template.write(
|
self.template.write({"auto_min_qty_criteria": "median"})
|
||||||
{"auto_min_qty_criteria": "median",}
|
|
||||||
)
|
|
||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_min_qty": 52.0})
|
||||||
{"product_min_qty": 52.0,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
# Average of stock for p1: 60.4
|
# Average of stock for p1: 60.4
|
||||||
self.template.write(
|
self.template.write({"auto_min_qty_criteria": "avg"})
|
||||||
{"auto_min_qty_criteria": "avg",}
|
|
||||||
)
|
|
||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_min_qty": 60.4})
|
||||||
{"product_min_qty": 60.4,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
# Set auto values for min and max: 60.4 (avg) 100 (max)
|
# Set auto values for min and max: 60.4 (avg) 100 (max)
|
||||||
self.template.write(
|
self.template.write(
|
||||||
@@ -370,19 +357,13 @@ class TestOrderpointGenerator(SavepointCase):
|
|||||||
)
|
)
|
||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_max_qty": 100})
|
||||||
{"product_max_qty": 100,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
# If they have the same values, only one is computed:
|
# If they have the same values, only one is computed:
|
||||||
self.template.write(
|
self.template.write({"auto_min_qty_criteria": "max"})
|
||||||
{"auto_min_qty_criteria": "max",}
|
|
||||||
)
|
|
||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_min_qty": 100})
|
||||||
{"product_min_qty": 100,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
# Auto min max over a shorter period
|
# Auto min max over a shorter period
|
||||||
self.template.write(
|
self.template.write(
|
||||||
@@ -395,9 +376,7 @@ class TestOrderpointGenerator(SavepointCase):
|
|||||||
)
|
)
|
||||||
wizard = self.wizard_over_products(self.p1, self.template)
|
wizard = self.wizard_over_products(self.p1, self.template)
|
||||||
wizard.action_configure()
|
wizard.action_configure()
|
||||||
orderpoint_auto_dict.update(
|
orderpoint_auto_dict.update({"product_min_qty": 55, "product_max_qty": 50})
|
||||||
{"product_min_qty": 55, "product_max_qty": 50,}
|
|
||||||
)
|
|
||||||
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
|
||||||
|
|
||||||
def test_auto_qty_multi_products(self):
|
def test_auto_qty_multi_products(self):
|
||||||
|
|||||||
@@ -166,8 +166,7 @@
|
|||||||
<field name="name">Reordering Rule Templates</field>
|
<field name="name">Reordering Rule Templates</field>
|
||||||
<field name="res_model">stock.warehouse.orderpoint.template</field>
|
<field name="res_model">stock.warehouse.orderpoint.template</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_mode">form,tree</field>
|
||||||
<field name="view_mode">tree,form</field>
|
|
||||||
<field name="view_id" ref="view_warehouse_orderpoint_template_tree" />
|
<field name="view_id" ref="view_warehouse_orderpoint_template_tree" />
|
||||||
<field name="search_view_id" ref="view_warehouse_orderpoint_template_search" />
|
<field name="search_view_id" ref="view_warehouse_orderpoint_template_search" />
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# 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 _, api, fields, models
|
from odoo import _, fields, models
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
_template_register = ["orderpoint_template_id"]
|
_template_register = ["orderpoint_template_id"]
|
||||||
@@ -22,7 +22,6 @@ class OrderpointGenerator(models.TransientModel):
|
|||||||
string="Reordering Rule Templates",
|
string="Reordering Rule Templates",
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def action_configure(self):
|
def action_configure(self):
|
||||||
"""Action to retrieve wizard data and launch creation of items."""
|
"""Action to retrieve wizard data and launch creation of items."""
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -27,19 +27,17 @@
|
|||||||
<act_window
|
<act_window
|
||||||
name="Reordering Rules Generator"
|
name="Reordering Rules Generator"
|
||||||
res_model="stock.warehouse.orderpoint.generator"
|
res_model="stock.warehouse.orderpoint.generator"
|
||||||
src_model="product.product"
|
binding_model="product.product"
|
||||||
view_mode="form"
|
view_mode="form"
|
||||||
target="new"
|
target="new"
|
||||||
key2="client_action_multi"
|
|
||||||
id="act_create_product_conf"
|
id="act_create_product_conf"
|
||||||
/>
|
/>
|
||||||
<act_window
|
<act_window
|
||||||
name="Reordering Rules Generator"
|
name="Reordering Rules Generator"
|
||||||
res_model="stock.warehouse.orderpoint.generator"
|
res_model="stock.warehouse.orderpoint.generator"
|
||||||
src_model="product.template"
|
binding_model="product.template"
|
||||||
view_mode="form"
|
view_mode="form"
|
||||||
target="new"
|
target="new"
|
||||||
key2="client_action_multi"
|
|
||||||
id="act_create_product_template_conf"
|
id="act_create_product_template_conf"
|
||||||
/>
|
/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
Reference in New Issue
Block a user