mirror of
https://github.com/OCA/stock-logistics-warehouse.git
synced 2025-01-21 14:27:28 +02:00
[IMP] stock_orderpoint_generator: black, isort, prettier
This commit is contained in:
@@ -0,0 +1 @@
|
||||
../../../../stock_orderpoint_generator
|
||||
6
setup/stock_orderpoint_generator/setup.py
Normal file
6
setup/stock_orderpoint_generator/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user