[MIG] stock_orderpoint_generator: Migration to 13.0

This commit is contained in:
Víctor Martínez
2020-10-27 12:10:05 +01:00
committed by sergiocorato
parent 8a23ede668
commit 1b95696248
8 changed files with 76 additions and 94 deletions

View File

@@ -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",

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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">

View File

@@ -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()

View File

@@ -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>