From a718f91f2b6c79b55ab7018b6d0a90457bfd8b01 Mon Sep 17 00:00:00 2001 From: Lois Rilo Date: Thu, 30 Aug 2018 11:28:09 +0200 Subject: [PATCH] [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 3b06b24ee..0fd166eec 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 4ec561c06..4a64c1b08 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 @@ class MrpProduct(models.Model): @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 9ad7e0f12..01bd8c4d1 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 000000000..b3ae9a0e8 --- /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 af5fd2268..098b713b9 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 @@ class TestMrpMultiLevel(SavepointCase): '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 @@ class TestMrpMultiLevel(SavepointCase): }) 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 @@ class TestMrpMultiLevel(SavepointCase): 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 33bfde765..388a2e91a 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 @@ class MrpInventoryProcure(models.TransientModel): 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