[REF] stock*,purchase,mrp: used groupby of odoo

Instead of using sort + groupby of itertools (which group only
consecutive), use only the groupby of odoo.tools which
decrease the complexity of the code and avoid unmatched keys
between sort keys and groupby keys

task-2648449

Part-of: odoo/odoo#76761
This commit is contained in:
Rémy Voet (ryv)
2021-09-27 16:55:58 +00:00
parent 33cd7305c0
commit e8d2920d9f
14 changed files with 43 additions and 129 deletions
+1 -7
View File
@@ -1668,12 +1668,6 @@ class MrpProduction(models.Model):
def _log_downside_manufactured_quantity(self, moves_modification, cancel=False):
def _keys_in_sorted(move):
""" sort by picking and the responsible for the product the
move.
"""
return (move.picking_id.id, move.product_id.responsible_id.id)
def _keys_in_groupby(move):
""" group by picking and the responsible for the product the
move.
@@ -1689,7 +1683,7 @@ class MrpProduction(models.Model):
}
return self.env.ref('mrp.exception_on_mo')._render(values=values)
documents = self.env['stock.picking']._log_activity_get_documents(moves_modification, 'move_dest_ids', 'DOWN', _keys_in_sorted, _keys_in_groupby)
documents = self.env['stock.picking']._log_activity_get_documents(moves_modification, 'move_dest_ids', 'DOWN', _keys_in_groupby)
documents = self.env['stock.picking']._less_quantities_than_expected_add_documents(moves_modification, documents)
self.env['stock.picking']._log_activity(_render_note_exception_quantity_mo, documents)
+2 -2
View File
@@ -2,9 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from itertools import groupby
from odoo import api, fields, models
from odoo.tools import groupby
from odoo.tools.float_utils import float_round, float_is_zero
@@ -257,7 +257,7 @@ class ProductProduct(models.Model):
self.ensure_one()
if not product_template_attribute_value_ids:
return True
for _, iter_ptav in groupby(product_template_attribute_value_ids.sorted('attribute_line_id'), lambda ptav: ptav.attribute_line_id):
for _, iter_ptav in groupby(product_template_attribute_value_ids, lambda ptav: ptav.attribute_line_id):
if not any(ptav in self.product_template_attribute_value_ids for ptav in iter_ptav):
return False
return True
-6
View File
@@ -399,12 +399,6 @@ class StockMove(models.Model):
res['cost_share'] = sum(self.mapped('cost_share'))
return res
@api.model
def _prepare_merge_move_sort_method(self, move):
keys_sorted = super()._prepare_merge_move_sort_method(move)
keys_sorted += [move.created_production_id.id, move.cost_share]
return keys_sorted
def _compute_kit_quantities(self, product_id, kit_qty, kit_bom, filters):
""" Computes the quantity delivered or received when a kit is sold or purchased.
A ratio 'qty_processed/qty_needed' is computed for each component, and the lowest one is kept
+1 -7
View File
@@ -63,17 +63,11 @@ class StockPicking(models.Model):
def _less_quantities_than_expected_add_documents(self, moves, documents):
documents = super(StockPicking, self)._less_quantities_than_expected_add_documents(moves, documents)
def _keys_in_sorted(move):
""" sort by picking and the responsible for the product the
move.
"""
return (move.raw_material_production_id.id, move.product_id.responsible_id.id)
def _keys_in_groupby(move):
""" group by picking and the responsible for the product the
move.
"""
return (move.raw_material_production_id, move.product_id.responsible_id)
production_documents = self._log_activity_get_documents(moves, 'move_dest_ids', 'DOWN', _keys_in_sorted, _keys_in_groupby)
production_documents = self._log_activity_get_documents(moves, 'move_dest_ids', 'DOWN', _keys_in_groupby)
return {**documents, **production_documents}
+1 -4
View File
@@ -2,8 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
from functools import partial
from itertools import groupby
import json
from markupsafe import escape, Markup
@@ -12,10 +10,9 @@ from werkzeug.urls import url_encode
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, formatLang, get_lang, groupby
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.misc import formatLang, get_lang
class PurchaseOrder(models.Model):
+1 -7
View File
@@ -151,12 +151,6 @@ class PurchaseOrder(models.Model):
def _log_decrease_ordered_quantity(self, purchase_order_lines_quantities):
def _keys_in_sorted(move):
""" sort by picking and the responsible for the product the
move.
"""
return (move.picking_id.id, move.product_id.responsible_id.id)
def _keys_in_groupby(move):
""" group by picking and the responsible for the product the
move.
@@ -175,7 +169,7 @@ class PurchaseOrder(models.Model):
}
return self.env.ref('purchase_stock.exception_on_po')._render(values=values)
documents = self.env['stock.picking']._log_activity_get_documents(purchase_order_lines_quantities, 'move_ids', 'DOWN', _keys_in_sorted, _keys_in_groupby)
documents = self.env['stock.picking']._log_activity_get_documents(purchase_order_lines_quantities, 'move_ids', 'DOWN', _keys_in_groupby)
filtered_documents = {}
for (parent, responsible), rendering_context in documents.items():
if parent._name == 'stock.picking':
-7
View File
@@ -26,13 +26,6 @@ class StockMove(models.Model):
distinct_fields += ['purchase_line_id', 'created_purchase_line_id']
return distinct_fields
@api.model
def _prepare_merge_move_sort_method(self, move):
move.ensure_one()
keys_sorted = super(StockMove, self)._prepare_merge_move_sort_method(move)
keys_sorted += [move.purchase_line_id.id, move.created_purchase_line_id.id]
return keys_sorted
def _get_price_unit(self):
""" Returns the unit price for the move"""
self.ensure_one()
+4 -14
View File
@@ -4,10 +4,10 @@
from collections import defaultdict
from datetime import datetime
from dateutil.relativedelta import relativedelta
from itertools import groupby
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.addons.stock.models.stock_rule import ProcurementException
from odoo.tools import groupby
class StockRule(models.Model):
@@ -115,9 +115,9 @@ class StockRule(models.Model):
procurements = self._merge_procurements(procurements_to_merge)
po_lines_by_product = {}
grouped_po_lines = groupby(po.order_line.filtered(lambda l: not l.display_type and l.product_uom == l.product_id.uom_po_id).sorted(lambda l: l.product_id.id), key=lambda l: l.product_id.id)
grouped_po_lines = groupby(po.order_line.filtered(lambda l: not l.display_type and l.product_uom == l.product_id.uom_po_id), key=lambda l: l.product_id.id)
for product, po_lines in grouped_po_lines:
po_lines_by_product[product] = self.env['purchase.order.line'].concat(*list(po_lines))
po_lines_by_product[product] = self.env['purchase.order.line'].concat(*po_lines)
po_line_values = []
for procurement in procurements:
po_lines = po_lines_by_product.get(procurement.product_id.id, self.env['purchase.order.line'])
@@ -176,12 +176,6 @@ class StockRule(models.Model):
procurement.values.get('product_description_variants'),\
(procurement.values.get('orderpoint_id') and not procurement.values.get('move_dest_ids')) and procurement.values['orderpoint_id']
@api.model
def _get_procurements_to_merge_sorted(self, procurement):
return procurement.product_id.id, procurement.product_uom.id, procurement.values['propagate_cancel'],\
procurement.values.get('product_description_variants'),\
(procurement.values.get('orderpoint_id') and not procurement.values.get('move_dest_ids')) and procurement.values['orderpoint_id']
@api.model
def _get_procurements_to_merge(self, procurements):
""" Get a list of procurements values and create groups of procurements
@@ -190,11 +184,7 @@ class StockRule(models.Model):
sorted).
return list: procurements requests grouped by their product_id.
"""
procurements_to_merge = []
for k, procurements in groupby(sorted(procurements, key=self._get_procurements_to_merge_sorted), key=self._get_procurements_to_merge_groupby):
procurements_to_merge.append(list(procurements))
return procurements_to_merge
return [pro_g for __, pro_g in groupby(procurements, key=self._get_procurements_to_merge_groupby)]
@api.model
def _merge_procurements(self, procurements_to_merge):
+1 -12
View File
@@ -21,13 +21,6 @@ class StockMove(models.Model):
distinct_fields.append('sale_line_id')
return distinct_fields
@api.model
def _prepare_merge_move_sort_method(self, move):
move.ensure_one()
keys_sorted = super(StockMove, self)._prepare_merge_move_sort_method(move)
keys_sorted.append(move.sale_line_id.id)
return keys_sorted
def _get_related_invoices(self):
""" Overridden from stock_account to return the customer invoices
related to this stock move.
@@ -116,10 +109,6 @@ class StockPicking(models.Model):
new and old quantity as value. eg: {move_1 : (4, 5)}
"""
def _keys_in_sorted(sale_line):
""" sort by order_id and the sale_person on the order """
return (sale_line.order_id.id, sale_line.order_id.user_id.id)
def _keys_in_groupby(sale_line):
""" group by order_id and the sale_person on the order """
return (sale_line.order_id, sale_line.order_id.user_id)
@@ -144,7 +133,7 @@ class StockPicking(models.Model):
}
return self.env.ref('sale_stock.exception_on_picking')._render(values=values)
documents = self._log_activity_get_documents(moves, 'sale_line_id', 'DOWN', _keys_in_sorted, _keys_in_groupby)
documents = self._log_activity_get_documents(moves, 'sale_line_id', 'DOWN', _keys_in_groupby)
self._log_activity(_render_note_exception_quantity, documents)
return super(StockPicking, self)._log_less_quantities_than_expected(moves)
+12 -24
View File
@@ -4,14 +4,13 @@
from collections import defaultdict
from datetime import timedelta
from itertools import groupby
from operator import itemgetter
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
from odoo.tools.misc import clean_context, OrderedSet
from odoo.tools.misc import clean_context, OrderedSet, groupby
PROCUREMENT_PRIORITIES = [('0', 'Normal'), ('1', 'Urgent')]
@@ -805,15 +804,6 @@ class StockMove(models.Model):
'product_packaging_id',
]
@api.model
def _prepare_merge_move_sort_method(self, move):
move.ensure_one()
return [
move.product_id.id, move.price_unit, move.procure_method, move.location_id, move.location_dest_id,
move.product_uom.id, move.restrict_partner_id.id, move.scrapped, move.origin_returned_move_id.id,
move.package_level_id.id, move.propagate_cancel, move.description_picking or ""
]
def _clean_merged(self):
"""Cleanup hook used when merging moves"""
self.write({'propagate_cancel': False})
@@ -842,7 +832,7 @@ class StockMove(models.Model):
for candidate_moves in candidate_moves_list:
# First step find move to merge.
candidate_moves = candidate_moves.with_context(prefetch_fields=False)
for k, g in groupby(sorted(candidate_moves, key=self._prepare_merge_move_sort_method), key=itemgetter(*distinct_fields)):
for __, g in groupby(candidate_moves, key=itemgetter(*distinct_fields)):
moves = self.env['stock.move'].concat(*g).filtered(lambda m: m.state not in ('done', 'cancel', 'draft'))
# If we have multiple records we will merge then in a single one.
if len(moves) > 1:
@@ -994,7 +984,7 @@ class StockMove(models.Model):
def _key_assign_picking(self):
self.ensure_one()
return self.group_id, self.location_id, self.location_dest_id, self.picking_type_id
return (self.group_id, self.location_id, self.location_dest_id, self.picking_type_id)
def _search_picking_for_assignation_domain(self):
return [('group_id', '=', self.group_id.id),
@@ -1017,9 +1007,9 @@ class StockMove(models.Model):
type (moves should already have them identical). Otherwise, create a new
picking to assign them to. """
Picking = self.env['stock.picking']
grouped_moves = groupby(sorted(self, key=lambda m: [f.id for f in m._key_assign_picking()]), key=lambda m: [m._key_assign_picking()])
grouped_moves = groupby(self, key=lambda m: m._key_assign_picking())
for group, moves in grouped_moves:
moves = self.env['stock.move'].concat(*list(moves))
moves = self.env['stock.move'].concat(*moves)
new_picking = False
# Could pass the arguments contained in group but they are the same
# for each move that why moves[0] is acceptable
@@ -1345,13 +1335,12 @@ class StockMove(models.Model):
def _get_available_move_lines(move):
move_lines_in = move.move_orig_ids.filtered(lambda m: m.state == 'done').mapped('move_line_ids')
keys_in_groupby = ['location_dest_id', 'lot_id', 'result_package_id', 'owner_id']
def _keys_in_sorted(ml):
return (ml.location_dest_id.id, ml.lot_id.id, ml.result_package_id.id, ml.owner_id.id)
def _keys_in_groupby(ml):
return (ml.location_dest_id, ml.lot_id, ml.result_package_id, ml.owner_id)
grouped_move_lines_in = {}
for k, g in groupby(sorted(move_lines_in, key=_keys_in_sorted), key=itemgetter(*keys_in_groupby)):
for k, g in groupby(move_lines_in, key=_keys_in_groupby):
qty_done = 0
for ml in g:
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
@@ -1365,19 +1354,18 @@ class StockMove(models.Model):
moves_out_siblings_to_consider = moves_out_siblings & (StockMove.browse(assigned_moves_ids) + StockMove.browse(partially_available_moves_ids))
reserved_moves_out_siblings = moves_out_siblings.filtered(lambda m: m.state in ['partially_available', 'assigned'])
move_lines_out_reserved = (reserved_moves_out_siblings | moves_out_siblings_to_consider).mapped('move_line_ids')
keys_out_groupby = ['location_id', 'lot_id', 'package_id', 'owner_id']
def _keys_out_sorted(ml):
def _keys_out_groupby(ml):
return (ml.location_id.id, ml.lot_id.id, ml.package_id.id, ml.owner_id.id)
grouped_move_lines_out = {}
for k, g in groupby(sorted(move_lines_out_done, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)):
for k, g in groupby(move_lines_out_done, key=_keys_out_groupby):
qty_done = 0
for ml in g:
qty_done += ml.product_uom_id._compute_quantity(ml.qty_done, ml.product_id.uom_id)
grouped_move_lines_out[k] = qty_done
for k, g in groupby(sorted(move_lines_out_reserved, key=_keys_out_sorted), key=itemgetter(*keys_out_groupby)):
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
for k, g in groupby(move_lines_out_reserved, key=_keys_out_groupby):
grouped_move_lines_out[k] = sum(self.env['stock.move.line'].concat(*g).mapped('product_qty'))
available_move_lines = {key: grouped_move_lines_in[key] - grouped_move_lines_out.get(key, 0) for key in grouped_move_lines_in}
# pop key if the quantity available amount to 0
return dict((k, v) for k, v in available_move_lines.items() if v)
-1
View File
@@ -5,7 +5,6 @@ import logging
from collections import defaultdict
from datetime import datetime, time
from dateutil import relativedelta
from itertools import groupby
from psycopg2 import OperationalError
from odoo import SUPERUSER_ID, _, api, fields, models, registry
+6 -10
View File
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from itertools import groupby
from operator import itemgetter
from collections import defaultdict
from odoo import _, api, fields, models
from odoo.tools.misc import groupby
class StockPackageLevel(models.Model):
@@ -165,19 +165,15 @@ class StockPackageLevel(models.Model):
""" should compare in good uom """
all_in = True
pack_move_lines = self.move_line_ids
keys = ['product_id', 'lot_id']
def sorted_key(object):
object.ensure_one()
return [object.product_id.id, object.lot_id.id]
groupby_keys = ('product_id', 'lot_id')
grouped_quants = {}
for k, g in groupby(sorted(package.quant_ids, key=sorted_key), key=itemgetter(*keys)):
grouped_quants[k] = sum(self.env['stock.quant'].concat(*list(g)).mapped('quantity'))
for k, g in groupby(package.quant_ids, key=itemgetter(*groupby_keys)):
grouped_quants[k] = sum(self.env['stock.quant'].concat(*g).mapped('quantity'))
grouped_ops = {}
for k, g in groupby(sorted(pack_move_lines, key=sorted_key), key=itemgetter(*keys)):
grouped_ops[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped(field))
for k, g in groupby(pack_move_lines, key=itemgetter(*groupby_keys)):
grouped_ops[k] = sum(self.env['stock.move.line'].concat(*g).mapped(field))
if any(grouped_quants.get(key, 0) - grouped_ops.get(key, 0) != 0 for key in grouped_quants) \
or any(grouped_ops.get(key, 0) - grouped_quants.get(key, 0) != 0 for key in grouped_ops):
all_in = False
+14 -24
View File
@@ -5,7 +5,6 @@ import json
import time
from ast import literal_eval
from datetime import date, timedelta
from itertools import groupby
from operator import attrgetter, itemgetter
from collections import defaultdict
@@ -13,9 +12,8 @@ from odoo import SUPERUSER_ID, _, api, fields, models
from odoo.addons.stock.models.stock_move import PROCUREMENT_PRIORITIES
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, format_datetime
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, format_datetime, format_date, groupby
from odoo.tools.float_utils import float_compare, float_is_zero, float_round
from odoo.tools.misc import format_date
class PickingType(models.Model):
@@ -835,17 +833,16 @@ class Picking(models.Model):
""" This method checks that all product of the package (quant) are well present in the move_line_ids of the picking. """
all_in = True
pack_move_lines = self.move_line_ids.filtered(lambda ml: ml.package_id == package)
keys = ['product_id', 'lot_id']
keys_ids = ["{}.id".format(fname) for fname in keys]
precision_digits = self.env['decimal.precision'].precision_get('Product Unit of Measure')
groupby_keys = ('product_id', 'lot_id')
grouped_quants = {}
for k, g in groupby(sorted(package.quant_ids, key=attrgetter(*keys_ids)), key=itemgetter(*keys)):
grouped_quants[k] = sum(self.env['stock.quant'].concat(*list(g)).mapped('quantity'))
for k, g in groupby(package.quant_ids, key=itemgetter(*groupby_keys)):
grouped_quants[k] = sum(self.env['stock.quant'].concat(*g).mapped('quantity'))
grouped_ops = {}
for k, g in groupby(sorted(pack_move_lines, key=attrgetter(*keys_ids)), key=itemgetter(*keys)):
grouped_ops[k] = sum(self.env['stock.move.line'].concat(*list(g)).mapped('product_qty'))
for k, g in groupby(pack_move_lines, key=itemgetter(*groupby_keys)):
grouped_ops[k] = sum(self.env['stock.move.line'].concat(*g).mapped('product_qty'))
if any(not float_is_zero(grouped_quants.get(key, 0) - grouped_ops.get(key, 0), precision_digits=precision_digits) for key in grouped_quants) \
or any(not float_is_zero(grouped_ops.get(key, 0) - grouped_quants.get(key, 0), precision_digits=precision_digits) for key in grouped_ops):
all_in = False
@@ -1127,7 +1124,7 @@ class Picking(models.Model):
backorders |= backorder_picking
return backorders
def _log_activity_get_documents(self, orig_obj_changes, stream_field, stream, sorted_method=False, groupby_method=False):
def _log_activity_get_documents(self, orig_obj_changes, stream_field, stream, groupby_method=False):
""" Generic method to log activity. To use with
_log_activity method. It either log on uppermost
ongoing documents or following documents. This method
@@ -1147,8 +1144,8 @@ class Picking(models.Model):
- 'UP' if we want to log on the upper most ongoing
documents.
- 'DOWN' if we want to log on following documents.
:param sorted_method method, groupby_method: Only need when
stream is 'DOWN', it should sort/group by tuple(object on
:param groupby_method: Only need when
stream is 'DOWN', it should group by tuple(object on
which the activity is log, the responsible for this object)
"""
if self.env.context.get('skip_activity'):
@@ -1162,10 +1159,10 @@ class Picking(models.Model):
# '(delivery_picking_2, admin)': stock.move(3)}
visited_documents = {}
if stream == 'DOWN':
if sorted_method and groupby_method:
grouped_moves = groupby(sorted(origin_objects.mapped(stream_field), key=sorted_method), key=groupby_method)
if groupby_method:
grouped_moves = groupby(origin_objects.mapped(stream_field), key=groupby_method)
else:
raise UserError(_('You have to define a groupby and sorted method and pass them as arguments.'))
raise AssertionError('You have to define a groupby method and pass them as arguments.')
elif stream == 'UP':
# When using upstream document it is required to define
# _get_upstream_documents_and_responsibles on
@@ -1181,13 +1178,12 @@ class Picking(models.Model):
visited_documents[(document, responsible)] = visited
grouped_moves = grouped_moves.items()
else:
raise UserError(_('Unknown stream.'))
raise AssertionError('Unknown stream.')
documents = {}
for (parent, responsible), moves in grouped_moves:
if not parent:
continue
moves = list(moves)
moves = self.env[moves[0]._name].concat(*moves)
# Get the note
rendering_context = {move: (orig_object, orig_obj_changes[orig_object]) for move in moves for orig_object in move_to_orig_object_rel[move]}
@@ -1233,12 +1229,6 @@ class Picking(models.Model):
:param dict moves: a dict with a move as key and tuple with
new and old quantity as value. eg: {move_1 : (4, 5)}
"""
def _keys_in_sorted(move):
""" sort by picking and the responsible for the product the
move.
"""
return (move.picking_id.id, move.product_id.responsible_id.id)
def _keys_in_groupby(move):
""" group by picking and the responsible for the product the
move.
@@ -1260,7 +1250,7 @@ class Picking(models.Model):
}
return self.env.ref('stock.exception_on_picking')._render(values=values)
documents = self._log_activity_get_documents(moves, 'move_dest_ids', 'DOWN', _keys_in_sorted, _keys_in_groupby)
documents = self._log_activity_get_documents(moves, 'move_dest_ids', 'DOWN', _keys_in_groupby)
documents = self._less_quantities_than_expected_add_documents(moves, documents)
self._log_activity(_render_note_exception_quantity, documents)
@@ -14,10 +14,6 @@ class StockRule(models.Model):
"""
return procurement.values.get('sale_line_id'), super(StockRule, self)._get_procurements_to_merge_groupby(procurement)
@api.model
def _get_procurements_to_merge_sorted(self, procurement):
return procurement.values.get('sale_line_id'), super(StockRule, self)._get_procurements_to_merge_sorted(procurement)
class ProcurementGroup(models.Model):
_inherit = "procurement.group"