mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
[11.0][FIX] Consider *Qty Multiple* on product to propose the quantity to procure.
This commit is contained in:
@@ -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, '
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <https://github.com/OCA/manufacture/pull/297>`_)
|
||||
|
||||
11.0.1.0.1 (2018-08-03)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
2
mrp_multi_level/readme/ROADMAP.rst
Normal file
2
mrp_multi_level/readme/ROADMAP.rst
Normal file
@@ -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.
|
||||
@@ -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:...
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user