From fcf67f4fd93221e7093df2d12f1048bfc5592731 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Tue, 13 Feb 2024 16:17:56 +0100 Subject: [PATCH] [MIG] patch for reserved_qty and free_qty from v12 to v14 --- ...mrp-add_product_reserved_qty_free_qty.diff | 271 ++++++++++++++++++ ...ock_add_product_reserved_qty_free_qty.diff | 238 --------------- 2 files changed, 271 insertions(+), 238 deletions(-) create mode 100644 stock_usability/stock-mrp-add_product_reserved_qty_free_qty.diff delete mode 100644 stock_usability/stock_add_product_reserved_qty_free_qty.diff diff --git a/stock_usability/stock-mrp-add_product_reserved_qty_free_qty.diff b/stock_usability/stock-mrp-add_product_reserved_qty_free_qty.diff new file mode 100644 index 0000000..43146d1 --- /dev/null +++ b/stock_usability/stock-mrp-add_product_reserved_qty_free_qty.diff @@ -0,0 +1,271 @@ +diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py +index b0b3c8f2662..7fefa873014 100644 +--- a/addons/mrp/models/product.py ++++ b/addons/mrp/models/product.py +@@ -195,6 +195,7 @@ class ProductProduct(models.Model): + ratios_qty_available = [] + ratios_incoming_qty = [] + ratios_outgoing_qty = [] ++ ratios_reserved_qty = [] + ratios_free_qty = [] + + for component, bom_sub_lines in bom_sub_lines_grouped.items(): +@@ -219,6 +220,7 @@ class ProductProduct(models.Model): + "qty_available": float_round(component.qty_available, precision_rounding=rounding), + "incoming_qty": float_round(component.incoming_qty, precision_rounding=rounding), + "outgoing_qty": float_round(component.outgoing_qty, precision_rounding=rounding), ++ "reserved_qty": float_round(component.reserved_qty, precision_rounding=rounding), + "free_qty": float_round(component.free_qty, precision_rounding=rounding), + } + ) +@@ -226,6 +228,7 @@ class ProductProduct(models.Model): + ratios_qty_available.append(component_res["qty_available"] / qty_per_kit) + ratios_incoming_qty.append(component_res["incoming_qty"] / qty_per_kit) + ratios_outgoing_qty.append(component_res["outgoing_qty"] / qty_per_kit) ++ ratios_reserved_qty.append(component_res["reserved_qty"] / qty_per_kit) + ratios_free_qty.append(component_res["free_qty"] / qty_per_kit) + if bom_sub_lines and ratios_virtual_available: # Guard against all cnsumable bom: at least one ratio should be present. + res[product.id] = { +@@ -233,6 +236,7 @@ class ProductProduct(models.Model): + 'qty_available': min(ratios_qty_available) * bom_kits[product].product_qty // 1, + 'incoming_qty': min(ratios_incoming_qty) * bom_kits[product].product_qty // 1, + 'outgoing_qty': min(ratios_outgoing_qty) * bom_kits[product].product_qty // 1, ++ 'reserved_qty': min(ratios_reserved_qty) * bom_kits[product].product_qty // 1, + 'free_qty': min(ratios_free_qty) * bom_kits[product].product_qty // 1, + } + else: +@@ -241,6 +245,7 @@ class ProductProduct(models.Model): + 'qty_available': 0, + 'incoming_qty': 0, + 'outgoing_qty': 0, ++ 'reserved_qty': 0, + 'free_qty': 0, + } + +diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py +index e0b9b2f95d6..f50fb39eb5a 100644 +--- a/addons/stock/models/product.py ++++ b/addons/stock/models/product.py +@@ -50,6 +50,11 @@ class Product(models.Model): + "of its children.\n" + "Otherwise, this includes goods stored in any Stock Location " + "with 'internal' type.") ++ reserved_qty = fields.Float( ++ compute='_compute_quantities', compute_sudo=False, ++ digits='Product Unit of Measure', ++ search='_search_reserved_qty', ++ string='Reserved Quantity') + free_qty = fields.Float( + 'Free To Use Quantity ', compute='_compute_quantities', search='_search_free_qty', + digits='Product Unit of Measure', compute_sudo=False, +@@ -107,6 +112,7 @@ class Product(models.Model): + product.incoming_qty = res[product.id]['incoming_qty'] + product.outgoing_qty = res[product.id]['outgoing_qty'] + product.virtual_available = res[product.id]['virtual_available'] ++ product.reserved_qty = res[product.id]['reserved_qty'] + product.free_qty = res[product.id]['free_qty'] + # Services need to be set with 0.0 for all quantities + services = self - products +@@ -114,6 +120,7 @@ class Product(models.Model): + services.incoming_qty = 0.0 + services.outgoing_qty = 0.0 + services.virtual_available = 0.0 ++ services.reserved_qty = 0.0 + services.free_qty = 0.0 + + def _product_available(self, field_names=None, arg=False): +@@ -170,7 +177,7 @@ class Product(models.Model): + product_id = product.id + if not product_id: + res[product_id] = dict.fromkeys( +- ['qty_available', 'free_qty', 'incoming_qty', 'outgoing_qty', 'virtual_available'], ++ ['qty_available', 'reserved_qty', 'free_qty', 'incoming_qty', 'outgoing_qty', 'virtual_available'], + 0.0, + ) + continue +@@ -182,6 +189,7 @@ class Product(models.Model): + qty_available = quants_res.get(product_id, [0.0])[0] + reserved_quantity = quants_res.get(product_id, [False, 0.0])[1] + res[product_id]['qty_available'] = float_round(qty_available, precision_rounding=rounding) ++ res[product_id]['reserved_qty'] = float_round(reserved_quantity, precision_rounding=rounding) + res[product_id]['free_qty'] = float_round(qty_available - reserved_quantity, precision_rounding=rounding) + res[product_id]['incoming_qty'] = float_round(moves_in_res.get(product_id, 0.0), precision_rounding=rounding) + res[product_id]['outgoing_qty'] = float_round(moves_out_res.get(product_id, 0.0), precision_rounding=rounding) +@@ -308,13 +316,16 @@ class Product(models.Model): + # TDE FIXME: should probably clean the search methods + return self._search_product_quantity(operator, value, 'outgoing_qty') + ++ def _search_reserved_qty(self, operator, value): ++ return self._search_product_quantity(operator, value, 'reserved_qty') ++ + def _search_free_qty(self, operator, value): + return self._search_product_quantity(operator, value, 'free_qty') + + def _search_product_quantity(self, operator, value, field): + # TDE FIXME: should probably clean the search methods + # to prevent sql injections +- if field not in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty', 'free_qty'): ++ if field not in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty', 'reserved_qty', 'free_qty'): + raise UserError(_('Invalid domain left operand %s', field)) + if operator not in ('<', '>', '=', '!=', '<=', '>='): + raise UserError(_('Invalid domain operator %s', operator)) +@@ -464,6 +475,13 @@ class Product(models.Model): + action['domain'] = [('product_id', '=', self.id)] + return action + ++ def action_view_reserved_stock_move_lines(self): ++ action = self.action_view_stock_move_lines() ++ action['domain'].append(('state', 'not in', ('done', 'draft', 'cancel'))) ++ # stock.stock_move_line_action sets search_default_done to 1, so we force it to 0 ++ action['context'] = dict(self._context, search_default_done=0) ++ return action ++ + def action_view_related_putaway_rules(self): + self.ensure_one() + domain = [ +@@ -616,6 +634,18 @@ class ProductTemplate(models.Model): + qty_available = fields.Float( + 'Quantity On Hand', compute='_compute_quantities', search='_search_qty_available', + compute_sudo=False, digits='Product Unit of Measure') ++ reserved_qty = fields.Float( ++ compute='_compute_quantities', compute_sudo=False, ++ search='_search_reserved_qty', ++ digits='Product Unit of Measure', ++ string='Reserved Quantity') ++ free_qty = fields.Float( ++ compute='_compute_quantities', compute_sudo=False, ++ search='_search_free_qty', ++ digits='Product Unit of Measure', ++ string='Free To Use Quantity', ++ help="The free to use quantity corresponds to the quantity on hand " ++ "- reserved quantity") + virtual_available = fields.Float( + 'Forecasted Quantity', compute='_compute_quantities', search='_search_virtual_available', + compute_sudo=False, digits='Product Unit of Measure') +@@ -667,6 +697,8 @@ class ProductTemplate(models.Model): + res = self._compute_quantities_dict() + for template in self: + template.qty_available = res[template.id]['qty_available'] ++ template.reserved_qty = res[template.id]['reserved_qty'] ++ template.free_qty = res[template.id]['free_qty'] + template.virtual_available = res[template.id]['virtual_available'] + template.incoming_qty = res[template.id]['incoming_qty'] + template.outgoing_qty = res[template.id]['outgoing_qty'] +@@ -680,16 +712,22 @@ class ProductTemplate(models.Model): + prod_available = {} + for template in self: + qty_available = 0 ++ reserved_qty = 0 ++ free_qty = 0 + virtual_available = 0 + incoming_qty = 0 + outgoing_qty = 0 + for p in template.product_variant_ids: + qty_available += variants_available[p.id]["qty_available"] ++ reserved_qty += variants_available[p.id]["reserved_qty"] ++ free_qty += variants_available[p.id]["free_qty"] + virtual_available += variants_available[p.id]["virtual_available"] + incoming_qty += variants_available[p.id]["incoming_qty"] + outgoing_qty += variants_available[p.id]["outgoing_qty"] + prod_available[template.id] = { + "qty_available": qty_available, ++ "reserved_qty": reserved_qty, ++ "free_qty": free_qty, + "virtual_available": virtual_available, + "incoming_qty": incoming_qty, + "outgoing_qty": outgoing_qty, +@@ -717,6 +755,16 @@ class ProductTemplate(models.Model): + product_variant_ids = self.env['product.product'].search(domain) + return [('product_variant_ids', 'in', product_variant_ids.ids)] + ++ def _search_reserved_qty(self, operator, value): ++ domain = [('reserved_qty', operator, value)] ++ product_variants = self.env['product.product'].search(domain) ++ return [('product_variant_ids', 'in', product_variants.ids)] ++ ++ def _search_free_qty(self, operator, value): ++ domain = [('free_qty', operator, value)] ++ product_variants = self.env['product.product'].search(domain) ++ return [('product_variant_ids', 'in', product_variants.ids)] ++ + def _search_virtual_available(self, operator, value): + domain = [('virtual_available', operator, value)] + product_variant_ids = self.env['product.product'].search(domain) +@@ -859,6 +907,13 @@ class ProductTemplate(models.Model): + action['domain'] = [('product_id.product_tmpl_id', 'in', self.ids)] + return action + ++ def action_view_reserved_stock_move_lines(self): ++ action = self.action_view_stock_move_lines() ++ action['domain'].append(('state', 'not in', ('done', 'draft', 'cancel'))) ++ # stock.stock_move_line_action sets search_default_done to 1, so we force it to 0 ++ action['context'] = dict(self._context, search_default_done=0) ++ return action ++ + def action_open_product_lot(self): + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id("stock.action_production_lot_form") +diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml +index 7655c15ac09..0897952c79d 100644 +--- a/addons/stock/views/product_views.xml ++++ b/addons/stock/views/product_views.xml +@@ -40,7 +40,9 @@ + + + +- ++ ++ ++ + + + +@@ -53,7 +55,9 @@ + + + +- ++ ++ ++ + + + +@@ -234,6 +238,18 @@ + + On Hand + ++ ++ + ++ + -+ - -+ -