[IMP] stock_orderpoint_generator: black, isort, prettier

This commit is contained in:
Víctor Martínez
2020-10-27 12:09:46 +01:00
parent 6f0d48c001
commit 14e9ff28f8
10 changed files with 635 additions and 522 deletions

View File

@@ -0,0 +1 @@
../../../../stock_orderpoint_generator

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@@ -3,22 +3,20 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Order point generator',
'summary': 'Mass configuration of stock order points',
'version': '12.0.1.0.0',
'author': "Camptocamp, "
"Tecnativa, "
"Odoo Community Association (OCA)",
'category': 'Warehouse',
'license': 'AGPL-3',
'website': "https://github.com/OCA/stock-logistics-warehouse",
'depends': ['stock'],
'data': [
'views/orderpoint_template_views.xml',
"name": "Order point generator",
"summary": "Mass configuration of stock order points",
"version": "12.0.1.0.0",
"author": "Camptocamp, " "Tecnativa, " "Odoo Community Association (OCA)",
"category": "Warehouse",
"license": "AGPL-3",
"website": "https://github.com/OCA/stock-logistics-warehouse",
"depends": ["stock"],
"data": [
"views/orderpoint_template_views.xml",
"wizard/orderpoint_generator_view.xml",
"data/ir_cron.xml",
"security/ir.model.access.csv",
],
'installable': True,
'auto_install': False,
"installable": True,
"auto_install": False,
}

View File

@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="ir_cron_auto_orderpoint_template" model="ir.cron">
<field name="name">Reordering Rule Templates Generator</field>
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
<field name="model_id" ref="model_stock_warehouse_orderpoint_template"/>
<field eval="False" name="doall" />
<field name="model_id" ref="model_stock_warehouse_orderpoint_template" />
<field name="code">model._cron_create_auto_orderpoints()</field>
<field name="active" eval="True" />
</record>
</odoo>

View File

@@ -3,9 +3,10 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from statistics import mean, median_high
from odoo import api, fields, models
class OrderpointTemplate(models.Model):
""" Template for orderpoints
@@ -18,11 +19,12 @@ class OrderpointTemplate(models.Model):
_table is redefined to separate templates from orderpoints
"""
_name = 'stock.warehouse.orderpoint.template'
_description = 'Reordering Rule Templates'
_inherit = 'stock.warehouse.orderpoint'
_table = 'stock_warehouse_orderpoint_template'
_name = "stock.warehouse.orderpoint.template"
_description = "Reordering Rule Templates"
_inherit = "stock.warehouse.orderpoint"
_table = "stock_warehouse_orderpoint_template"
name = fields.Char(copy=True)
group_id = fields.Many2one(copy=True)
@@ -33,137 +35,138 @@ class OrderpointTemplate(models.Model):
auto_min_qty = fields.Boolean(
string="Auto Minimum",
help="Auto compute minimum quantity "
"per product for a given a date range",
help="Auto compute minimum quantity " "per product for a given a date range",
)
auto_min_date_start = fields.Datetime()
auto_min_date_end = fields.Datetime()
auto_min_qty_criteria = fields.Selection(
selection=[
('max', 'Maximum'),
('median', 'Most frequent'),
('avg', 'Average'),
('min', 'Minimum'),
("max", "Maximum"),
("median", "Most frequent"),
("avg", "Average"),
("min", "Minimum"),
],
default='max',
default="max",
help="Select a criteria to auto compute the minimum",
)
auto_max_qty = fields.Boolean(
string="Auto Maximum",
help="Auto compute maximum quantity "
"per product for a given a date range",
help="Auto compute maximum quantity " "per product for a given a date range",
)
auto_max_qty_criteria = fields.Selection(
selection=[
('max', 'Maximum'),
('median', 'Most frequent'),
('avg', 'Average'),
('min', 'Minimum'),
("max", "Maximum"),
("median", "Most frequent"),
("avg", "Average"),
("min", "Minimum"),
],
help="Select a criteria to auto compute the maximum",
)
auto_max_date_start = fields.Datetime()
auto_max_date_end = fields.Datetime()
auto_generate = fields.Boolean(
string='Create Rules Automatically',
string="Create Rules Automatically",
help="When checked, the 'Reordering Rule Templates Generator' "
"scheduled action will automatically update the rules of a "
"selection of products."
"scheduled action will automatically update the rules of a "
"selection of products.",
)
auto_product_ids = fields.Many2many(
comodel_name='product.product',
string='Products',
comodel_name="product.product",
string="Products",
help="A reordering rule will be automatically created by the "
"scheduled action for every product in this list."
"scheduled action for every product in this list.",
)
auto_last_generation = fields.Datetime(string='Last Automatic Generation')
auto_last_generation = fields.Datetime(string="Last Automatic Generation")
def _template_fields_to_discard(self):
"""In order to create every orderpoint we should pop this template
customization fields """
return [
'auto_generate', 'auto_product_ids', 'auto_last_generation',
'auto_min_qty', 'auto_min_date_start', 'auto_min_qty_criteria',
'auto_min_date_end', 'auto_max_date_start', 'auto_max_date_end',
'auto_max_qty_criteria', 'auto_max_qty',
"auto_generate",
"auto_product_ids",
"auto_last_generation",
"auto_min_qty",
"auto_min_date_start",
"auto_min_qty_criteria",
"auto_min_date_end",
"auto_max_date_start",
"auto_max_date_end",
"auto_max_qty_criteria",
"auto_max_qty",
]
def _disable_old_instances(self, products):
"""Clean old instance by setting those inactives"""
orderpoints = self.env['stock.warehouse.orderpoint'].search(
[('product_id', 'in', products.ids)]
orderpoints = self.env["stock.warehouse.orderpoint"].search(
[("product_id", "in", products.ids)]
)
orderpoints.write({'active': False})
orderpoints.write({"active": False})
@api.model
def _get_criteria_methods(self):
"""Allows to extend methods with other statistical aproaches"""
return {
'max': max,
'median': median_high,
'avg': mean,
'min': min,
"max": max,
"median": median_high,
"avg": mean,
"min": min,
}
@api.model
def _get_product_qty_by_criteria(
self, products, location_id, from_date, to_date, criteria):
self, products, location_id, from_date, to_date, criteria
):
"""Returns a dict with product ids as keys and the resulting
calculation of historic moves according to criteria"""
stock_qty_history = products._compute_historic_quantities_dict(
location_id=location_id,
from_date=from_date,
to_date=to_date)
location_id=location_id, from_date=from_date, to_date=to_date
)
criteria_methods = self._get_criteria_methods()
return {x: criteria_methods[criteria](y['stock_history'])
for x, y in stock_qty_history.items()}
return {
x: criteria_methods[criteria](y["stock_history"])
for x, y in stock_qty_history.items()
}
def _create_instances(self, product_ids):
"""Create instances of model using template inherited model and
compute autovalues if needed"""
orderpoint_model = self.env['stock.warehouse.orderpoint']
orderpoint_model = self.env["stock.warehouse.orderpoint"]
for record in self:
# Flag equality so we compute the values just once
auto_same_values = (
record.auto_max_date_start == record.auto_min_date_start
) and (
record.auto_max_date_end == record.auto_max_date_end
) and (
record.auto_max_qty_criteria ==
record.auto_min_qty_criteria)
(record.auto_max_date_start == record.auto_min_date_start)
and (record.auto_max_date_end == record.auto_max_date_end)
and (record.auto_max_qty_criteria == record.auto_min_qty_criteria)
)
stock_min_qty = stock_max_qty = {}
if record.auto_min_qty:
stock_min_qty = (
self._get_product_qty_by_criteria(
product_ids,
location_id=record.location_id,
from_date=record.auto_min_date_start,
to_date=record.auto_min_date_end,
criteria=record.auto_min_qty_criteria,
))
stock_min_qty = self._get_product_qty_by_criteria(
product_ids,
location_id=record.location_id,
from_date=record.auto_min_date_start,
to_date=record.auto_min_date_end,
criteria=record.auto_min_qty_criteria,
)
if auto_same_values:
stock_max_qty = stock_min_qty
if record.auto_max_qty and not stock_max_qty:
stock_max_qty = (
self._get_product_qty_by_criteria(
product_ids,
location_id=record.location_id,
from_date=record.auto_max_date_start,
to_date=record.auto_max_date_end,
criteria=record.auto_max_qty_criteria,
))
stock_max_qty = self._get_product_qty_by_criteria(
product_ids,
location_id=record.location_id,
from_date=record.auto_max_date_start,
to_date=record.auto_max_date_end,
criteria=record.auto_max_qty_criteria,
)
for data in record.copy_data():
for discard_field in self._template_fields_to_discard():
data.pop(discard_field)
for product_id in product_ids:
vals = data.copy()
vals['product_id'] = product_id.id
vals["product_id"] = product_id.id
if record.auto_min_qty:
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:
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)
@api.multi
@@ -179,11 +182,13 @@ class OrderpointTemplate(models.Model):
for template in self:
if not template.auto_generate:
continue
if (not template.auto_last_generation or
template.write_date > template.auto_last_generation):
if (
not template.auto_last_generation
or template.write_date > template.auto_last_generation
):
template.auto_last_generation = fields.Datetime.now()
template.create_orderpoints(template.auto_product_ids)
@api.model
def _cron_create_auto_orderpoints(self):
self.search([('auto_generate', '=', True)]).create_auto_orderpoints()
self.search([("auto_generate", "=", True)]).create_auto_orderpoints()

View File

@@ -2,72 +2,75 @@
# Copyright 2019 Tecnativa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import api, fields, models
from collections import OrderedDict
from odoo import api, fields, models
class ProductProduct(models.Model):
_inherit = 'product.product'
_inherit = "product.product"
auto_orderpoint_template_ids = fields.Many2many(
comodel_name='stock.warehouse.orderpoint.template',
comodel_name="stock.warehouse.orderpoint.template",
string="Automatic Reordering Rules",
domain=[('auto_generate', '=', True)],
domain=[("auto_generate", "=", True)],
help="When one or several automatic reordering rule is selected, "
"a Scheduled Action will automatically generate or update "
"the reordering rules of the product."
"a Scheduled Action will automatically generate or update "
"the reordering rules of the product.",
)
@api.model
def create(self, vals):
record = super().create(vals)
if vals.get('auto_orderpoint_template_ids'):
if vals.get("auto_orderpoint_template_ids"):
record.auto_orderpoint_template_ids.create_orderpoints(record)
return record
@api.multi
def write(self, vals):
result = super().write(vals)
if vals.get('auto_orderpoint_template_ids'):
orderpoint_templates = self.mapped('auto_orderpoint_template_ids')
if vals.get("auto_orderpoint_template_ids"):
orderpoint_templates = self.mapped("auto_orderpoint_template_ids")
orderpoint_templates.create_orderpoints(self)
return result
def _compute_historic_quantities_dict(
self, location_id=False, from_date=False, to_date=False):
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())
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)
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:
domain_move_in += [('date', '>=', from_date)]
domain_move_out += [('date', '>=', from_date)]
domain_move_in += [('date', '<=', to_date)]
domain_move_out += [('date', '<=', to_date)]
move_obj = self.env['stock.move']
domain_move_in += [("date", ">=", from_date)]
domain_move_out += [("date", ">=", from_date)]
domain_move_in += [("date", "<=", to_date)]
domain_move_out += [("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')
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')
domain_move_out, ["product_id", "product_qty", "date"], order="date asc"
)
for move in moves_out:
move['product_qty'] *= -1
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
@@ -75,17 +78,15 @@ class ProductProduct(models.Model):
# default the compute the stock value anyway to default the value
# for products with no moves for the given period
initial_stock = {}
initial_stock = self.with_context(
location=location)._compute_quantities_dict(
False, False, False, to_date=from_date or to_date)
initial_stock = self.with_context(location=location)._compute_quantities_dict(
False, False, False, to_date=from_date or to_date
)
product_moves_dict = {}
for move in moves:
product_moves_dict.setdefault(move['product_id'][0], {})
product_moves_dict[move['product_id'][0]].update({
move['date']: {
'prod_qty': move['product_qty'],
}
})
product_moves_dict.setdefault(move["product_id"][0], {})
product_moves_dict[move["product_id"][0]].update(
{move["date"]: {"prod_qty": move["product_qty"],}}
)
for product in self.with_context(prefetch_fields=False):
# If no there are no moves for a product we default the stock
# to the one for the given period nevermind the dates
@@ -94,11 +95,10 @@ class ProductProduct(models.Model):
if not product_moves:
product_moves_dict[product.id] = {
to_date: {
'prod_qty': 0,
'stock': prod_initial_stock.get('qty_available', 0),
"prod_qty": 0,
"stock": prod_initial_stock.get("qty_available", 0),
},
'stock_history': [
prod_initial_stock.get('qty_available', 0)],
"stock_history": [prod_initial_stock.get("qty_available", 0)],
}
continue
# Now we'll sort the moves by date and assign an initial stock so
@@ -108,15 +108,16 @@ class ProductProduct(models.Model):
stock = False
first_item = product_moves[next(iter(product_moves))]
if from_date:
stock = prod_initial_stock.get('qty_available')
stock = prod_initial_stock.get("qty_available")
if not stock:
stock = first_item['prod_qty']
first_item['stock'] = stock
stock = first_item["prod_qty"]
first_item["stock"] = stock
iter_moves = iter(product_moves)
next(iter_moves, None)
for date in iter_moves:
stock += product_moves[date]['prod_qty']
product_moves[date]['stock'] = stock
product_moves_dict[product.id]['stock_history'] = (
[v['stock'] for k, v in product_moves.items()])
stock += product_moves[date]["prod_qty"]
product_moves[date]["stock"] = stock
product_moves_dict[product.id]["stock_history"] = [
v["stock"] for k, v in product_moves.items()
]
return product_moves_dict

View File

@@ -7,242 +7,270 @@ from odoo.tests.common import SavepointCase
class TestOrderpointGenerator(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.wizard_model = cls.env['stock.warehouse.orderpoint.generator']
cls.orderpoint_model = cls.env['stock.warehouse.orderpoint']
cls.orderpoint_template_model = (
cls.env['stock.warehouse.orderpoint.template'])
cls.product_model = cls.env['product.product']
cls.p1 = cls.product_model.create({
'name': 'Unittest P1',
'type': 'product',
})
cls.p2 = cls.product_model.create({
'name': 'Unittest P2',
'type': 'product',
})
cls.wh1 = cls.env['stock.warehouse'].create({
'name': 'TEST WH1',
'code': 'TST1',
})
location_obj = cls.env['stock.location']
cls.supplier_loc = location_obj.create({
'name': 'Test supplier location',
'usage': 'supplier',
})
cls.customer_loc = location_obj.create({
'name': 'Test customer location',
'usage': 'customer',
})
cls.wizard_model = cls.env["stock.warehouse.orderpoint.generator"]
cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"]
cls.orderpoint_template_model = cls.env["stock.warehouse.orderpoint.template"]
cls.product_model = cls.env["product.product"]
cls.p1 = cls.product_model.create({"name": "Unittest P1", "type": "product",})
cls.p2 = cls.product_model.create({"name": "Unittest P2", "type": "product",})
cls.wh1 = cls.env["stock.warehouse"].create(
{"name": "TEST WH1", "code": "TST1",}
)
location_obj = cls.env["stock.location"]
cls.supplier_loc = location_obj.create(
{"name": "Test supplier location", "usage": "supplier",}
)
cls.customer_loc = location_obj.create(
{"name": "Test customer location", "usage": "customer",}
)
cls.orderpoint_fields_dict = {
'warehouse_id': cls.wh1.id,
'location_id': cls.wh1.lot_stock_id.id,
'name': 'TEST-ORDERPOINT-001',
'product_max_qty': 15.0,
'product_min_qty': 5.0,
'qty_multiple': 1,
"warehouse_id": cls.wh1.id,
"location_id": cls.wh1.lot_stock_id.id,
"name": "TEST-ORDERPOINT-001",
"product_max_qty": 15.0,
"product_min_qty": 5.0,
"qty_multiple": 1,
}
cls.template = cls.orderpoint_template_model.create(
cls.orderpoint_fields_dict)
cls.template = cls.orderpoint_template_model.create(cls.orderpoint_fields_dict)
# Create some moves for p1 and p2 so we can have a history to test
# p1 [100, 50, 45, 55, 52]
# t1 - p1 - stock.move location1 100 # 100
cls.p1m1 = cls.env['stock.move'].create({
'name': cls.p1.name,
'product_id': cls.p1.id,
'product_uom_qty': 100,
'product_uom': cls.p1.uom_id.id,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 01:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p1m1.id,
'product_id': cls.p1.id,
'qty_done': 100,
'product_uom_id': cls.p1.uom_id.id,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 01:00:00',
})
cls.p1m1 = cls.env["stock.move"].create(
{
"name": cls.p1.name,
"product_id": cls.p1.id,
"product_uom_qty": 100,
"product_uom": cls.p1.uom_id.id,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 01:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p1m1.id,
"product_id": cls.p1.id,
"qty_done": 100,
"product_uom_id": cls.p1.uom_id.id,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 01:00:00",
}
)
# t2 - p1 - stock.move location1 -50 # 50
cls.p1m2 = cls.p1m1.copy({
'product_uom_qty': 50,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 02:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p1m2.id,
'product_id': cls.p1.id,
'qty_done': 50,
'product_uom_id': cls.p1.uom_id.id,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 02:00:00',
})
cls.p1m2 = cls.p1m1.copy(
{
"product_uom_qty": 50,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 02:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p1m2.id,
"product_id": cls.p1.id,
"qty_done": 50,
"product_uom_id": cls.p1.uom_id.id,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 02:00:00",
}
)
# t3 - p1 - stock.move location1 -5 # 45
cls.p1m3 = cls.p1m1.copy({
'name': cls.p1.name,
'product_id': cls.p1.id,
'product_uom_qty': 5,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 03:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p1m3.id,
'product_id': cls.p1.id,
'qty_done': 5,
'product_uom_id': cls.p1.uom_id.id,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 03:00:00',
})
cls.p1m3 = cls.p1m1.copy(
{
"name": cls.p1.name,
"product_id": cls.p1.id,
"product_uom_qty": 5,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 03:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p1m3.id,
"product_id": cls.p1.id,
"qty_done": 5,
"product_uom_id": cls.p1.uom_id.id,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 03:00:00",
}
)
# t4 - p1 - stock.move location1 10 # 55
cls.p1m4 = cls.p1m1.copy({
'product_uom_qty': 10,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 04:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p1m4.id,
'product_id': cls.p1.id,
'qty_done': 10,
'product_uom_id': cls.p1.uom_id.id,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 04:00:00',
})
cls.p1m4 = cls.p1m1.copy(
{
"product_uom_qty": 10,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 04:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p1m4.id,
"product_id": cls.p1.id,
"qty_done": 10,
"product_uom_id": cls.p1.uom_id.id,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 04:00:00",
}
)
# t5 - p1 - stock.move location1 -3 # 52
cls.p1m5 = cls.p1m1.copy({
'product_uom_qty': 3,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 05:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p1m5.id,
'product_id': cls.p1.id,
'qty_done': 3,
'product_uom_id': cls.p1.uom_id.id,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 05:00:00',
})
cls.p1m5 = cls.p1m1.copy(
{
"product_uom_qty": 3,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 05:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p1m5.id,
"product_id": cls.p1.id,
"qty_done": 3,
"product_uom_id": cls.p1.uom_id.id,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 05:00:00",
}
)
# p2
# t1 - p2 - stock.move location1 1000 # 1000
cls.p2m1 = cls.env['stock.move'].create({
'name': cls.p2.name,
'product_id': cls.p2.id,
'product_uom': cls.p2.uom_id.id,
'product_uom_qty': 1000,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 01:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p2m1.id,
'product_id': cls.p2.id,
'qty_done': 1000,
'product_uom_id': cls.p2.uom_id.id,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 01:00:00',
})
cls.p2m1 = cls.env["stock.move"].create(
{
"name": cls.p2.name,
"product_id": cls.p2.id,
"product_uom": cls.p2.uom_id.id,
"product_uom_qty": 1000,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 01:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p2m1.id,
"product_id": cls.p2.id,
"qty_done": 1000,
"product_uom_id": cls.p2.uom_id.id,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 01:00:00",
}
)
# t2 - p2 - stock.move location1 -50 # 950
cls.p2m2 = cls.p2m1.copy({
'product_uom_qty': 50,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 02:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p2m2.id,
'product_id': cls.p2.id,
'qty_done': 50,
'product_uom_id': cls.p2.uom_id.id,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 02:00:00',
})
cls.p2m2 = cls.p2m1.copy(
{
"product_uom_qty": 50,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 02:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p2m2.id,
"product_id": cls.p2.id,
"qty_done": 50,
"product_uom_id": cls.p2.uom_id.id,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 02:00:00",
}
)
# t3 - p2 - stock.move location1 -7 # 943
cls.p2m3 = cls.p2m1.copy({
'product_uom_qty': 7,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 03:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p2m3.id,
'product_id': cls.p2.id,
'qty_done': 7,
'product_uom_id': cls.p2.uom_id.id,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 03:00:00',
})
cls.p2m3 = cls.p2m1.copy(
{
"product_uom_qty": 7,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 03:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p2m3.id,
"product_id": cls.p2.id,
"qty_done": 7,
"product_uom_id": cls.p2.uom_id.id,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 03:00:00",
}
)
# t4 - p2 - stock.move location1 100 # 1043
cls.p2m4 = cls.p2m1.copy({
'product_uom_qty': 100,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 04:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p2m4.id,
'product_id': cls.p2.id,
'qty_done': 100,
'product_uom_id': cls.p2.uom_id.id,
'location_id': cls.supplier_loc.id,
'location_dest_id': cls.wh1.lot_stock_id.id,
'state': 'done',
'date': '2019-01-01 04:00:00',
})
cls.p2m4 = cls.p2m1.copy(
{
"product_uom_qty": 100,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 04:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p2m4.id,
"product_id": cls.p2.id,
"qty_done": 100,
"product_uom_id": cls.p2.uom_id.id,
"location_id": cls.supplier_loc.id,
"location_dest_id": cls.wh1.lot_stock_id.id,
"state": "done",
"date": "2019-01-01 04:00:00",
}
)
# t5 - p2 - stock.move location1 -3 # 1040
cls.p2m5 = cls.p2m1.copy({
'product_uom_qty': 3,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 05:00:00',
})
cls.env['stock.move.line'].create({
'move_id': cls.p2m5.id,
'product_id': cls.p2.id,
'qty_done': 3,
'product_uom_id': cls.p2.uom_id.id,
'location_id': cls.wh1.lot_stock_id.id,
'location_dest_id': cls.customer_loc.id,
'state': 'done',
'date': '2019-01-01 05:00:00',
})
cls.p2m5 = cls.p2m1.copy(
{
"product_uom_qty": 3,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 05:00:00",
}
)
cls.env["stock.move.line"].create(
{
"move_id": cls.p2m5.id,
"product_id": cls.p2.id,
"qty_done": 3,
"product_uom_id": cls.p2.uom_id.id,
"location_id": cls.wh1.lot_stock_id.id,
"location_dest_id": cls.customer_loc.id,
"state": "done",
"date": "2019-01-01 05:00:00",
}
)
def check_orderpoint(self, products, template, fields_dict):
orderpoints = self.orderpoint_model.search([
('name', '=', template.name)
], order='product_id')
orderpoints = self.orderpoint_model.search(
[("name", "=", template.name)], order="product_id"
)
self.assertEqual(len(products), len(orderpoints))
for i, product in enumerate(products):
self.assertEqual(product, orderpoints[i].product_id)
@@ -256,135 +284,139 @@ class TestOrderpointGenerator(SavepointCase):
def wizard_over_products(self, product, template):
return self.wizard_model.with_context(
active_model=product._name,
active_ids=product.ids,
).create({
'orderpoint_template_id': [(6, 0, template.ids)]
})
active_model=product._name, active_ids=product.ids,
).create({"orderpoint_template_id": [(6, 0, template.ids)]})
def test_product_orderpoint(self):
products = self.p1 + self.p2
wizard = self.wizard_over_products(products, self.template)
wizard.action_configure()
self.check_orderpoint(
products, self.template, self.orderpoint_fields_dict)
self.check_orderpoint(products, self.template, self.orderpoint_fields_dict)
def test_template_orderpoint(self):
prod_tmpl = self.p1.product_tmpl_id + self.p2.product_tmpl_id
wizard = self.wizard_over_products(prod_tmpl, self.template)
wizard.action_configure()
products = self.p1 + self.p2
self.check_orderpoint(
products, self.template, self.orderpoint_fields_dict)
self.check_orderpoint(products, self.template, self.orderpoint_fields_dict)
def test_template_variants_orderpoint(self):
self.product_model.create({
'product_tmpl_id': self.p1.product_tmpl_id.id,
'name': 'Unittest P1 variant'
})
wizard = self.wizard_over_products(
self.p1.product_tmpl_id, self.template)
self.product_model.create(
{
"product_tmpl_id": self.p1.product_tmpl_id.id,
"name": "Unittest P1 variant",
}
)
wizard = self.wizard_over_products(self.p1.product_tmpl_id, self.template)
with self.assertRaises(UserError):
wizard.action_configure()
def test_auto_qty(self):
"""Compute min and max qty according to criteria"""
# Max stock for p1: 100
self.template.write({
'auto_min_qty': True,
'auto_min_date_start': '2019-01-01 00:00:00',
'auto_min_date_end': '2019-02-01 00:00:00',
'auto_min_qty_criteria': 'max',
})
self.template.write(
{
"auto_min_qty": True,
"auto_min_date_start": "2019-01-01 00:00:00",
"auto_min_date_end": "2019-02-01 00:00:00",
"auto_min_qty_criteria": "max",
}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict = self.orderpoint_fields_dict.copy()
orderpoint_auto_dict.update({
'product_min_qty': 100.0,
})
orderpoint_auto_dict.update(
{"product_min_qty": 100.0,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
# Min stock for p1: 45
self.template.write({
'auto_min_qty_criteria': 'min',
})
self.template.write(
{"auto_min_qty_criteria": "min",}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict.update({
'product_min_qty': 45.0,
})
orderpoint_auto_dict.update(
{"product_min_qty": 45.0,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
# Median of stock for p1: 52
self.template.write({
'auto_min_qty_criteria': 'median',
})
self.template.write(
{"auto_min_qty_criteria": "median",}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict.update({
'product_min_qty': 52.0,
})
orderpoint_auto_dict.update(
{"product_min_qty": 52.0,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
# Average of stock for p1: 60.4
self.template.write({
'auto_min_qty_criteria': 'avg',
})
self.template.write(
{"auto_min_qty_criteria": "avg",}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict.update({
'product_min_qty': 60.4,
})
orderpoint_auto_dict.update(
{"product_min_qty": 60.4,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
# Set auto values for min and max: 60.4 (avg) 100 (max)
self.template.write({
'auto_max_qty': True,
'auto_max_date_start': '2019-01-01 00:00:00',
'auto_max_date_end': '2019-02-01 00:00:00',
'auto_max_qty_criteria': 'max',
})
self.template.write(
{
"auto_max_qty": True,
"auto_max_date_start": "2019-01-01 00:00:00",
"auto_max_date_end": "2019-02-01 00:00:00",
"auto_max_qty_criteria": "max",
}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict.update({
'product_max_qty': 100,
})
orderpoint_auto_dict.update(
{"product_max_qty": 100,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
# If they have the same values, only one is computed:
self.template.write({
'auto_min_qty_criteria': 'max',
})
self.template.write(
{"auto_min_qty_criteria": "max",}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict.update({
'product_min_qty': 100,
})
orderpoint_auto_dict.update(
{"product_min_qty": 100,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
# Auto min max over a shorter period
self.template.write({
'auto_max_date_start': '2019-01-01 02:00:00',
'auto_max_date_end': '2019-01-01 03:00:00',
'auto_min_date_start': '2019-01-01 04:00:00',
'auto_min_date_end': '2019-01-01 06:00:00',
})
self.template.write(
{
"auto_max_date_start": "2019-01-01 02:00:00",
"auto_max_date_end": "2019-01-01 03:00:00",
"auto_min_date_start": "2019-01-01 04:00:00",
"auto_min_date_end": "2019-01-01 06:00:00",
}
)
wizard = self.wizard_over_products(self.p1, self.template)
wizard.action_configure()
orderpoint_auto_dict.update({
'product_min_qty': 55,
'product_max_qty': 50,
})
orderpoint_auto_dict.update(
{"product_min_qty": 55, "product_max_qty": 50,}
)
self.check_orderpoint(self.p1, self.template, orderpoint_auto_dict)
def test_auto_qty_multi_products(self):
"""Each product has a different history"""
products = self.p1 + self.p2
self.template.write({
'auto_min_qty': True,
'auto_min_date_start': '2019-01-01 00:00:00',
'auto_min_date_end': '2019-02-01 00:00:00',
'auto_min_qty_criteria': 'max',
})
self.template.write(
{
"auto_min_qty": True,
"auto_min_date_start": "2019-01-01 00:00:00",
"auto_min_date_end": "2019-02-01 00:00:00",
"auto_min_qty_criteria": "max",
}
)
wizard = self.wizard_over_products(products, self.template)
wizard.action_configure()
orderpoint_auto_dict = self.orderpoint_fields_dict.copy()
del orderpoint_auto_dict['product_min_qty']
del orderpoint_auto_dict["product_min_qty"]
orderpoints = self.check_orderpoint(
products, self.template, orderpoint_auto_dict)
products, self.template, orderpoint_auto_dict
)
self.assertEqual(orderpoints[0].product_min_qty, 100)
self.assertEqual(orderpoints[1].product_min_qty, 1043)

View File

@@ -1,51 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_warehouse_orderpoint_template_tree" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.template.tree</field>
<field name="model">stock.warehouse.orderpoint.template</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<tree string="Reordering Rule Templates">
<field name="name"/>
<field name="warehouse_id" groups="stock.group_stock_multi_locations"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<field name="product_min_qty" attrs="{'invisble': [('auto_min_qty', '!=', False)]}"/>
<field name="product_max_qty" attrs="{'invisble': [('auto_min_qty', '!=', False)]}"/>
<field name="auto_min_qty"/>
<field name="auto_max_qty"/>
<field name="auto_generate"/>
<field name="name" />
<field name="warehouse_id" groups="stock.group_stock_multi_locations" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field
name="product_min_qty"
attrs="{'invisble': [('auto_min_qty', '!=', False)]}"
/>
<field
name="product_max_qty"
attrs="{'invisble': [('auto_min_qty', '!=', False)]}"
/>
<field name="auto_min_qty" />
<field name="auto_max_qty" />
<field name="auto_generate" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_warehouse_orderpoint_template_search">
<field name="name">stock.warehouse.orderpoint.template.search</field>
<field name="model">stock.warehouse.orderpoint.template</field>
<field name="mode">primary</field>
<field name="arch" type="xml">
<search string="Reordering Rule Templates Search">
<field name="name" string="Reordering Rule Templates"/>
<field name="warehouse_id"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="name" string="Reordering Rule Templates" />
<field name="warehouse_id" />
<field name="location_id" groups="stock.group_stock_multi_locations" />
<field name="company_id" groups="base.group_multi_company" />
<group expand="0" string="Group By">
<filter string="Warehouse" name="warehouse" domain="[]" context="{'group_by':'warehouse_id'}"/>
<filter string="Location" name="location" domain="[]" context="{'group_by':'location_id'}"/>
<filter
string="Warehouse"
name="warehouse"
domain="[]"
context="{'group_by':'warehouse_id'}"
/>
<filter
string="Location"
name="location"
domain="[]"
context="{'group_by':'location_id'}"
/>
</group>
</search>
</field>
</record>
<record id="view_warehouse_orderpoint_template_form" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.template.form</field>
<field name="model">stock.warehouse.orderpoint.template</field>
<field name="arch" type="xml">
<form string="Reordering Rule Template">
<header>
<button name="create_auto_orderpoints" type="object" string="Generate Automatic Rules"
help="Reordering rules will be created for the selected products. This is equivalent to the Scheduled Action action."
attrs="{'invisible': [('auto_generate', '=', False)]}"/>
<button
name="create_auto_orderpoints"
type="object"
string="Generate Automatic Rules"
help="Reordering rules will be created for the selected products. This is equivalent to the Scheduled Action action."
attrs="{'invisible': [('auto_generate', '=', False)]}"
/>
</header>
<sheet>
<group>
@@ -53,58 +70,105 @@
<field name="name" />
</group>
<group>
<field name="warehouse_id" widget="selection" groups="stock.group_stock_multi_locations"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<field name="group_id" groups="stock.group_adv_location"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field
name="warehouse_id"
widget="selection"
groups="stock.group_stock_multi_locations"
/>
<field
name="location_id"
groups="stock.group_stock_multi_locations"
/>
<field name="group_id" groups="stock.group_adv_location" />
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
</group>
</group>
<group>
<group string="Rules">
<field name="auto_min_qty"/>
<field name="product_min_qty" attrs="{'required': [('auto_min_qty','=', False)], 'invisible': [('auto_min_qty','!=', False)]}"/>
<field name="auto_max_qty"/>
<field name="product_max_qty" attrs="{'required': [('auto_max_qty','=', False)], 'invisible': [('auto_max_qty','!=', False)]}"/>
<field name="qty_multiple" string="Quantity Multiple"/>
<field name="auto_min_qty" />
<field
name="product_min_qty"
attrs="{'required': [('auto_min_qty','=', False)], 'invisible': [('auto_min_qty','!=', False)]}"
/>
<field name="auto_max_qty" />
<field
name="product_max_qty"
attrs="{'required': [('auto_max_qty','=', False)], 'invisible': [('auto_max_qty','!=', False)]}"
/>
<field name="qty_multiple" string="Quantity Multiple" />
</group>
<group string="Misc">
<field name="active" />
<field name="auto_generate"/>
<label for="lead_days"/>
<field name="auto_generate" />
<label for="lead_days" />
<div class="o_row">
<field name="lead_days"/>
<field name="lead_type"/>
<field name="lead_days" />
<field name="lead_type" />
</div>
</group>
<group name="auto_minimum" string="Auto Minimum Criteria" attrs="{'invisible': [('auto_min_qty','=', False)]}">
<field name="auto_min_qty_criteria" string="Criteria" attrs="{'required': [('auto_min_qty','!=', False)]}"/>
<field name="auto_min_date_start" string="From" attrs="{'required': [('auto_min_qty','!=', False)]}"/>
<field name="auto_min_date_end" string="To" attrs="{'required': [('auto_min_qty','!=', False)]}"/>
<group
name="auto_minimum"
string="Auto Minimum Criteria"
attrs="{'invisible': [('auto_min_qty','=', False)]}"
>
<field
name="auto_min_qty_criteria"
string="Criteria"
attrs="{'required': [('auto_min_qty','!=', False)]}"
/>
<field
name="auto_min_date_start"
string="From"
attrs="{'required': [('auto_min_qty','!=', False)]}"
/>
<field
name="auto_min_date_end"
string="To"
attrs="{'required': [('auto_min_qty','!=', False)]}"
/>
</group>
<group name="auto_maximum" string="Auto Maximum Criteria" attrs="{'invisible': [('auto_max_qty','=', False)]}">
<field name="auto_max_qty_criteria" string="Criteria" attrs="{'required': [('auto_max_qty','!=', False)]}"/>
<field name="auto_max_date_start" string="From" attrs="{'required': [('auto_max_qty','!=', False)]}"/>
<field name="auto_max_date_end" string="To" attrs="{'required': [('auto_max_qty','!=', False)]}"/>
<group
name="auto_maximum"
string="Auto Maximum Criteria"
attrs="{'invisible': [('auto_max_qty','=', False)]}"
>
<field
name="auto_max_qty_criteria"
string="Criteria"
attrs="{'required': [('auto_max_qty','!=', False)]}"
/>
<field
name="auto_max_date_start"
string="From"
attrs="{'required': [('auto_max_qty','!=', False)]}"
/>
<field
name="auto_max_date_end"
string="To"
attrs="{'required': [('auto_max_qty','!=', False)]}"
/>
</group>
</group>
<notebook attrs="{'invisible': [('auto_generate', '=', False)]}">
<page string="Products" name="auto_rules">
<field name="auto_product_ids"/>
<field name="auto_product_ids" />
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_orderpoint_template" model="ir.actions.act_window">
<field name="name">Reordering Rule Templates</field>
<field name="res_model">stock.warehouse.orderpoint.template</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</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="help" type="html">
<p class="oe_view_nocontent_create">
@@ -112,11 +176,10 @@
</p>
</field>
</record>
<menuitem
id="menu_orderpoint_template" name="Reordering Rule Templates"
parent="stock.menu_product_in_config_stock"
action="action_orderpoint_template"
id="menu_orderpoint_template"
name="Reordering Rule Templates"
parent="stock.menu_product_in_config_stock"
action="action_orderpoint_template"
/>
</odoo>

View File

@@ -3,10 +3,9 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_template_register = ['orderpoint_template_id']
_template_register = ["orderpoint_template_id"]
class OrderpointGenerator(models.TransientModel):
@@ -14,29 +13,31 @@ class OrderpointGenerator(models.TransientModel):
products. Those configs are generated using templates
"""
_name = 'stock.warehouse.orderpoint.generator'
_description = 'Orderpoint Generator'
_name = "stock.warehouse.orderpoint.generator"
_description = "Orderpoint Generator"
orderpoint_template_id = fields.Many2many(
comodel_name='stock.warehouse.orderpoint.template',
relation='order_point_generator_rel',
string='Reordering Rule Templates'
comodel_name="stock.warehouse.orderpoint.template",
relation="order_point_generator_rel",
string="Reordering Rule Templates",
)
@api.multi
def action_configure(self):
"""Action to retrieve wizard data and launch creation of items."""
self.ensure_one()
model_obj = self.env[self.env.context.get('active_model')]
record_ids = model_obj.browse(self.env.context.get('active_ids'))
model_obj = self.env[self.env.context.get("active_model")]
record_ids = model_obj.browse(self.env.context.get("active_ids"))
if not record_ids:
return model_obj
if self.env.context.get('active_model') == 'product.template':
product_ids = record_ids.mapped('product_variant_ids')
if self.env.context.get("active_model") == "product.template":
product_ids = record_ids.mapped("product_variant_ids")
if len(product_ids) != len(record_ids):
raise UserError(_(
'Cannot apply because some of selected '
'products has multiple variants.'
))
raise UserError(
_(
"Cannot apply because some of selected "
"products has multiple variants."
)
)
record_ids = product_ids
self.orderpoint_template_id.create_orderpoints(record_ids)

View File

@@ -1,37 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="orderpoint_generator_view" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.generator</field>
<field name="model">stock.warehouse.orderpoint.generator</field>
<field name="arch" type="xml">
<form string="Reordering Rules Generator">
<label for="orderpoint_template" string="This wizard will apply the following orderpoint to selected product(s)"/>
<label
for="orderpoint_template"
string="This wizard will apply the following orderpoint to selected product(s)"
/>
<group string="Templates" colspan="4">
<field name="orderpoint_template_id" colspan="4"/>
<field name="orderpoint_template_id" colspan="4" />
</group>
<footer>
<button name="action_configure" string="Apply" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
<button
name="action_configure"
string="Apply"
type="object"
class="btn-primary"
/>
<button string="Cancel" class="btn-default" special="cancel" />
</footer>
</form>
</field>
</record>
<act_window name="Reordering Rules Generator"
res_model="stock.warehouse.orderpoint.generator"
src_model="product.product"
view_mode="form"
target="new"
key2="client_action_multi"
id="act_create_product_conf"/>
<act_window name="Reordering Rules Generator"
res_model="stock.warehouse.orderpoint.generator"
src_model="product.template"
view_mode="form"
target="new"
key2="client_action_multi"
id="act_create_product_template_conf"/>
<act_window
name="Reordering Rules Generator"
res_model="stock.warehouse.orderpoint.generator"
src_model="product.product"
view_mode="form"
target="new"
key2="client_action_multi"
id="act_create_product_conf"
/>
<act_window
name="Reordering Rules Generator"
res_model="stock.warehouse.orderpoint.generator"
src_model="product.template"
view_mode="form"
target="new"
key2="client_action_multi"
id="act_create_product_template_conf"
/>
</odoo>