[11.0][REW] multi_level_mrp: substitute mrp forecast with stock demand estimates

This commit is contained in:
Lois Rilo
2018-06-21 11:25:23 +02:00
committed by Jordi Ballester Alomar
parent d613212ae7
commit cad67a0231
7 changed files with 101 additions and 307 deletions

View File

@@ -14,11 +14,11 @@
'mrp',
'stock',
'purchase',
'stock_demand_estimate',
],
'data': [
'security/multi_level_mrp_security.xml',
'security/ir.model.access.csv',
'views/mrp_forecast_view.xml',
'views/mrp_area_view.xml',
'views/product_view.xml',
'views/stock_location_view.xml',

View File

@@ -1,6 +1,5 @@
from . import mrp_area
from . import stock_location
from . import mrp_forecast
from . import product
from . import mrp_product
from . import mrp_move

View File

@@ -1,197 +0,0 @@
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# © 2016 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from datetime import date, datetime
class MrpForecastForecast(models.Model):
_name = 'mrp.forecast.forecast'
_order = 'forecast_product_id, date'
date = fields.Date('Date')
forecast_product_id = fields.Many2one('mrp.forecast.product', 'Product',
select=True)
name = fields.Char('Description')
qty_forecast = fields.Float('Quantity')
class MrpForecastProduct(models.Model):
_name = 'mrp.forecast.product'
# TODO: adapt to demand_estimate?? or at least to date_range??
@api.one
@api.depends('product_id')
def _function_name(self):
if self.product_id:
self.name = "[%s] %s" % (self.product_id.default_code,
self.product_id.name, )
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m0(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + \
datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).month
tmonth = (date.today().year * 100) + date.today().month
if not(datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')) < date.today()) \
and fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m0 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m1(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')).month
tdyear = date.today().year
tdmonth = date.today().month + 1
if tdmonth > 12:
tdmonth -= 12
tdyear += 1
tmonth = (tdyear * 100) + tdmonth
if fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m1 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m2(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')).month
tdyear = date.today().year
tdmonth = date.today().month + 2
if tdmonth > 12:
tdmonth -= 12
tdyear += 1
tmonth = (tdyear * 100) + tdmonth
if fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m2 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m3(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')).month
tdyear = date.today().year
tdmonth = date.today().month + 3
if tdmonth > 12:
tdmonth -= 12
tdyear += 1
tmonth = (tdyear * 100) + tdmonth
if fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m3 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m4(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')).month
tdyear = date.today().year
tdmonth = date.today().month + 4
if tdmonth > 12:
tdmonth -= 12
tdyear += 1
tmonth = (tdyear * 100) + tdmonth
if fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m4 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m5(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')).month
tdyear = date.today().year
tdmonth = date.today().month + 5
if tdmonth > 12:
tdmonth -= 12
tdyear += 1
tmonth = (tdyear * 100) + tdmonth
if fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m5 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_m6(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
fcmonth = (datetime.date(datetime.strptime(
fc.date, '%Y-%m-%d')).year * 100) + datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')).month
tdyear = date.today().year
tdmonth = date.today().month + 6
if tdmonth > 12:
tdmonth -= 12
tdyear += 1
tmonth = (tdyear * 100) + tdmonth
if fcmonth == tmonth:
qty += fc.qty_forecast
self.qty_forecast_m6 = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_past(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
if datetime.date(
datetime.strptime(fc.date, '%Y-%m-%d')) < date.today():
qty += fc.qty_forecast
self.qty_forecast_past = qty
@api.one
@api.depends('mrp_forecast_ids')
def _function_forecast_total(self):
qty = 0.00
for fc in self.mrp_forecast_ids:
qty += fc.qty_forecast
self.qty_forecast_total = qty
mrp_forecast_ids = fields.One2many('mrp.forecast.forecast',
'forecast_product_id',
'Forecast')
name = fields.Char(compute='_function_name', string='Description')
product_id = fields.Many2one('product.product', 'Product', index=True)
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
qty_forecast_m0 = fields.Float(compute='_function_forecast_m0',
string='This Month Forecast')
qty_forecast_m1 = fields.Float(compute='_function_forecast_m1',
string='Month+1 Forecast')
qty_forecast_m2 = fields.Float(compute='_function_forecast_m2',
string='Month+2 Forecast')
qty_forecast_m3 = fields.Float(compute='_function_forecast_m3',
string='Month+3 Forecast')
qty_forecast_m4 = fields.Float(compute='_function_forecast_m4',
string='Month+4 Forecast')
qty_forecast_m5 = fields.Float(compute='_function_forecast_m5',
string='Month+5 Forecast')
qty_forecast_m6 = fields.Float(compute='_function_forecast_m6',
string='Month+6 Forecast')
qty_forecast_past = fields.Float(compute='_function_forecast_past',
string='Past Forecast')
qty_forecast_total = fields.Float(compute='_function_forecast_total',
string='Total Forecast')

View File

@@ -6,6 +6,7 @@ from datetime import date, datetime, timedelta
from odoo.tests.common import SavepointCase
from odoo import fields
from dateutil.rrule import WEEKLY
class TestMultiLevelMRP(SavepointCase):
@@ -15,7 +16,10 @@ class TestMultiLevelMRP(SavepointCase):
super(TestMultiLevelMRP, cls).setUpClass()
cls.mo_obj = cls.env['mrp.production']
cls.po_obj = cls.env['purchase.order']
cls.product_obj = cls.env['product.product']
cls.partner_obj = cls.env['res.partner']
cls.stock_picking_obj = cls.env['stock.picking']
cls.estimate_obj = cls.env['stock.demand.estimate']
cls.multi_level_mrp_wiz = cls.env['multi.level.mrp']
cls.mrp_inventory_procure_wiz = cls.env['mrp.inventory.procure']
cls.mrp_inventory_obj = cls.env['mrp.inventory']
@@ -34,6 +38,20 @@ class TestMultiLevelMRP(SavepointCase):
cls.customer_location = cls.env.ref(
'stock.stock_location_customers')
# Partner:
vendor1 = cls.partner_obj.create({'name': 'Vendor 1'})
# Create products:
route_buy = cls.env.ref('purchase.route_warehouse0_buy').id
cls.prod_test = cls.product_obj.create({
'name': 'Test Top Seller',
'type': 'product',
'list_price': 150.0,
'produce_delay': 5.0,
'route_ids': [(6, 0, [route_buy])],
'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})],
})
# Create test picking:
date_move = datetime.today() + timedelta(days=7)
cls.picking_1 = cls.stock_picking_obj.create({
@@ -90,7 +108,6 @@ class TestMultiLevelMRP(SavepointCase):
'product_uom_id': cls.fp_2.uom_id.id,
'date_planned_start': date_mo,
})
cls.multi_level_mrp_wiz.create({}).run_multi_level_mrp()
# Dates (Strings):
today = datetime.today()
@@ -102,6 +119,42 @@ class TestMultiLevelMRP(SavepointCase):
cls.date_9 = fields.Date.to_string(today + timedelta(days=9))
cls.date_10 = fields.Date.to_string(today + timedelta(days=10))
# Create Date Ranges:
cls.dr_type = cls.env['date.range.type'].create({
'name': 'Weeks',
'company_id': False,
'allow_overlap': False,
})
generator = cls.env['date.range.generator'].create({
'date_start': today - timedelta(days=3),
'name_prefix': 'W-',
'type_id': cls.dr_type.id,
'duration_count': 1,
'unit_of_time': WEEKLY,
'count': 3})
generator.action_apply()
# Create Demand Estimates:
ranges = cls.env['date.range'].search(
[('type_id', '=', cls.dr_type.id)])
qty = 140.0
for dr in ranges:
qty += 70.0
cls._create_demand_estimate(
cls.prod_test, cls.stock_location, dr, qty)
cls.multi_level_mrp_wiz.create({}).run_multi_level_mrp()
@classmethod
def _create_demand_estimate(cls, product, location, date_range, qty):
cls.estimate_obj.create({
'product_id': product.id,
'location_id': location.id,
'product_uom': product.uom_id.id,
'product_uom_qty': qty,
'date_range_id': date_range.id,
})
def test_01_mrp_levels(self):
"""Tests computation of MRP levels."""
self.assertEqual(self.fp_1.llc, 0)
@@ -271,7 +324,25 @@ class TestMultiLevelMRP(SavepointCase):
self.assertEqual(mrp_product.nbr_mrp_actions, 3)
self.assertEqual(mrp_product.nbr_mrp_actions_4w, 3)
def test_06_procure_mo(self):
def test_06_demand_estimates(self):
"""Tests demand estimates integration."""
estimates = self.estimate_obj.search(
[('product_id', '=', self.prod_test.id)])
self.assertEqual(len(estimates), 3)
moves = self.mrp_move_obj.search([
('product_id', '=', self.prod_test.id),
])
# 3 weeks - 3 days in the past = 18 days of valid estimates:
moves_from_estimates = moves.filtered(lambda m: m.mrp_type == 'd')
self.assertEqual(len(moves_from_estimates), 18)
quantities = moves_from_estimates.mapped('mrp_qty')
self.assertIn(-30.0, quantities) # 210 a week => 30.0 dayly:
self.assertIn(-40.0, quantities) # 280 a week => 40.0 dayly:
self.assertIn(-50.0, quantities) # 350 a week => 50.0 dayly:
actions = moves.filtered(lambda m: m.mrp_action == 'po')
self.assertEqual(len(actions), 18)
def test_07_procure_mo(self):
"""Test procurement wizard with MOs."""
mos = self.mo_obj.search([
('product_id', '=', self.fp_1.id)])

View File

@@ -1,74 +0,0 @@
<?xml version="1.0"?>
<odoo>
<record model="ir.ui.view" id="mrp_forecast_tree">
<field name="name">mrp.forecast.tree</field>
<field name="model">mrp.forecast.product</field>
<field name="type">form</field>
<field name="arch" type="xml">
<tree string="MRP Product Forecast">
<field name="mrp_area_id"/>
<field name="product_id"/>
<field name="qty_forecast_total"/>
<field name="qty_forecast_past"/>
<field name="qty_forecast_m0"/>
<field name="qty_forecast_m1"/>
<field name="qty_forecast_m2"/>
<field name="qty_forecast_m3"/>
<field name="qty_forecast_m4"/>
<field name="qty_forecast_m5"/>
<field name="qty_forecast_m6"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="mrp_forecast_form">
<field name="name">mrp.forecast.form</field>
<field name="model">mrp.forecast.product</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="MRP Product Forecast">
<group colspan="4" col="2">
<group>
<field name="mrp_area_id"/>
<field name="product_id"/>
</group>
</group>
<group colspan="2" col="2">
<separator string="Forecasts"/>
<field name="mrp_forecast_ids" nolabel="1" colspan="2" context="{'default_forecast_product_id': active_id}">
<tree string="Forecasts" editable="bottom">
<field name="forecast_product_id" invisible="True"/>
<field name="date"/>
<field name="qty_forecast"/>
</tree>
</field>
</group>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="mrp_forecast_action">
<field name="sequence" eval="20"/>
<field name="name">MRP Forecast</field>
<field name="res_model">mrp.forecast.product</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="mrp_forecast_tree"/>
<field name="act_window_id" ref="mrp_forecast_form"/>
</record>
<record model="ir.actions.act_window.view" id="mrp_forecast_form_action">
<field name="sequence" eval="22"/>
<field name="view_mode">form</field>
<field name="view_id" ref="mrp_forecast_form"/>
<field name="act_window_id" ref="mrp_forecast_action"/>
</record>
<record model="ir.actions.act_window.view" id="mrp_forecast_tree_action">
<field name="sequence" eval="20"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="mrp_forecast_tree"/>
<field name="act_window_id" ref="mrp_forecast_action"/>
</record>
</odoo>

View File

@@ -30,10 +30,4 @@
parent="menu_mrp_mrp"
sequence="40"/>
<menuitem name="Forecasted Products"
id="menu_mrp_forecast_products"
action="mrp_forecast_action"
parent="menu_mrp_mrp"
sequence="60"/>
</odoo>

View File

@@ -40,28 +40,24 @@ class MultiLevelMrp(models.TransientModel):
}
@api.model
def _prepare_mrp_move_data_from_forecast(self, fc, fc_id, mrpproduct):
def _prepare_mrp_move_data_from_forecast(
self, estimate, mrp_product, date):
mrp_type = 'd'
origin = 'fc'
mrp_date = date.today()
if datetime.date(datetime.strptime(fc_id.date,
'%Y-%m-%d')) > date.today():
mrp_date = datetime.date(datetime.strptime(
fc_id.date, '%Y-%m-%d'))
return {
'mrp_area_id': fc.mrp_area_id.id,
'product_id': fc.product_id.id,
'mrp_product_id': mrpproduct.id,
'mrp_area_id': mrp_product.mrp_area_id.id,
'product_id': mrp_product.product_id.id,
'mrp_product_id': mrp_product.id,
'production_id': None,
'purchase_order_id': None,
'purchase_line_id': None,
'sale_order_id': None,
'sale_line_id': None,
'stock_move_id': None,
'mrp_qty': -fc_id.qty_forecast,
'current_qty': -fc_id.qty_forecast,
'mrp_date': mrp_date,
'current_date': mrp_date,
'mrp_qty': -estimate.daily_qty,
'current_qty': -estimate.daily_qty,
'mrp_date': date,
'current_date': date,
'mrp_action': 'none',
'mrp_type': mrp_type,
'mrp_processed': False,
@@ -379,13 +375,6 @@ class MultiLevelMrp(models.TransientModel):
AND llc > 0;"""
self.env.cr.execute(sql_stat)
sql_stat = """
UPDATE product_product
SET mrp_applicable=True
FROM mrp_forecast_product
WHERE product_product.id = mrp_forecast_product.product_id;"""
self.env.cr.execute(sql_stat)
# self.env.cr.commit()
counter = 0
sql_stat = """
@@ -408,15 +397,27 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def _init_mrp_move_from_forecast(self, mrp_product):
forecast = self.env['mrp.forecast.product'].search(
[('product_id', '=', mrp_product.product_id.id),
('mrp_area_id', '=', mrp_product.mrp_area_id.id)])
for fc in forecast:
for fc_id in fc.mrp_forecast_ids:
locations = self.env['stock.location'].search(
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
today = fields.Date.today()
estimates = self.env['stock.demand.estimate'].search([
('product_id', '=', mrp_product.product_id.id),
('location_id', 'in', locations.ids),
('date_range_id.date_end', '>=', today)
])
for rec in estimates:
start = rec.date_range_id.date_start
if start < today:
start = today
mrp_date = fields.Date.from_string(start)
date_end = fields.Date.from_string(rec.date_range_id.date_end)
delta = timedelta(days=1)
while mrp_date <= date_end:
mrp_move_data = \
self._prepare_mrp_move_data_from_forecast(
fc, fc_id, mrp_product)
rec, mrp_product, mrp_date)
self.env['mrp.move'].create(mrp_move_data)
mrp_date += delta
return True
# TODO: move this methods to mrp_product?? to be able to show moves with an action