[11.0][REW/IMP] multi_level_mrp: major overhault

This commit is contained in:
Lois Rilo
2018-06-13 20:16:40 +02:00
committed by Jordi Ballester Alomar
parent 523ca538fe
commit a3a6992ba7
8 changed files with 640 additions and 393 deletions

View File

@@ -175,7 +175,7 @@ class MrpForecastProduct(models.Model):
'forecast_product_id',
'Forecast')
name = fields.Char(compute='_function_name', string='Description')
product_id = fields.Many2one('product.product', 'Product', select=True)
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')

View File

@@ -17,6 +17,7 @@ class MrpInventory(models.Model):
# TODO: compute procurement_date to pass to the wizard? not needed for PO at least. Check for MO and moves
# TODO: substract qty already procured.
# TODO: show a LT based on the procure method?
# TODO: add to_procure_date
mrp_area_id = fields.Many2one(
comodel_name='mrp.area', string='MRP Area',
@@ -24,7 +25,7 @@ class MrpInventory(models.Model):
)
mrp_product_id = fields.Many2one(
comodel_name='mrp.product', string='Product',
select=True,
index=True,
)
uom_id = fields.Many2one(
comodel_name='product.uom', string='Product UoM',

View File

@@ -1,6 +1,5 @@
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# © 2016 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# © 2016-18 Eficent Business and IT Consulting Services S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
@@ -9,43 +8,66 @@ from odoo import exceptions
class MrpMove(models.Model):
_name = 'mrp.move'
_order = 'mrp_product_id, mrp_date, mrp_type desc, id'
# TODO: too many indexes...
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
current_date = fields.Date('Current Date')
current_qty = fields.Float('Current Qty')
mrp_action = fields.Selection((('mo', 'Manufacturing Order'),
('po', 'Purchase Order'),
('pr', 'Purchase Request'),
('so', 'Sale Order'),
('push', 'Push'),
('pull', 'Pull'),
('cancel', 'Cancel'),
('none', 'None')),
'Action')
# TODO: remove purchase request and move to other module?
# TODO: cancel is not needed I think...
mrp_action = fields.Selection(
selection=[('mo', 'Manufacturing Order'),
('po', 'Purchase Order'),
('pr', 'Purchase Request'),
('so', 'Sale Order'),
('cancel', 'Cancel'),
('none', 'None')],
string='Action',
)
mrp_action_date = fields.Date('MRP Action Date')
mrp_date = fields.Date('MRP Date')
mrp_move_down_ids = fields.Many2many('mrp.move', 'mrp_move_rel',
'move_up_id', 'move_down_id',
'MRP Move DOWN')
mrp_move_up_ids = fields.Many2many('mrp.move', 'mrp_move_rel',
'move_down_id', 'move_up_id',
'MRP Move UP')
mrp_minimum_stock = fields.Float(string='Minimum Stock',
related='product_id.mrp_minimum_stock')
mrp_move_down_ids = fields.Many2many(
comodel_name='mrp.move',
relation='mrp_move_rel',
column1='move_up_id',
column2='move_down_id',
string='MRP Move DOWN',
)
mrp_move_up_ids = fields.Many2many(
comodel_name='mrp.move',
relation='mrp_move_rel',
column1='move_down_id',
column2='move_up_id',
string='MRP Move UP',
)
mrp_minimum_stock = fields.Float(
string='Minimum Stock',
related='product_id.mrp_minimum_stock',
)
mrp_order_number = fields.Char('Order Number')
mrp_origin = fields.Selection((('mo', 'Manufacturing Order'),
('po', 'Purchase Order'),
('pr', 'Purchase Request'),
('so', 'Sale Order'), ('mv','Move'),
('fc', 'Forecast'), ('mrp', 'MRP')),
'Origin')
# TODO: move purchase request to another module
mrp_origin = fields.Selection(
selection=[('mo', 'Manufacturing Order'),
('po', 'Purchase Order'),
('pr', 'Purchase Request'),
('so', 'Sale Order'),
('mv', 'Move'),
('fc', 'Forecast'), ('mrp', 'MRP')],
string='Origin')
mrp_processed = fields.Boolean('Processed')
mrp_product_id = fields.Many2one('mrp.product', 'Product', index=True)
mrp_qty = fields.Float('MRP Quantity')
mrp_type = fields.Selection((('s', 'Supply'), ('d', 'Demand')), 'Type')
mrp_type = fields.Selection(
selection=[('s', 'Supply'), ('d', 'Demand')],
string='Type',
)
name = fields.Char('Description')
parent_product_id = fields.Many2one('product.product',
'Parent Product', index=True)
parent_product_id = fields.Many2one(
comodel_name='product.product',
string='Parent Product', index=True,
)
product_id = fields.Many2one('product.product',
'Product', index=True)
production_id = fields.Many2one('mrp.production',
@@ -65,15 +87,12 @@ class MrpMove(models.Model):
('waiting', 'Waiting'),
('partially_available', 'Partially Available'),
('ready', 'Ready'),
('in_production', 'In Production'),
('picking_except', 'Picking Exception'),
('sent', 'Sent'), ('approved', 'Approved'),
('except_invoice', 'Invoice Exception')],
('sent', 'Sent'),
('to approve', 'To Approve'),
('approved', 'Approved')],
string='State',
)
stock_move_id = fields.Many2one('stock.move', 'Stock Move', index=True)
_order = 'mrp_product_id, mrp_date, mrp_type desc, id'
@api.model
def mrp_production_prepare(self, bom_id, routing_id):

View File

@@ -1,9 +1,8 @@
# © 2016 Ucamco - Wim Audenaert <wim.audenaert@ucamco.com>
# © 2016 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar <jordi.ballester@eficent.com>
# © 2016-18 Eficent Business and IT Consulting Services S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
from odoo import api, fields, models
class MrpProduct(models.Model):
@@ -12,38 +11,105 @@ class MrpProduct(models.Model):
mrp_area_id = fields.Many2one('mrp.area', 'MRP Area')
current_qty_available = fields.Float(string='Current Qty Available',
related='product_id.qty_available')
main_supplier_id = fields.Many2one('res.partner', 'Main Supplier',
select=True)
main_supplier_id = fields.Many2one(
comodel_name='res.partner', string='Main Supplier',
compute='_compute_main_supplier', store=True,
index=True,
)
mrp_inspection_delay = fields.Integer(
string='Inspection Delay', related='product_id.mrp_inspection_delay')
mrp_lead_time = fields.Float(string='Lead Time',
related='product_id.produce_delay')
mrp_llc = fields.Integer('Low Level Code', select=True)
mrp_lead_time = fields.Float(
string='Lead Time',
related='product_id.produce_delay',
)
mrp_llc = fields.Integer(
string='Low Level Code', index=True, readonly=True,
)
# TODO: minimun stock and max/min order qty assigned by area?
mrp_maximum_order_qty = fields.Float(
string='Maximum Order Qty', related='product_id.mrp_maximum_order_qty')
string='Maximum Order Qty', related='product_id.mrp_maximum_order_qty',
)
mrp_minimum_order_qty = fields.Float(
string='Minimum Order Qty', related='product_id.mrp_minimum_order_qty')
mrp_minimum_stock = fields.Float(string='Minimum Stock',
related='product_id.mrp_minimum_stock')
mrp_move_ids = fields.One2many('mrp.move', 'mrp_product_id', 'MRP Moves')
string='Minimum Order Qty', related='product_id.mrp_minimum_order_qty',
)
mrp_minimum_stock = fields.Float(
string='Minimum Stock',
related='product_id.mrp_minimum_stock',
)
mrp_move_ids = fields.One2many(
comodel_name='mrp.move', inverse_name='mrp_product_id',
string='MRP Moves',
)
mrp_nbr_days = fields.Integer(
string='Nbr. Days', related='product_id.mrp_nbr_days')
mrp_qty_available = fields.Float('MRP Qty Available')
mrp_qty_multiple = fields.Float(string='Qty Multiple',
related='product_id.mrp_qty_multiple')
# TODO: this was: mrp_transit_delay = fields.Integer(mrp_move_ids) ??¿?¿¿?
mrp_transit_delay = fields.Integer(related = 'product_id.mrp_transit_delay')
mrp_transit_delay = fields.Integer(related='product_id.mrp_transit_delay')
mrp_verified = fields.Boolean(string='MRP Verified',
related='product_id.mrp_verified')
name = fields.Char('Description')
nbr_mrp_actions = fields.Integer('Nbr Actions', select=True)
nbr_mrp_actions_4w = fields.Integer('Nbr Actions 4 Weeks', select=True)
product_id = fields.Many2one('product.product', 'Product', select=True)
# TODO: rename to mrp_action_count?
nbr_mrp_actions = fields.Integer(
string='Nbr Actions', index=True,
)
nbr_mrp_actions_4w = fields.Integer(
string='Nbr Actions 4 Weeks', index=True,
)
product_id = fields.Many2one(
comodel_name='product.product', string='Product',
index=True,
)
product_tmpl_id = fields.Many2one('product.template', 'Product Template',
related='product_id.product_tmpl_id')
# TODO: extension to purchase requisition in other module?
# purchase_requisition = fields.Boolean(string='Purchase Requisition',
# related='product_id.purchase_requisition')
supply_method = fields.Selection((('buy', 'Buy'),
('produce', 'Produce')),
'Supply Method')
supply_method = fields.Selection(
selection=[('buy', 'Buy'),
('none', 'Undefined'),
('manufacture', 'Produce'),
('move', 'Transfer')],
string='Supply Method',
compute='_compute_supply_method', store=True,
)
@api.multi
@api.depends('mrp_area_id')
def _compute_supply_method(self):
group_obj = self.env['procurement.group']
for rec in self:
values = {
'warehouse_id': rec.mrp_area_id.warehouse_id,
'company_id': self.env.user.company_id.id, # TODO: better way to get company
}
rule = group_obj._get_rule(
rec.product_id, rec.mrp_area_id.location_id, values)
rec.supply_method = rule.action if rule else 'none'
@api.multi
@api.depends('supply_method')
def _compute_main_supplier(self):
"""Simplified and similar to procurement.rule logic."""
for rec in self.filtered(lambda r: r.supply_method == 'buy'):
suppliers = rec.product_id.seller_ids.filtered(
lambda r: (not r.product_id or r.product_id == rec.product_id))
if not suppliers:
continue
rec.main_supplier_id = suppliers[0].name
@api.multi
def _adjust_qty_to_order(self, qty_to_order):
# TODO: consider mrp_qty_multiple?
self.ensure_one()
if not self.mrp_maximum_order_qty and not self.mrp_minimum_order_qty:
return qty_to_order
if qty_to_order < self.mrp_minimum_order_qty:
return self.mrp_minimum_order_qty
if self.mrp_maximum_order_qty and qty_to_order > \
self.mrp_maximum_order_qty:
qty = self.mrp_maximum_order_qty
else:
qty = qty_to_order
return qty

View File

@@ -19,14 +19,18 @@ class Product(models.Model):
mrp_applicable = fields.Boolean('MRP Applicable')
mrp_exclude = fields.Boolean('Exclude from MRP')
mrp_inspection_delay = fields.Integer('Inspection Delay', default=0)
mrp_maximum_order_qty = fields.Float('Maximum Order Qty', default=0.00)
mrp_minimum_order_qty = fields.Float('Minimum Order Qty', default=0.00)
mrp_maximum_order_qty = fields.Float(
string='Maximum Order Qty', default=0.0,
)
mrp_minimum_order_qty = fields.Float(
string='Minimum Order Qty', default=0.0,
)
mrp_minimum_stock = fields.Float('Minimum Stock')
mrp_nbr_days = fields.Integer('Nbr. Days', default=0,
help="Number of days to group demand for "
"this product during the MRP run, "
"in order to determine the quantity "
"to order.")
mrp_nbr_days = fields.Integer(
string='Nbr. Days', default=0,
help="Number of days to group demand for this product during the "
"MRP run, in order to determine the quantity to order.",
)
mrp_product_ids = fields.One2many('mrp.product',
'product_id', 'MRP Product data')
mrp_qty_multiple = fields.Float('Qty Multiple', default=1.00)

View File

@@ -2,7 +2,7 @@
# (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from datetime import datetime, timedelta
from datetime import date, datetime, timedelta
from odoo.tests.common import SavepointCase
from odoo import fields
@@ -13,27 +13,36 @@ class TestMultiLevelMRP(SavepointCase):
@classmethod
def setUpClass(cls):
super(TestMultiLevelMRP, cls).setUpClass()
cls.wiz_multi_level_mrp_model = cls.env['multi.level.mrp']
cls.stock_picking_model = cls.env['stock.picking']
cls.mrp_inventory_model = cls.env['mrp.inventory']
cls.mo_obj = cls.env['mrp.production']
cls.po_obj = cls.env['purchase.order']
cls.stock_picking_obj = cls.env['stock.picking']
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']
cls.mrp_product_obj = cls.env['mrp.product']
cls.mrp_move_obj = cls.env['mrp.move']
cls.fp_1 = cls.env.ref('multi_level_mrp.product_product_fp_1')
cls.fp_2 = cls.env.ref('multi_level_mrp.product_product_fp_2')
cls.sf_1 = cls.env.ref('multi_level_mrp.product_product_sf_1')
cls.sf_2 = cls.env.ref('multi_level_mrp.product_product_sf_2')
cls.pp_1 = cls.env.ref('multi_level_mrp.product_product_pp_1')
cls.pp_2 = cls.env.ref('multi_level_mrp.product_product_pp_2')
cls.vendor = cls.env.ref('multi_level_mrp.res_partner_lazer_tech')
cls.wh = cls.env.ref('stock.warehouse0')
cls.stock_location = cls.wh.lot_stock_id
cls.customer_location = cls.env.ref(
'stock.stock_location_customers')
# Create test picking:
date_move = datetime.today() + timedelta(days=7)
cls.picking_1 = cls.stock_picking_model.create({
cls.picking_1 = cls.stock_picking_obj.create({
'picking_type_id': cls.env.ref('stock.picking_type_out').id,
'location_id': cls.stock_location.id,
'location_dest_id': cls.customer_location.id,
'move_lines': [
(0, 0, {
'name': 'Test move pf-1',
'name': 'Test move fp-1',
'product_id': cls.fp_1.id,
'date_expected': date_move,
'date': date_move,
@@ -54,7 +63,44 @@ class TestMultiLevelMRP(SavepointCase):
})]
})
cls.picking_1.action_confirm()
cls.wiz_multi_level_mrp_model.create({}).run_multi_level_mrp()
# Create Test PO:
date_po = datetime.today() + timedelta(days=1)
cls.po = cls.po_obj.create({
'name': 'Test PO-001',
'partner_id': cls.vendor.id,
'order_line': [
(0, 0, {
'name': 'Test PP-2 line',
'product_id': cls.pp_2.id,
'date_planned': date_po,
'product_qty': 5.0,
'product_uom': cls.pp_2.uom_id.id,
'price_unit': 25.0,
})],
})
# Create test MO:
date_mo = datetime.today() + timedelta(days=9)
bom_fp_2 = cls.env.ref('multi_level_mrp.mrp_bom_fp_2')
cls.mo = cls.mo_obj.create({
'product_id': cls.fp_2.id,
'bom_id': bom_fp_2.id,
'product_qty': 12.0,
'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()
cls.date_3 = fields.Date.to_string(today + timedelta(days=3))
cls.date_5 = fields.Date.to_string(today + timedelta(days=5))
cls.date_6 = fields.Date.to_string(today + timedelta(days=6))
cls.date_7 = fields.Date.to_string(today + timedelta(days=7))
cls.date_8 = fields.Date.to_string(today + timedelta(days=8))
cls.date_9 = fields.Date.to_string(today + timedelta(days=9))
cls.date_10 = fields.Date.to_string(today + timedelta(days=10))
def test_01_mrp_levels(self):
"""Tests computation of MRP levels."""
@@ -65,61 +111,172 @@ class TestMultiLevelMRP(SavepointCase):
self.assertEqual(self.pp_1.llc, 2)
self.assertEqual(self.pp_2.llc, 2)
def test_02_multi_level_mrp(self):
def test_02_mrp_product(self):
"""Tests that mrp products are generated correctly."""
mrp_product = self.mrp_product_obj.search([
('product_id', '=', self.pp_1.id)])
self.assertEqual(mrp_product.supply_method, 'buy')
self.assertEqual(mrp_product.main_supplier_id, self.vendor)
self.assertEqual(mrp_product.mrp_qty_available, 10.0)
mrp_product = self.mrp_product_obj.search([
('product_id', '=', self.sf_1.id)])
self.assertEqual(mrp_product.supply_method, 'manufacture')
def test_03_mrp_moves(self):
"""Tests for mrp moves generated."""
moves = self.mrp_move_obj.search([
('product_id', '=', self.pp_1.id),
('mrp_action', '=', 'none'),
])
self.assertEqual(len(moves), 3)
self.assertNotIn('s', moves.mapped('mrp_type'))
for move in moves:
self.assertTrue(move.mrp_move_up_ids)
if move.mrp_move_up_ids.mrp_product_id.product_id == self.fp_1:
# Demand coming from FP-1
self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo')
self.assertEqual(move.mrp_qty, -200.0)
elif move.mrp_move_up_ids.mrp_product_id.product_id == self.sf_1:
# Demand coming from FP-2 -> SF-1
self.assertEqual(move.mrp_move_up_ids.mrp_action, 'mo')
if move.mrp_date == self.date_5:
self.assertEqual(move.mrp_qty, -90.0)
elif move.mrp_date == self.date_8:
self.assertEqual(move.mrp_qty, -72.0)
# Check actions:
moves = self.mrp_move_obj.search([
('product_id', '=', self.pp_1.id),
('mrp_action', '!=', 'none'),
])
self.assertEqual(len(moves), 3)
for move in moves:
self.assertEqual(move.mrp_action, 'po')
self.assertEqual(move.mrp_type, 's')
# Check PP-2 PO being accounted:
po_move = self.mrp_move_obj.search([
('product_id', '=', self.pp_2.id),
('mrp_action', '=', 'none'),
('mrp_type', '=', 's'),
])
self.assertEqual(len(po_move), 1)
self.assertEqual(po_move.purchase_order_id, self.po)
self.assertEqual(po_move.purchase_line_id, self.po.order_line)
def test_04_multi_level_mrp(self):
"""Tests MRP inventories created."""
# FP-1
fp_1_inventory_lines = self.mrp_inventory_model.search(
fp_1_inventory_lines = self.mrp_inventory_obj.search(
[('mrp_product_id.product_id', '=', self.fp_1.id)])
self.assertEqual(len(fp_1_inventory_lines), 1)
date_7 = fields.Date.to_string(datetime.today() + timedelta(days=7))
self.assertEqual(fp_1_inventory_lines.date, date_7)
self.assertEqual(fp_1_inventory_lines.date, self.date_7)
self.assertEqual(fp_1_inventory_lines.demand_qty, 100.0)
self.assertEqual(fp_1_inventory_lines.to_procure, 100.0)
# FP-2
fp_2_inventory_lines = self.mrp_inventory_model.search(
[('mrp_product_id.product_id', '=', self.fp_2.id)])
self.assertEqual(len(fp_2_inventory_lines), 1)
self.assertEqual(fp_2_inventory_lines.date, date_7)
self.assertEqual(fp_2_inventory_lines.demand_qty, 15.0)
self.assertEqual(fp_2_inventory_lines.to_procure, 15.0)
fp_2_line_1 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.fp_2.id),
('date', '=', self.date_7)])
self.assertEqual(len(fp_2_line_1), 1)
self.assertEqual(fp_2_line_1.demand_qty, 15.0)
self.assertEqual(fp_2_line_1.to_procure, 15.0)
# TODO: ask odoo to fix it... should be date10
fp_2_line_2 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.fp_2.id),
('date', '=', self.date_9)])
self.assertEqual(len(fp_2_line_2), 1)
self.assertEqual(fp_2_line_2.demand_qty, 0.0)
self.assertEqual(fp_2_line_2.to_procure, 0.0)
self.assertEqual(fp_2_line_2.supply_qty, 12.0)
# SF-1
sf_1_inventory_lines = self.mrp_inventory_model.search(
[('mrp_product_id.product_id', '=', self.sf_1.id)])
self.assertEqual(len(sf_1_inventory_lines), 1)
date_6 = fields.Date.to_string(datetime.today() + timedelta(days=6))
self.assertEqual(sf_1_inventory_lines.date, date_6)
self.assertEqual(sf_1_inventory_lines.demand_qty, 30.0)
self.assertEqual(sf_1_inventory_lines.to_procure, 30.0)
sf_1_line_1 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.sf_1.id),
('date', '=', self.date_6)])
self.assertEqual(len(sf_1_line_1), 1)
self.assertEqual(sf_1_line_1.demand_qty, 30.0)
self.assertEqual(sf_1_line_1.to_procure, 30.0)
sf_1_line_2 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.sf_1.id),
('date', '=', self.date_9)])
self.assertEqual(len(sf_1_line_2), 1)
self.assertEqual(sf_1_line_2.demand_qty, 24.0)
self.assertEqual(sf_1_line_2.to_procure, 24.0)
# SF-2
sf_2_inventory_lines = self.mrp_inventory_model.search(
[('mrp_product_id.product_id', '=', self.sf_2.id)])
self.assertEqual(len(sf_2_inventory_lines), 1)
self.assertEqual(sf_2_inventory_lines.date, date_6)
self.assertEqual(sf_2_inventory_lines.demand_qty, 45.0)
self.assertEqual(sf_2_inventory_lines.to_procure, 30.0)
sf_2_line_1 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.sf_2.id),
('date', '=', self.date_6)])
self.assertEqual(len(sf_2_line_1), 1)
self.assertEqual(sf_2_line_1.demand_qty, 45.0)
self.assertEqual(sf_2_line_1.to_procure, 30.0)
sf_2_line_2 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.sf_2.id),
('date', '=', self.date_9)])
self.assertEqual(len(sf_2_line_2), 1)
self.assertEqual(sf_2_line_2.demand_qty, 36.0)
self.assertEqual(sf_2_line_2.to_procure, 36.0)
# PP-1
pp_1_inventory_lines = self.mrp_inventory_model.search(
[('mrp_product_id.product_id', '=', self.pp_1.id)])
self.assertEqual(len(pp_1_inventory_lines), 1)
date_5 = fields.Date.to_string(datetime.today() + timedelta(days=5))
self.assertEqual(pp_1_inventory_lines.date, date_5)
self.assertEqual(pp_1_inventory_lines.demand_qty, 290.0)
self.assertEqual(pp_1_inventory_lines.to_procure, 280.0)
pp_1_line_1 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.pp_1.id),
('date', '=', self.date_5)])
self.assertEqual(len(pp_1_line_1), 1)
self.assertEqual(pp_1_line_1.demand_qty, 290.0)
self.assertEqual(pp_1_line_1.to_procure, 280.0)
pp_1_line_2 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.pp_1.id),
('date', '=', self.date_8)])
self.assertEqual(len(pp_1_line_2), 1)
self.assertEqual(pp_1_line_2.demand_qty, 72.0)
self.assertEqual(pp_1_line_2.to_procure, 72.0)
# PP-2
pp_2_line_1 = self.mrp_inventory_model.search([
pp_2_line_1 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.pp_2.id),
('date', '=', date_5)])
('date', '=', self.date_3)])
self.assertEqual(len(pp_2_line_1), 1)
self.assertEqual(pp_2_line_1.demand_qty, 360.0)
self.assertEqual(pp_2_line_1.to_procure, 360.0)
date_3 = fields.Date.to_string(datetime.today() + timedelta(days=3))
pp_2_line_2 = self.mrp_inventory_model.search([
self.assertEqual(pp_2_line_1.demand_qty, 90.0)
# 90.0 demand - 20.0 on hand - 5.0 on PO = 65.0
self.assertEqual(pp_2_line_1.to_procure, 65.0)
pp_2_line_2 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.pp_2.id),
('date', '=', date_3)])
('date', '=', self.date_5)])
self.assertEqual(len(pp_2_line_2), 1)
self.assertEqual(pp_2_line_2.demand_qty, 90.0)
self.assertEqual(pp_2_line_2.to_procure, 70.0)
self.assertEqual(pp_2_line_2.demand_qty, 360.0)
self.assertEqual(pp_2_line_2.to_procure, 360.0)
pp_2_line_3 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.pp_2.id),
('date', '=', self.date_6)])
self.assertEqual(len(pp_2_line_3), 1)
self.assertEqual(pp_2_line_3.demand_qty, 108.0)
self.assertEqual(pp_2_line_3.to_procure, 108.0)
pp_2_line_4 = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.pp_2.id),
('date', '=', self.date_8)])
self.assertEqual(len(pp_2_line_4), 1)
self.assertEqual(pp_2_line_4.demand_qty, 48.0)
self.assertEqual(pp_2_line_4.to_procure, 48.0)
# TODO: test procure wizard: dates...
def test_05_actions_count(self):
"""Test actions counters."""
pass
def test_06_procure_mo(self):
"""Test procurement wizard with MOs."""
mos = self.mo_obj.search([
('product_id', '=', self.fp_1.id)])
self.assertFalse(mos)
mrp_inv = self.mrp_inventory_obj.search([
('mrp_product_id.product_id', '=', self.fp_1.id)])
self.mrp_inventory_procure_wiz.with_context({
'active_model': 'mrp.inventory',
'active_ids': mrp_inv.ids,
'active_id': mrp_inv.id,
}).create({}).make_procurement()
mos = self.mo_obj.search([
('product_id', '=', self.fp_1.id)])
self.assertTrue(mos)
self.assertEqual(mos.product_qty, 100.0)
datetime_5 = fields.Datetime.to_string(
date.today() + timedelta(days=5))
self.assertEqual(mos.date_planned_start, datetime_5)
# TODO: test procure wizard: pos, multiple...
# TODO: test multiple destination IDS:...

View File

@@ -138,7 +138,7 @@
<filter string="With Actions in Coming 4 Weeks" name="actions4w" domain="[['nbr_mrp_actions_4w','!=',0]]"/>
<filter string="With Actions" name="actions" domain="[['nbr_mrp_actions','!=',0]]"/>
<filter string="Purchase Actions" domain="[['supply_method','=','buy'],['nbr_mrp_actions','!=',0]]"/>
<filter string="Manufacture Actions" domain="[['supply_method','=','produce'],['nbr_mrp_actions','!=',0]]"/>
<filter string="Manufacture Actions" domain="[['supply_method','=','manufacture'],['nbr_mrp_actions','!=',0]]"/>
<separator/>
<group expand="0" string="Group By...">
<filter string="Supply Method" context="{'group_by':'supply_method'}"/>

View File

@@ -4,6 +4,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models, exceptions, _
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from datetime import date, datetime, timedelta
import logging
@@ -13,25 +14,13 @@ logger = logging.getLogger(__name__)
class MultiLevelMrp(models.TransientModel):
_name = 'multi.level.mrp'
# TODO: fix supply_method calculation.
# TODO: dates are not being correctly computed for supply...
@api.model
def _prepare_mrp_product_data(self, product, mrp_area):
main_supplier_id = False
sequence = 9999
# TODO: All this should not be really needed, as at the time
# of procurement you will figure out these details.
for supplier in product.product_tmpl_id.seller_ids:
if supplier.sequence < sequence:
sequence = supplier.sequence
main_supplier_id = supplier.name.id
supply_method = 'produce'
for route in product.product_tmpl_id.route_ids:
if route.name == 'Buy':
supply_method = 'buy'
qty_available = 0.0
product_obj = self.env['product.product']
# TODO: move mrp_qty_available computation, maybe unreserved??
location_ids = self.env['stock.location'].search(
[('id', 'child_of', mrp_area.location_id.id)])
for location in location_ids:
@@ -44,11 +33,7 @@ class MultiLevelMrp(models.TransientModel):
'product_id': product.id,
'mrp_qty_available': product.qty_available,
'mrp_llc': product.llc,
'nbr_mrp_actions': 0,
'nbr_mrp_actions_4w': 0,
'name': product.name,
'supply_method': supply_method,
'main_supplier_id': main_supplier_id,
}
@api.model
@@ -86,146 +71,142 @@ class MultiLevelMrp(models.TransientModel):
}
@api.model
def _prepare_mrp_move_data_from_stock_move(self, mrp_product, move):
def _prepare_mrp_move_data_from_stock_move(
self, mrp_product, move, direction='in'):
# TODO: Clean up to reduce dependencies
if (move.location_id.usage == 'internal' and
if not((move.location_id.usage == 'internal' and
move.location_dest_id.usage != 'internal') \
or (move.location_id.usage != 'internal' and
move.location_dest_id.usage == 'internal'):
if move.location_id.usage == 'internal':
mrp_type = 'd'
productqty = -move.product_qty
else:
mrp_type = 's'
productqty = move.product_qty
po = None
po_line = None
so = None
so_line = None
mo = None
origin = None
order_number = None
parent_product_id = None
if move.purchase_line_id:
order_number = move.purchase_line_id.order_id.name
origin = 'po'
po = move.purchase_line_id.order_id.id
po_line = move.purchase_line_id.id
if move.production_id:
order_number = move.production_id.name
origin = 'mo'
mo = move.production_id.id
else:
# TODO: move.move_dest_id -> move.move_dest_ids
if move.move_dest_ids:
move_dest_id = move.move_dest_ids[0]
if move.move_dest_id.production_id:
order_number = \
move_dest_id.production_id.name
move.location_dest_id.usage == 'internal')):
# TODO: not sure about this...
return {}
if direction == 'out':
mrp_type = 'd'
product_qty = -move.product_qty
else:
mrp_type = 's'
product_qty = move.product_qty
po = po_line = so = so_line = None
mo = origin = order_number = parent_product_id = None
if move.purchase_line_id:
order_number = move.purchase_line_id.order_id.name
origin = 'po'
po = move.purchase_line_id.order_id.id
po_line = move.purchase_line_id.id
if move.production_id:
order_number = move.production_id.name
origin = 'mo'
mo = move.production_id.id
else:
# TODO: move.move_dest_id -> move.move_dest_ids. DONE, review
if move.move_dest_ids:
# move_dest_id = move.move_dest_ids[:1]
for move_dest_id in move.move_dest_ids:
if move_dest_id.production_id:
order_number = move_dest_id.production_id.name
origin = 'mo'
mo = move_dest_id.production_id.id
if move_dest_id.production_id.product_id:
parent_product_id = \
move_dest_id.production_id.product_id.id
else:
parent_product_id = \
move_dest_id.product_id.id
if order_number is None:
order_number = move.name
mrp_date = date.today()
if datetime.date(datetime.strptime(
move.date_expected, '%Y-%m-%d %H:%M:%S')) > date.today():
mrp_date = datetime.date(datetime.strptime(
move.date_expected, '%Y-%m-%d %H:%M:%S'))
return {
'mrp_area_id': mrp_product.mrp_area_id.id,
'product_id': move.product_id.id,
'mrp_product_id': mrp_product.id,
'production_id': mo,
'purchase_order_id': po,
'purchase_line_id': po_line,
'sale_order_id': so,
'sale_line_id': so_line,
'stock_move_id': move.id,
'mrp_qty': productqty,
'current_qty': productqty,
'mrp_date': mrp_date,
'current_date': move.date_expected,
'mrp_action': 'none',
'mrp_type': mrp_type,
'mrp_processed': False,
'mrp_origin': origin,
'mrp_order_number': order_number,
'parent_product_id': parent_product_id,
'running_availability': 0.00,
'name': order_number,
'state': move.state,
}
return {}
@api.model
def _prepare_mrp_move_data_supply(self, product, qty, mrp_date_supply,
mrp_action_date, mrp_action, name):
parent_product_id = move_dest_id.product_id.id
if order_number is None:
order_number = move.name
mrp_date = date.today()
if datetime.date(datetime.strptime(
move.date_expected,
DEFAULT_SERVER_DATETIME_FORMAT)) > date.today():
mrp_date = datetime.date(datetime.strptime(
move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT))
return {
'mrp_area_id': product.mrp_area_id.id,
'product_id': product.product_id.id,
'mrp_product_id': 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': qty,
'current_qty': None,
'mrp_date': mrp_date_supply,
'mrp_action_date': mrp_action_date,
'current_date': None,
'mrp_action': mrp_action,
'mrp_type': 's',
'mrp_processed': False,
'mrp_origin': None,
'mrp_order_number': None,
'parent_product_id': None,
'name': 'Supply: ' + name,
}
'mrp_area_id': mrp_product.mrp_area_id.id,
'product_id': move.product_id.id,
'mrp_product_id': mrp_product.id,
'production_id': mo,
'purchase_order_id': po,
'purchase_line_id': po_line,
'sale_order_id': so,
'sale_line_id': so_line,
'stock_move_id': move.id,
'mrp_qty': product_qty,
'current_qty': product_qty,
'mrp_date': mrp_date,
'current_date': move.date_expected,
'mrp_action': 'none',
'mrp_type': mrp_type,
'mrp_processed': False,
'mrp_origin': origin,
'mrp_order_number': order_number,
'parent_product_id': parent_product_id,
'running_availability': 0.00,
'name': order_number,
'state': move.state,
}
@api.model
def _prepare_mrp_move_data_bom_explosion(self, product, bomline, qty,
mrp_date_demand_2, bom, name):
mrp_products = self.env['mrp.product'].search(
[('mrp_area_id', '=', product.mrp_area_id.id),
('product_id', '=', bomline.product_id.id)], limit=1)
if not mrp_products:
raise exceptions.Warning(
_("No MRP product found"))
def _prepare_mrp_move_data_supply(
self, mrp_product, qty, mrp_date_supply, mrp_action_date,
mrp_action, name):
return {
'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': qty,
'current_qty': None,
'mrp_date': mrp_date_supply,
'mrp_action_date': mrp_action_date,
'current_date': None,
'mrp_action': mrp_action,
'mrp_type': 's',
'mrp_processed': False,
'mrp_origin': None,
'mrp_order_number': None,
'parent_product_id': None,
'name': 'Supply: ' + name,
}
return {
'mrp_area_id': product.mrp_area_id.id,
'product_id': bomline.product_id.id,
'mrp_product_id': mrp_products[0].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': -(qty * bomline.product_qty),
'current_qty': None,
'mrp_date': mrp_date_demand_2,
'current_date': None,
'mrp_action': 'none',
'mrp_type': 'd',
'mrp_processed': False,
'mrp_origin': 'mrp',
'mrp_order_number': None,
'parent_product_id': bom.product_id.id,
'name':
('Demand Bom Explosion: ' + name).replace(
'Demand Bom Explosion: Demand Bom '
'Explosion: ',
'Demand Bom Explosion: '),
}
@api.model
def _prepare_mrp_move_data_bom_explosion(
self, product, bomline, qty, mrp_date_demand_2, bom, name):
mrp_product = self._get_mrp_product_from_product_and_area(
bomline.product_id, product.mrp_area_id)
if not mrp_product:
raise exceptions.Warning(
_("No MRP product found"))
return {
'mrp_area_id': product.mrp_area_id.id,
'product_id': bomline.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': -(qty * bomline.product_qty), # TODO: review with UoM
'current_qty': None,
'mrp_date': mrp_date_demand_2,
'current_date': None,
'mrp_action': 'none',
'mrp_type': 'd',
'mrp_processed': False,
'mrp_origin': 'mrp',
'mrp_order_number': None,
'parent_product_id': bom.product_id.id,
'name':
('Demand Bom Explosion: ' + name).replace(
'Demand Bom Explosion: Demand Bom '
'Explosion: ',
'Demand Bom Explosion: '),
}
@api.model
def create_move(self, mrp_product_id, mrp_date, mrp_qty, name):
@@ -233,90 +214,73 @@ class MultiLevelMrp(models.TransientModel):
values = {}
if not isinstance(mrp_date, date):
mrp_date = datetime.date(datetime.strptime(mrp_date, '%Y-%m-%d'))
mrp_date = fields.Date.from_string(mrp_date)
if mrp_product_id.supply_method == 'buy':
# if mrp_product_id.purchase_requisition:
# mrp_action = 'pr'
# else:
mrp_action = 'po'
else:
# TODO: consider 'none'...
mrp_action = 'mo'
if mrp_date < date.today():
mrp_date_supply = date.today()
else:
mrp_date_supply = mrp_date
mrp_action_date = mrp_date - timedelta(
days=mrp_product_id.mrp_lead_time)
qty_ordered = 0.00
products = self.env['mrp.product'].search([('id', '=',
mrp_product_id)])
for product in products:
if product.supply_method == 'buy':
# if product.purchase_requisition:
# mrp_action = 'pr'
# else:
mrp_action = 'po'
else:
mrp_action = 'mo'
qty_to_order = mrp_qty
while qty_ordered < mrp_qty:
qty = mrp_product_id._adjust_qty_to_order(qty_to_order)
qty_to_order -= qty
move_data = self._prepare_mrp_move_data_supply(
mrp_product_id, qty, mrp_date_supply, mrp_action_date,
mrp_action, name)
mrpmove_id = self.env['mrp.move'].create(move_data)
qty_ordered = qty_ordered + qty
if mrp_date < date.today():
mrp_date_supply = date.today()
else:
mrp_date_supply = mrp_date
mrp_action_date = mrp_date-timedelta(days=product.mrp_lead_time)
qty_ordered = 0.00
qty_to_order = mrp_qty
while qty_ordered < mrp_qty:
qty = 0.00
if product.mrp_maximum_order_qty == 0.00 and \
product.mrp_minimum_order_qty == 0.00:
qty = qty_to_order
else:
if qty_to_order < product.mrp_minimum_order_qty:
qty = product.mrp_minimum_order_qty
else:
if product.mrp_maximum_order_qty and qty_to_order > \
product.mrp_maximum_order_qty:
qty = product.mrp_maximum_order_qty
else:
qty = qty_to_order
qty_to_order -= qty
move_data = self._prepare_mrp_move_data_supply(product, qty,
mrp_date_supply,
mrp_action_date,
mrp_action,
name)
mrpmove_id = self.env['mrp.move'].create(move_data)
qty_ordered = qty_ordered + qty
if mrp_action == 'mo':
mrp_date_demand = mrp_date-timedelta(days=product.mrp_lead_time)
if mrp_date_demand < date.today():
mrp_date_demand = date.today()
if not product.product_id.bom_ids:
if mrp_action == 'mo':
mrp_date_demand = mrp_date - timedelta(
days=mrp_product_id.mrp_lead_time)
if mrp_date_demand < date.today():
mrp_date_demand = date.today()
if not mrp_product_id.product_id.bom_ids:
continue
bomcount = 0
for bom in mrp_product_id.product_id.bom_ids:
if not bom.active or not bom.bom_line_ids:
continue
bomcount = 0
for bom in product.product_id.bom_ids:
if not bom.active or not bom.bom_line_ids:
bomcount += 1
if bomcount != 1:
continue
for bomline in bom.bom_line_ids:
if bomline.product_qty <= 0.00:
continue
bomcount += 1
if bomcount != 1:
if self._exclude_from_mrp(
mrp_product_id.mrp_area_id, bomline.product_id):
# Stop explosion.
continue
for bomline in bom.bom_line_ids:
if bomline.product_qty <= 0.00:
continue
if self._exclude_from_mrp(
product.mrp_area_id, bomline.product_id):
# Stop explosion.
continue
mrp_date_demand_2 = mrp_date_demand-timedelta(
days=(product.mrp_transit_delay+product.
mrp_inspection_delay))
move_data = \
self._prepare_mrp_move_data_bom_explosion(
product, bomline, qty,
mrp_date_demand_2,
bom, name)
mrpmove_id2 = self.env['mrp.move'].create(
move_data)
sql_stat = "INSERT INTO mrp_move_rel (" \
"move_up_id, " \
"move_down_id) values (%d, %d)" % \
(mrpmove_id, mrpmove_id2, )
self.env.cr.execute(sql_stat)
mrp_date_demand_2 = mrp_date_demand - timedelta( # TODO: review this...
days=(mrp_product_id.mrp_transit_delay +
mrp_product_id.mrp_inspection_delay))
move_data = \
self._prepare_mrp_move_data_bom_explosion(
mrp_product_id, bomline, qty,
mrp_date_demand_2,
bom, name)
mrpmove_id2 = self.env['mrp.move'].create(move_data)
sql_stat = "INSERT INTO mrp_move_rel (" \
"move_up_id, " \
"move_down_id) values (%d, %d)" % \
(mrpmove_id, mrpmove_id2, )
self.env.cr.execute(sql_stat)
values['qty_ordered'] = qty_ordered
log_msg = '%s' % qty_ordered
log_msg = '%s' % qty_ordered
logger.info(log_msg)
return values
@@ -452,30 +416,50 @@ class MultiLevelMrp(models.TransientModel):
self.env['mrp.move'].create(mrp_move_data)
return True
# TODO: move this methods to mrp_product?? to be able to show moves with an action
@api.model
def _in_stock_moves_domain(self, mrp_product):
locations = self.env['stock.location'].search(
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
return [
('product_id', '=', mrp_product.product_id.id),
('state', 'not in', ['done', 'cancel']),
('product_qty', '>', 0.00),
('location_id', 'not in', locations.ids),
('location_dest_id', 'in', locations.ids),
]
@api.model
def _out_stock_moves_domain(self, mrp_product):
locations = self.env['stock.location'].search(
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
return [
('product_id', '=', mrp_product.product_id.id),
('state', 'not in', ['done', 'cancel']),
('product_qty', '>', 0.00),
('location_id', 'in', locations.ids),
('location_dest_id', 'not in', locations.ids),
]
@api.model
def _init_mrp_move_from_stock_move(self, mrp_product):
# TODO: Should we exclude the quantity done from the moves?
move_obj = self.env['stock.move']
mrp_move_obj = self.env['mrp.move']
location_ids = self.env['stock.location'].search(
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
in_moves = move_obj.search(
[('product_id', '=', mrp_product.product_id.id),
('state', '!=', 'done'),
('state', '!=', 'cancel'),
('product_qty', '>', 0.00),
('location_id', 'in', location_ids.ids)])
out_moves = move_obj.search(
[('product_id', '=', mrp_product.product_id.id),
('state', '!=', 'done'),
('state', '!=', 'cancel'),
('product_qty', '>', 0.00),
('location_dest_id', 'in', location_ids.ids)])
moves = in_moves + out_moves
for move in moves:
move_data = self._prepare_mrp_move_data_from_stock_move(
mrp_product, move)
mrp_move_obj.create(move_data)
in_domain = self._in_stock_moves_domain(mrp_product)
in_moves = move_obj.search(in_domain)
out_domain = self._out_stock_moves_domain(mrp_product)
out_moves = move_obj.search(out_domain)
if in_moves:
for move in in_moves:
move_data = self._prepare_mrp_move_data_from_stock_move(
mrp_product, move, direction='in')
mrp_move_obj.create(move_data)
if out_moves:
for move in out_moves:
move_data = self._prepare_mrp_move_data_from_stock_move(
mrp_product, move, direction='out')
mrp_move_obj.create(move_data)
return True
# TODO: extension to purchase requisition in other module?
@@ -575,16 +559,16 @@ class MultiLevelMrp(models.TransientModel):
picking_type_ids = [ptype.id for ptype in picking_types]
orders = self.env['purchase.order'].search(
[('picking_type_id', 'in', picking_type_ids),
('state', 'in', ['draft', 'confirmed'])])
('state', 'in', ['draft', 'sent', 'to approve'])])
po_lines = self.env['purchase.order.line'].search(
[('order_id', 'in', orders.ids),
('product_qty', '>', 0.0),
('product_id', '=', mrp_product.product_id.id)])
for poline in po_lines:
for line in po_lines:
mrp_move_data = \
self._prepare_mrp_move_data_from_purchase_order(
poline, mrp_product)
line, mrp_product)
self.env['mrp.move'].create(mrp_move_data)
@api.model
@@ -620,14 +604,21 @@ class MultiLevelMrp(models.TransientModel):
}
@api.model
def _prepare_mrp_move_data_from_mrp_production_bom(self, mo, bomline,
mrp_date_demand,
mrp_product):
def _get_mrp_product_from_product_and_area(self, product, mrp_area):
return self.env['mrp.product'].search([
('product_id', '=', product.id),
('mrp_area_id', '=', mrp_area.id),
], limit=1)
@api.model
def _prepare_mrp_move_data_from_mrp_production_bom(
self, mo, bomline, mrp_date_demand, mrp_product):
line_mrp_product = self._get_mrp_product_from_product_and_area(
bomline.product_id, mrp_product.mrp_area_id)
return {
'mrp_area_id': mrp_product.mrp_area_id.id,
'product_id': bomline.product_id.id,
'mrp_product_id':
bomline.product_id.mrp_product_id.id,
'mrp_product_id': line_mrp_product.id,
'production_id': mo.id,
'purchase_order_id': None,
'purchase_line_id': None,
@@ -649,23 +640,28 @@ class MultiLevelMrp(models.TransientModel):
@api.model
def _init_mrp_move_from_mrp_production_bom(self, mo, mrp_product):
mrp_date = date.today()
mrp_date_demand = mrp_date-timedelta(
days=mrp_product.product_id.mrp_lead_time)
if mrp_date_demand < date.today():
mrp_date_demand = date.today()
# TODO: iniciate this from moves with MOs and for other moves from bom lines??
# TODO: fix this...
# mrp_date = date.today()
# mrp_date_demand = mrp_date - timedelta(
# days=mrp_product.product_id.produce_delay)
# if mrp_date_demand < date.today():
# mrp_date_demand = date.today()
mrp_date_demand = date.today()
if mo.bom_id and mo.bom_id.bom_line_ids:
for bomline in mo.bom_id.bom_line_ids:
if bomline.product_qty <= 0.00:
continue
if (bomline.date_start and datetime.date(
datetime.strptime(bomline.date_start, '%Y-%m-%d')) >=
mrp_date_demand):
continue
if (bomline.date_stop and datetime.date(
datetime.strptime(bomline.date_stop, '%Y-%m-%d')) <=
mrp_date_demand):
continue
# TODO: ['mrp.bom.line'].date_start does not exist in v11. Remove:
# if (bomline.date_start and datetime.date(
# datetime.strptime(bomline.date_start, '%Y-%m-%d')) >=
# mrp_date_demand):
# continue
# if (bomline.date_stop and datetime.date(
# datetime.strptime(bomline.date_stop, '%Y-%m-%d')) <=
# mrp_date_demand):
# continue
# TODO: add conditions to do this: ddmrp, not already existing MOs (MTO)...
mrp_move_data = \
self._prepare_mrp_move_data_from_mrp_production_bom(
mo, bomline, mrp_date_demand, mrp_product)
@@ -675,15 +671,18 @@ class MultiLevelMrp(models.TransientModel):
def _init_mrp_move_from_mrp_production(self, mrp_product):
location_ids = self.env['stock.location'].search(
[('id', 'child_of', mrp_product.mrp_area_id.location_id.id)])
production_orders = self.env['mrp.production'].search(
[('location_dest_id', 'in', location_ids.ids),
('product_qty', '>', 0.0),
('state', '=', 'draft')])
# TODO: there is no 'draft' state anymore. there will always be stock.moves
production_orders = self.env['mrp.production'].search([
('product_id', '=', mrp_product.product_id.id),
('location_dest_id', 'in', location_ids.ids),
('product_qty', '>', 0.0),
('state', 'in', ['confirmed', 'planned']),
]) # TODO: 'progress' as well?
for mo in production_orders:
mrp_move_data = \
self._prepare_mrp_move_data_from_mrp_production(
mo, mrp_product)
self.env['mrp.move'].create(mrp_move_data)
# mrp_move_data = \
# self._prepare_mrp_move_data_from_mrp_production(
# mo, mrp_product)
# self.env['mrp.move'].create(mrp_move_data)
self._init_mrp_move_from_mrp_production_bom(mo, mrp_product)
@api.model
@@ -693,7 +692,8 @@ class MultiLevelMrp(models.TransientModel):
# TODO: extension to purchase requisition in other module?
# self._init_mrp_move_from_purchase_requisition(mrp_product)
self._init_mrp_move_from_purchase_order(mrp_product)
self._init_mrp_move_from_mrp_production(mrp_product)
# TODO: not needed I think... check case when MO are partially done and posted...
# self._init_mrp_move_from_mrp_production(mrp_product)
@api.model
def _exclude_from_mrp(self, mrp_area, product):
@@ -716,6 +716,7 @@ class MultiLevelMrp(models.TransientModel):
init_counter, product.default_code)
logger.info(log_msg)
mrp_product = self._init_mrp_product(product, mrp_area)
# for mrp_product in self.env['mrp.product'].search([]):
self._init_mrp_move(mrp_product)
# self.env.cr.commit()
logger.info('END MRP INITIALISATION')
@@ -803,46 +804,45 @@ class MultiLevelMrp(models.TransientModel):
for mrp_product in mrp_products:
nbr_create = 0
onhand = mrp_product.mrp_qty_available
onhand = mrp_product.mrp_qty_available # TODO: qty unreserved?
if mrp_product.mrp_nbr_days == 0:
# todo: review ordering by date
for move in mrp_product.mrp_move_ids:
if move.mrp_action == 'none':
if (onhand + move.mrp_qty) < \
mrp_product.mrp_minimum_stock:
name = move.name
qtytoorder = \
mrp_product.mrp_minimum_stock - \
onhand - move.mrp_qty
cm = self.create_move(
mrp_product_id=mrp_product.id,
mrp_product_id=mrp_product,
mrp_date=move.mrp_date,
mrp_qty=qtytoorder, name=name)
mrp_qty=qtytoorder, name=move.name)
qty_ordered = cm['qty_ordered']
onhand += move.mrp_qty + qty_ordered
nbr_create += 1
else:
onhand += move.mrp_qty
else:
# TODO: review this
nbr_create = self._init_mrp_move_grouped_demand(
nbr_create, mrp_product)
if onhand < mrp_product.mrp_minimum_stock and \
nbr_create == 0:
name = 'Minimum Stock'
qtytoorder = mrp_product.mrp_minimum_stock - onhand
cm = self.create_move(mrp_product_id=mrp_product.id,
mrp_date=date.today(),
mrp_qty=qtytoorder, name=name)
cm = self.create_move(
mrp_product_id=mrp_product,
mrp_date=date.today(),
mrp_qty=qtytoorder,
name='Minimum Stock')
qty_ordered = cm['qty_ordered']
onhand += qty_ordered
counter += 1
# self.env.cr.commit()
log_msg = 'MRP CALCULATION LLC %s FINISHED - NBR PRODUCTS: %s' %(
log_msg = 'MRP CALCULATION LLC %s FINISHED - NBR PRODUCTS: %s' % (
llc - 1, counter)
logger.info(log_msg)
if llc < 0:
counter = 999999
# self.env.cr.commit()
logger.info('END MRP CALCULATION')