From fd60fa62cebaf15144a510d5a62afd2102ead224 Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Sat, 18 Aug 2018 16:31:23 +0200 Subject: [PATCH 1/4] enable MRP view in product templates --- mrp_multi_level/__manifest__.py | 3 +- mrp_multi_level/models/__init__.py | 3 +- .../models/{product.py => product_product.py} | 0 mrp_multi_level/models/product_template.py | 228 ++++++++++++++++++ ...duct_view.xml => product_product_view.xml} | 0 .../views/product_template_view.xml | 31 +++ 6 files changed, 263 insertions(+), 2 deletions(-) rename mrp_multi_level/models/{product.py => product_product.py} (100%) create mode 100644 mrp_multi_level/models/product_template.py rename mrp_multi_level/views/{product_view.xml => product_product_view.xml} (100%) create mode 100644 mrp_multi_level/views/product_template_view.xml diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index 09bad48d2e5..3b06b24eea9 100644 --- a/mrp_multi_level/__manifest__.py +++ b/mrp_multi_level/__manifest__.py @@ -24,7 +24,8 @@ 'security/mrp_multi_level_security.xml', 'security/ir.model.access.csv', 'views/mrp_area_view.xml', - 'views/product_view.xml', + 'views/product_product_view.xml', + 'views/product_template_view.xml', 'views/stock_location_view.xml', 'views/mrp_product_view.xml', 'wizards/mrp_inventory_procure_view.xml', diff --git a/mrp_multi_level/models/__init__.py b/mrp_multi_level/models/__init__.py index 353d6e3ed16..904be827780 100644 --- a/mrp_multi_level/models/__init__.py +++ b/mrp_multi_level/models/__init__.py @@ -1,6 +1,7 @@ from . import mrp_area from . import stock_location -from . import product +from . import product_product +from . import product_template from . import mrp_product from . import mrp_move from . import mrp_inventory diff --git a/mrp_multi_level/models/product.py b/mrp_multi_level/models/product_product.py similarity index 100% rename from mrp_multi_level/models/product.py rename to mrp_multi_level/models/product_product.py diff --git a/mrp_multi_level/models/product_template.py b/mrp_multi_level/models/product_template.py new file mode 100644 index 00000000000..6c9a3967a09 --- /dev/null +++ b/mrp_multi_level/models/product_template.py @@ -0,0 +1,228 @@ +# Copyright 2018 Eficent Business and IT Consulting Services S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + mrp_applicable = fields.Boolean(string='MRP Applicable', + compute='_compute_mrp_applicable', + inverse='_set_mrp_applicable', store=True + ) + mrp_exclude = fields.Boolean(string='Exclude from MRP', + compute='_compute_mrp_exclude', + inverse='_set_mrp_exclude', store=True + ) + mrp_inspection_delay = fields.Integer( + string='Inspection Delay', + compute='_compute_mrp_inspection_delay', + inverse='_set_mrp_inspection_delay', + store=True + ) + mrp_maximum_order_qty = fields.Float( + string='Maximum Order Qty', + compute='_compute_mrp_maximum_order_qty', + inverse='_set_mrp_maximum_order_qty', store=True + ) + mrp_minimum_order_qty = fields.Float( + string='Minimum Order Qty', + compute='_compute_mrp_minimum_order_qty', + inverse='_set_mrp_minimum_order_qty', store=True + ) + mrp_minimum_stock = fields.Float( + string='Minimum Stock', + compute='_compute_mrp_minimum_stock', + inverse='_set_mrp_minimum_stock', store=True + ) + mrp_nbr_days = fields.Integer( + string='Nbr. Days', + compute='_compute_mrp_nbr_days', + inverse='_set_mrp_nbr_days', store=True, + help="Number of days to group demand for this product during the " + "MRP run, in order to determine the quantity to order.", + ) + mrp_qty_multiple = fields.Float( + string='Qty Multiple', default=1.00, + compute='_compute_mrp_qty_multiple', + inverse='_set_mrp_qty_multiple', store=True + ) + mrp_transit_delay = fields.Integer( + string='Transit Delay', default=0, + compute='_compute_mrp_transit_delay', + inverse='_set_mrp_transit_delay', store=True + ) + mrp_verified = fields.Boolean( + string='Verified for MRP', + compute='_compute_mrp_verified', + inverse='_set_mrp_verified', store=True, + help="Identifies that this product has been verified " + "to be valid for the MRP.", + ) + + @api.depends('product_variant_ids', 'product_variant_ids.mrp_applicable') + def _compute_mrp_applicable(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_applicable = \ + template.product_variant_ids.mrp_applicable + for template in (self - unique_variants): + template.mrp_applicable = False + + @api.one + def _set_mrp_applicable(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_applicable = self.mrp_applicable + + @api.depends('product_variant_ids', 'product_variant_ids.mrp_exclude') + def _compute_mrp_exclude(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_exclude = \ + template.product_variant_ids.mrp_exclude + for template in (self - unique_variants): + template.mrp_exclude = False + + @api.one + def _set_mrp_exclude(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_exclude = self.mrp_exclude + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_inspection_delay') + def _compute_mrp_inspection_delay(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_inspection_delay = \ + template.product_variant_ids.mrp_inspection_delay + for template in (self - unique_variants): + template.mrp_inspection_delay = 0 + + @api.one + def _set_mrp_inspection_delay(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_inspection_delay = \ + self.mrp_inspection_delay + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_maximum_order_qty') + def _compute_mrp_maximum_order_qty(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_maximum_order_qty = \ + template.product_variant_ids.mrp_maximum_order_qty + for template in (self - unique_variants): + template.mrp_maximum_order_qty = 0.0 + + @api.one + def _set_mrp_maximum_order_qty(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_maximum_order_qty = \ + self.mrp_maximum_order_qty + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_minimum_order_qty') + def _compute_mrp_minimum_order_qty(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_minimum_order_qty = \ + template.product_variant_ids.mrp_minimum_order_qty + for template in (self - unique_variants): + template.mrp_minimum_order_qty = 0.0 + + @api.one + def _set_mrp_minimum_order_qty(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_minimum_order_qty = \ + self.mrp_minimum_order_qty + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_minimum_stock') + def _compute_mrp_minimum_stock(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_minimum_stock = \ + template.product_variant_ids.mrp_minimum_stock + for template in (self - unique_variants): + template.mrp_minimum_stock = 0.0 + + @api.one + def _set_mrp_minimum_stock(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_minimum_stock = \ + self.mrp_minimum_stock + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_nbr_days') + def _compute_mrp_nbr_days(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_nbr_days = \ + template.product_variant_ids.mrp_nbr_days + for template in (self - unique_variants): + template.mrp_nbr_days = 0.0 + + @api.one + def _set_mrp_nbr_days(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_nbr_days = \ + self.mrp_nbr_days + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_qty_multiple') + def _compute_mrp_qty_multiple(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_qty_multiple = \ + template.product_variant_ids.mrp_qty_multiple + for template in (self - unique_variants): + template.mrp_qty_multiple = 1 + + @api.one + def _set_mrp_qty_multiple(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_qty_multiple = \ + self.mrp_qty_multiple + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_transit_delay') + def _compute_mrp_transit_delay(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_transit_delay = \ + template.product_variant_ids.mrp_transit_delay + for template in (self - unique_variants): + template.mrp_transit_delay = 0.0 + + @api.one + def _set_mrp_transit_delay(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_transit_delay = \ + self.mrp_transit_delay + + @api.depends('product_variant_ids', + 'product_variant_ids.mrp_verified') + def _compute_mrp_verified(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1) + for template in unique_variants: + template.mrp_verified = \ + template.product_variant_ids.mrp_verified + for template in (self - unique_variants): + template.mrp_verified = 0.0 + + @api.one + def _set_mrp_verified(self): + if len(self.product_variant_ids) == 1: + self.product_variant_ids.mrp_verified = \ + self.mrp_verified diff --git a/mrp_multi_level/views/product_view.xml b/mrp_multi_level/views/product_product_view.xml similarity index 100% rename from mrp_multi_level/views/product_view.xml rename to mrp_multi_level/views/product_product_view.xml diff --git a/mrp_multi_level/views/product_template_view.xml b/mrp_multi_level/views/product_template_view.xml new file mode 100644 index 00000000000..97a45f396ad --- /dev/null +++ b/mrp_multi_level/views/product_template_view.xml @@ -0,0 +1,31 @@ + + + + + product.template.product.form.mrp + product.template + + + + + + + + + + + + + + + + + + + + + + + + + From 634e1ec3ccb35a6c7fe54f64e721db4608559bdb Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Thu, 30 Aug 2018 11:28:09 +0200 Subject: [PATCH 2/4] [11.0][FIX] Consider *Qty Multiple* on product to propose the quantity to procure. --- mrp_multi_level/__manifest__.py | 2 +- mrp_multi_level/models/mrp_product.py | 15 +-- mrp_multi_level/readme/HISTORY.rst | 6 ++ mrp_multi_level/readme/ROADMAP.rst | 2 + mrp_multi_level/tests/test_mrp_multi_level.py | 94 ++++++++++++++++++- .../wizards/mrp_inventory_procure.py | 16 +++- 6 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 mrp_multi_level/readme/ROADMAP.rst diff --git a/mrp_multi_level/__manifest__.py b/mrp_multi_level/__manifest__.py index 3b06b24eea9..0fd166eec4e 100644 --- a/mrp_multi_level/__manifest__.py +++ b/mrp_multi_level/__manifest__.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). { 'name': 'MRP Multi Level', - 'version': '11.0.1.0.1', + 'version': '11.0.1.1.0', 'development_status': 'Beta', 'license': 'AGPL-3', 'author': 'Ucamco, ' diff --git a/mrp_multi_level/models/mrp_product.py b/mrp_multi_level/models/mrp_product.py index 4ec561c063e..4a64c1b08eb 100644 --- a/mrp_multi_level/models/mrp_product.py +++ b/mrp_multi_level/models/mrp_product.py @@ -2,6 +2,8 @@ # © 2016-18 Eficent Business and IT Consulting Services S.L. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from math import ceil + from odoo import api, fields, models @@ -118,15 +120,16 @@ def _compute_main_supplier(self): @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: + if (not self.mrp_maximum_order_qty and not + self.mrp_minimum_order_qty and self.mrp_qty_multiple == 1.0): return qty_to_order if qty_to_order < self.mrp_minimum_order_qty: return self.mrp_minimum_order_qty + if self.mrp_qty_multiple: + multiplier = ceil(qty_to_order / self.mrp_qty_multiple) + qty_to_order = multiplier * self.mrp_qty_multiple 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 + return self.mrp_maximum_order_qty + return qty_to_order diff --git a/mrp_multi_level/readme/HISTORY.rst b/mrp_multi_level/readme/HISTORY.rst index 9ad7e0f128d..01bd8c4d1e2 100644 --- a/mrp_multi_level/readme/HISTORY.rst +++ b/mrp_multi_level/readme/HISTORY.rst @@ -1,3 +1,9 @@ +11.0.1.1.0 (2018-08-30) +~~~~~~~~~~~~~~~~~~~~~~~ + +* [FIX] Consider *Qty Multiple* on product to propose the quantity to procure. + (`#297 `_) + 11.0.1.0.1 (2018-08-03) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/mrp_multi_level/readme/ROADMAP.rst b/mrp_multi_level/readme/ROADMAP.rst new file mode 100644 index 00000000000..b3ae9a0e80d --- /dev/null +++ b/mrp_multi_level/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* The functionality related to field *Nbr. Days* in products is not + functional for the time being. Please, stay tuned to future updates. diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index af5fd2268e5..098b713b932 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -54,8 +54,38 @@ def setUpClass(cls): 'route_ids': [(6, 0, [route_buy])], 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 20.0})], }) + cls.prod_min = cls.product_obj.create({ + 'name': 'Product with minimum order qty', + 'type': 'product', + 'list_price': 50.0, + 'route_ids': [(6, 0, [route_buy])], + 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})], + 'mrp_minimum_order_qty': 50.0, + 'mrp_maximum_order_qty': 0.0, + 'mrp_qty_multiple': 1.0, + }) + cls.prod_max = cls.product_obj.create({ + 'name': 'Product with maximum order qty', + 'type': 'product', + 'list_price': 50.0, + 'route_ids': [(6, 0, [route_buy])], + 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})], + 'mrp_minimum_order_qty': 50.0, + 'mrp_maximum_order_qty': 100.0, + 'mrp_qty_multiple': 1.0, + }) + cls.prod_multiple = cls.product_obj.create({ + 'name': 'Product with qty multiple', + 'type': 'product', + 'list_price': 50.0, + 'route_ids': [(6, 0, [route_buy])], + 'seller_ids': [(0, 0, {'name': vendor1.id, 'price': 10.0})], + 'mrp_minimum_order_qty': 50.0, + 'mrp_maximum_order_qty': 500.0, + 'mrp_qty_multiple': 25.0, + }) - # Create test picking: + # Create test picking for FP-1 and FP-2: res = cls.calendar.plan_days(7+1, datetime.today()) date_move = res.date() cls.picking_1 = cls.stock_picking_obj.create({ @@ -86,6 +116,45 @@ def setUpClass(cls): }) cls.picking_1.action_confirm() + # Create test picking for procure qty adjustment tests: + cls.picking_2 = 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 prod_min', + 'product_id': cls.prod_min.id, + 'date_expected': date_move, + 'date': date_move, + 'product_uom': cls.prod_min.uom_id.id, + 'product_uom_qty': 16, + 'location_id': cls.stock_location.id, + 'location_dest_id': cls.customer_location.id + }), + (0, 0, { + 'name': 'Test move prod_max', + 'product_id': cls.prod_max.id, + 'date_expected': date_move, + 'date': date_move, + 'product_uom': cls.prod_max.uom_id.id, + 'product_uom_qty': 140, + 'location_id': cls.stock_location.id, + 'location_dest_id': cls.customer_location.id + }), + (0, 0, { + 'name': 'Test move prod_multiple', + 'product_id': cls.prod_multiple.id, + 'date_expected': date_move, + 'date': date_move, + 'product_uom': cls.prod_multiple.uom_id.id, + 'product_uom_qty': 112, + 'location_id': cls.stock_location.id, + 'location_dest_id': cls.customer_location.id + })] + }) + cls.picking_2.action_confirm() + # Create Test PO: date_po = cls.calendar.plan_days(1+1, datetime.today()).date() cls.po = cls.po_obj.create({ @@ -372,5 +441,28 @@ def test_07_procure_mo(self): mo_date_start = mos.date_planned_start.split(' ')[0] self.assertEqual(mo_date_start, self.date_5) + def test_08_adjust_qty_to_order(self): + """Test the adjustments made to the qty to procure when minimum, + maximum order quantities and quantity multiple are set.""" + # minimum order quantity: + mrp_inv_min = self.mrp_inventory_obj.search([ + ('mrp_product_id.product_id', '=', self.prod_min.id)]) + self.assertEqual(mrp_inv_min.to_procure, 50.0) + # maximum order quantity: + mrp_inv_max = self.mrp_inventory_obj.search([ + ('mrp_product_id.product_id', '=', self.prod_max.id)]) + self.assertEqual(mrp_inv_max.to_procure, 150) + moves = self.mrp_move_obj.search([ + ('product_id', '=', self.prod_max.id), + ('mrp_action', '!=', 'none'), + ]) + self.assertEqual(len(moves), 2) + self.assertIn(100.0, moves.mapped('mrp_qty')) + self.assertIn(50.0, moves.mapped('mrp_qty')) + # quantity multiple: + mrp_inv_multiple = self.mrp_inventory_obj.search([ + ('mrp_product_id.product_id', '=', self.prod_multiple.id)]) + self.assertEqual(mrp_inv_multiple.to_procure, 125) + # TODO: test procure wizard: pos, multiple... # TODO: test multiple destination IDS:... diff --git a/mrp_multi_level/wizards/mrp_inventory_procure.py b/mrp_multi_level/wizards/mrp_inventory_procure.py index 33bfde765ba..388a2e91a9b 100644 --- a/mrp_multi_level/wizards/mrp_inventory_procure.py +++ b/mrp_multi_level/wizards/mrp_inventory_procure.py @@ -17,9 +17,9 @@ class MrpInventoryProcure(models.TransientModel): ) @api.model - def _prepare_item(self, mrp_inventory): + def _prepare_item(self, mrp_inventory, qty_override=0.0): return { - 'qty': mrp_inventory.to_procure, + 'qty': qty_override if qty_override else mrp_inventory.to_procure, 'uom_id': mrp_inventory.uom_id.id, 'date_planned': mrp_inventory.date, 'mrp_inventory_id': mrp_inventory.id, @@ -57,7 +57,17 @@ def default_get(self, fields): items = item_obj = self.env['mrp.inventory.procure.item'] for line in mrp_inventory_obj.browse(mrp_inventory_ids): - items += item_obj.create(self._prepare_item(line)) + max_order = line.mrp_product_id.mrp_maximum_order_qty + qty_to_order = line.to_procure + if max_order and max_order < qty_to_order: + # split the procurement in batches: + while qty_to_order > 0.0: + qty = line.mrp_product_id._adjust_qty_to_order( + qty_to_order) + items += item_obj.create(self._prepare_item(line, qty)) + qty_to_order -= qty + else: + items += item_obj.create(self._prepare_item(line)) res['item_ids'] = [(6, 0, items.ids)] return res From de32d88fbe59a051a7ae69e3623bd8bd432050bf Mon Sep 17 00:00:00 2001 From: Jordi Ballester Alomar Date: Wed, 12 Sep 2018 12:23:16 +0200 Subject: [PATCH 3/4] mrp_multi_level: * during bom explosion consider only stockable products in the BOM. * add logo * add rounding on application of stock demand estimates. --- mrp_multi_level/static/description/icon.png | Bin 0 -> 7222 bytes mrp_multi_level/wizards/mrp_multi_level.py | 13 +++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 mrp_multi_level/static/description/icon.png diff --git a/mrp_multi_level/static/description/icon.png b/mrp_multi_level/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b1cef6c4cc410514ad582e091cdab458d691cb9e GIT binary patch literal 7222 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 literal 0 HcmV?d00001 diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index fbe837af651..c734b81fa9c 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -9,7 +9,7 @@ from datetime import date, datetime, timedelta import locale import logging - +from odoo.tools.float_utils import float_round logger = logging.getLogger(__name__) ODOO_READ_GROUP_DAY_FORMAT = '%d %b %Y' @@ -45,6 +45,10 @@ def _prepare_mrp_move_data_from_forecast( self, estimate, mrp_product, date): mrp_type = 'd' origin = 'fc' + daily_qty = float_round( + estimate.daily_qty, + precision_rounding=mrp_product.product_id.uom_id.rounding, + rounding_method='HALF-UP') return { 'mrp_area_id': mrp_product.mrp_area_id.id, 'product_id': mrp_product.product_id.id, @@ -53,8 +57,8 @@ def _prepare_mrp_move_data_from_forecast( 'purchase_order_id': None, 'purchase_line_id': None, 'stock_move_id': None, - 'mrp_qty': -estimate.daily_qty, - 'current_qty': -estimate.daily_qty, + 'mrp_qty': -daily_qty, + 'current_qty': -daily_qty, 'mrp_date': date, 'current_date': date, 'mrp_action': 'none', @@ -257,7 +261,8 @@ def create_move(self, mrp_product_id, mrp_date, mrp_qty, name): if bomcount != 1: continue for bomline in bom.bom_line_ids: - if bomline.product_qty <= 0.00: + if bomline.product_qty <= 0.00 or \ + bomline.product_id.type != 'product': continue if self._exclude_from_mrp( mrp_product_id.mrp_area_id, From 7d210ca9dff4d082f5a9a5a5ba34faee46d83acb Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Wed, 24 Oct 2018 13:49:47 +0200 Subject: [PATCH 4/4] mrp_multi_level: date_planned must be passed on as datetime. --- mrp_multi_level/wizards/mrp_inventory_procure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mrp_multi_level/wizards/mrp_inventory_procure.py b/mrp_multi_level/wizards/mrp_inventory_procure.py index 388a2e91a9b..14b3bf6be03 100644 --- a/mrp_multi_level/wizards/mrp_inventory_procure.py +++ b/mrp_multi_level/wizards/mrp_inventory_procure.py @@ -132,7 +132,8 @@ class MrpInventoryProcureItem(models.TransientModel): def _prepare_procurement_values(self, group=False): return { - 'date_planned': self.date_planned, # TODO: play with this... + 'date_planned': fields.Datetime.to_string( + fields.Date.from_string(self.date_planned)), 'warehouse_id': self.warehouse_id, # 'company_id': self.company_id, # TODO: consider company 'group_id': group,