summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorArnaud Baes <arb@odoo.com>2018-12-04 12:51:58 +0000
committerArnaud Baes <arb@odoo.com>2019-01-09 14:00:49 +0000
commitb50d6a07db043a51aca6ab74235b9f63d7dcd176 (patch)
tree3ef3f8943b4ee975723ed787e60b5e4fb343ae0a /addons
parent380b84bd091cc9ce3b04ce7c178eac2a33333b0e (diff)
[IMP] sale_stock, sale_mrp: Kits quantities availability
With this commit, the warning message triggered when there are not enough quantity available of a kit is based on the minimal quantity of a kit it's possible to make, based on the quantities available of its components. However, as this quantity is based on the 'virtual_available' quantity, the values could be negative, wich doesn't really make sense for a kit. This is why in such a case, the quantity available reported by the warning will be set to 0. TaskID: 1915370
Diffstat (limited to 'addons')
-rw-r--r--addons/sale_mrp/models/__init__.py1
-rw-r--r--addons/sale_mrp/models/product_product.py39
-rw-r--r--addons/sale_mrp/models/sale_mrp.py42
-rw-r--r--addons/sale_stock/models/sale_order.py41
4 files changed, 106 insertions, 17 deletions
diff --git a/addons/sale_mrp/models/__init__.py b/addons/sale_mrp/models/__init__.py
index 75e0d652578..dce709157ba 100644
--- a/addons/sale_mrp/models/__init__.py
+++ b/addons/sale_mrp/models/__init__.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import product_product
from . import sale_mrp
diff --git a/addons/sale_mrp/models/product_product.py b/addons/sale_mrp/models/product_product.py
new file mode 100644
index 00000000000..ef00ceda2ff
--- /dev/null
+++ b/addons/sale_mrp/models/product_product.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class Product(models.Model):
+ _inherit = "product.product"
+
+ def _compute_quantities(self):
+ """ When the product is a kit, this override computes the fields :
+ - 'virtual_available'
+ - 'qty_available'
+ - 'incoming_qty'
+ - 'outgoing_qty'
+ """
+ for product in self:
+ bom_kit = self.env['mrp.bom']._bom_find(product=product, bom_type='phantom')
+ if bom_kit:
+ boms, bom_sub_lines = bom_kit.explode(product, 1)
+ ratios_virtual_available = []
+ ratios_qty_available = []
+ ratios_incoming_qty = []
+ ratios_outgoing_qty = []
+ for bom_line, bom_line_data in bom_sub_lines:
+ component = bom_line.product_id
+ uom_qty_per_kit = bom_line_data['qty'] / bom_line_data['original_qty']
+ qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id)
+ ratios_virtual_available.append(component.virtual_available / qty_per_kit)
+ ratios_qty_available.append(component.qty_available / qty_per_kit)
+ ratios_incoming_qty.append(component.incoming_qty / qty_per_kit)
+ ratios_outgoing_qty.append(component.outgoing_qty / qty_per_kit)
+ if bom_sub_lines:
+ product.virtual_available = min(ratios_virtual_available) // 1
+ product.qty_available = min(ratios_qty_available) // 1
+ product.incoming_qty = min(ratios_incoming_qty) // 1
+ product.outgoing_qty = min(ratios_incoming_qty) // 1
+ else:
+ super(Product, self)._compute_quantities()
diff --git a/addons/sale_mrp/models/sale_mrp.py b/addons/sale_mrp/models/sale_mrp.py
index fb471fd2823..1c59e1a5757 100644
--- a/addons/sale_mrp/models/sale_mrp.py
+++ b/addons/sale_mrp/models/sale_mrp.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models
-from odoo.tools import float_compare
+from odoo import api, fields, models, _
+from odoo.tools import float_compare, float_round
class SaleOrderLine(models.Model):
@@ -79,6 +79,44 @@ class SaleOrderLine(models.Model):
components[product] = {'qty': qty, 'uom': to_uom.id}
return components
+ def _check_availability(self, product_id):
+ """ If the 'product_id' is a kit, this method check if every component's
+ availability and catch every warning returned in order to merge them in a single
+ comprehensive warning
+ """
+ bom_kit = self.env['mrp.bom']._bom_find(product=product_id, bom_type='phantom')
+ if not bom_kit:
+ return super(SaleOrderLine, self)._check_availability(product_id)
+
+ kit = product_id
+ kit_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id)
+
+ # We check if we need to display the quantities of each missing components for all warehouses
+ kit_by_wh = self.product_id.with_context(warehouse=self.order_id.warehouse_id.id)
+ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
+ if float_compare(kit_by_wh.virtual_available, kit_qty, precision_digits=precision) != -1:
+ return {}
+ ignore_warehouse = float_compare(kit.virtual_available, kit_qty, precision_digits=precision) != -1
+
+ message = ''
+ boms, bom_sub_lines = bom_kit.explode(kit, kit_qty)
+ for bom_line, bom_line_data in bom_sub_lines:
+ component = bom_line.product_id
+ component_uom_qty = bom_line_data['qty']
+ component_qty = bom_line.product_uom_id._compute_quantity(component_uom_qty, bom_line.product_id.uom_id)
+ component_warning = self._check_availability_warning(component, component_qty, ignore_warehouse=ignore_warehouse)
+ if component_warning:
+ message += component_warning['warning']['message'] + '\n'
+
+ warning = {}
+ if message:
+ warning_mess = {
+ 'title': _('Not enough inventory!'),
+ 'message': message
+ }
+ warning = {'warning': warning_mess}
+ return warning
+
def _get_qty_procurement(self):
self.ensure_one()
# Specific case when we change the qty on a SO for a kit product.
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index c207ec6195b..8658f2d0290 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -256,29 +256,41 @@ class SaleOrderLine(models.Model):
if not self.product_id or not self.product_uom_qty or not self.product_uom:
self.product_packaging = False
return {}
- if self.product_id.type == 'product':
+ return self._check_availability(self.product_id)
+
+ def _check_availability(self, product_id):
+ """ The purpose of this method is only to be overriden if
+ 'product_id' is a kit
+ """
+ product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id)
+ return self._check_availability_warning(product_id, product_qty)
+
+ def _check_availability_warning(self, product_id, product_qty, ignore_warehouse=False):
+ if product_id.type == 'product':
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
- product = self.product_id.with_context(
+ product_by_wh = product_id.with_context(
warehouse=self.order_id.warehouse_id.id,
lang=self.order_id.partner_id.lang or self.env.user.lang or 'en_US'
)
- product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id)
- if float_compare(product.virtual_available, product_qty, precision_digits=precision) == -1:
- is_available = self._check_routing()
+ if float_compare(product_by_wh.virtual_available, product_qty, precision_digits=precision) == -1:
+ is_available = self._check_routing(product_id)
if not is_available:
- message = _('You plan to sell %s %s of %s but you only have %s %s available in %s warehouse.') % \
- (self.product_uom_qty, self.product_uom.name, self.product_id.name, product.virtual_available, product.uom_id.name, self.order_id.warehouse_id.name)
+ message = _('You plan to sell %s %s of %s but you only have %s %s available in %s warehouse.') % \
+ (self.product_uom_qty, self.product_uom.name, product_id.name,
+ product_by_wh.virtual_available,
+ product_by_wh.uom_id.name, self.order_id.warehouse_id.name)
# We check if some products are available in other warehouses.
- if float_compare(product.virtual_available, self.product_id.virtual_available, precision_digits=precision) == -1:
+ if not ignore_warehouse and float_compare(product_by_wh.virtual_available, product_id.virtual_available,
+ precision_digits=precision) == -1:
message += _('\nThere are %s %s available across all warehouses.\n\n') % \
- (self.product_id.virtual_available, product.uom_id.name)
+ (product_id.virtual_available, product_by_wh.uom_id.name)
for warehouse in self.env['stock.warehouse'].search([]):
- quantity = self.product_id.with_context(warehouse=warehouse.id).virtual_available
+ quantity = product_id.with_context(warehouse=warehouse.id).virtual_available
if quantity > 0:
- message += "%s: %s %s\n" % (warehouse.name, quantity, self.product_id.uom_id.name)
+ message += "%s: %s %s\n" % (warehouse.name, quantity, product_id.uom_id.name)
warning_mess = {
'title': _('Not enough inventory!'),
- 'message' : message
+ 'message': message
}
return {'warning': warning_mess}
return {}
@@ -399,14 +411,13 @@ class SaleOrderLine(models.Model):
}
return {}
-
- def _check_routing(self):
+ def _check_routing(self, product_id):
""" Verify the route of the product based on the warehouse
return True if the product availibility in stock does not need to be verified,
which is the case in MTO, Cross-Dock or Drop-Shipping
"""
is_available = False
- product_routes = self.route_id or (self.product_id.route_ids + self.product_id.categ_id.total_route_ids)
+ product_routes = self.route_id or (product_id.route_ids + product_id.categ_id.total_route_ids)
# Check MTO
wh_mto_route = self.order_id.warehouse_id.mto_pull_id.route_id