[REF] stock: _procure_orderpoint_confirm multi

Allow to run the execution for a subset of orderpoints

Task: 2161378
This commit is contained in:
Arnold Moyaux
2020-04-22 07:50:13 +00:00
parent 7076a86add
commit 9ee2323c28
2 changed files with 81 additions and 107 deletions
+76 -3
View File
@@ -1,14 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from datetime import datetime, time
from json import dumps
from dateutil import relativedelta
from json import dumps
from psycopg2 import OperationalError
from odoo import _, api, fields, models
from odoo import SUPERUSER_ID, _, api, fields, models, registry
from odoo.addons.stock.models.stock_rule import ProcurementException
from odoo.exceptions import UserError, ValidationError
from odoo.osv import expression
from odoo.tools import float_compare, split_every
_logger = logging.getLogger(__name__)
class StockWarehouseOrderpoint(models.Model):
@@ -159,5 +164,73 @@ class StockWarehouseOrderpoint(models.Model):
'group_id': group or self.group_id,
}
def _procure_orderpoint_confirm(self, use_new_cursor=False, company_id=None):
""" Create procurements based on orderpoints.
:param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing
1000 orderpoints.
This is appropriate for batch jobs only.
"""
self = self.with_company(company_id)
orderpoints_noprefetch = self.read(['id'])
orderpoints_noprefetch = [orderpoint['id'] for orderpoint in orderpoints_noprefetch]
for orderpoints_batch in split_every(1000, orderpoints_noprefetch):
if use_new_cursor:
cr = registry(self._cr.dbname).cursor()
self = self.with_env(self.env(cr=cr))
orderpoints_batch = self.env['stock.warehouse.orderpoint'].browse(orderpoints_batch)
orderpoints_exceptions = []
while orderpoints_batch:
procurements = []
for orderpoint in orderpoints_batch:
if float_compare(orderpoint.qty_to_order, 0.0, precision_rounding=orderpoint.product_uom.rounding) == 1:
date = datetime.combine(orderpoint.lead_days_date, time.min)
values = orderpoint._prepare_procurement_values(orderpoint.qty_to_order, date=date)
procurements.append(self.env['procurement.group'].Procurement(
orderpoint.product_id, orderpoint.qty_to_order, orderpoint.product_uom,
orderpoint.location_id, orderpoint.name, orderpoint.name,
orderpoint.company_id, values))
try:
with self.env.cr.savepoint():
self.env['procurement.group'].with_context(from_orderpoint=True).run(procurements, raise_user_error=False)
except ProcurementException as errors:
for procurement, error_msg in errors.procurement_exceptions:
orderpoints_exceptions += [(procurement.values.get('orderpoint_id'), error_msg)]
failed_orderpoints = self.env['stock.warehouse.orderpoint'].concat(*[o[0] for o in orderpoints_exceptions])
if not failed_orderpoints:
_logger.error('Unable to process orderpoints')
break
orderpoints_batch -= failed_orderpoints
except OperationalError:
if use_new_cursor:
cr.rollback()
continue
else:
raise
else:
orderpoints_batch._post_process_scheduler()
break
# Log an activity on product template for failed orderpoints.
for orderpoint, error_msg in orderpoints_exceptions:
existing_activity = self.env['mail.activity'].search([
('res_id', '=', orderpoint.product_id.product_tmpl_id.id),
('res_model_id', '=', self.env.ref('product.model_product_template').id),
('note', '=', error_msg)])
if not existing_activity:
orderpoint.product_id.product_tmpl_id.activity_schedule(
'mail.mail_activity_data_warning',
note=error_msg,
user_id=orderpoint.product_id.responsible_id.id or SUPERUSER_ID,
)
if use_new_cursor:
cr.commit()
cr.close()
return {}
def _post_process_scheduler(self):
return True
+5 -104
View File
@@ -3,15 +3,13 @@
import logging
from collections import defaultdict, namedtuple
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
from psycopg2 import OperationalError
from odoo import SUPERUSER_ID, _, api, fields, models, registry
from odoo import _, api, fields, models, registry
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import float_compare, float_is_zero, float_round, frozendict, html_escape
from odoo.tools import float_compare, float_is_zero, html_escape
from odoo.tools.misc import split_every
_logger = logging.getLogger(__name__)
@@ -502,7 +500,9 @@ class ProcurementGroup(models.Model):
@api.model
def _run_scheduler_tasks(self, use_new_cursor=False, company_id=False):
# Minimum stock rules
self.sudo()._procure_orderpoint_confirm(use_new_cursor=use_new_cursor, company_id=company_id)
domain = self._get_orderpoint_domain(company_id=company_id)
orderpoints = self.env['stock.warehouse.orderpoint'].search(domain)
orderpoints.sudo()._procure_orderpoint_confirm(use_new_cursor=use_new_cursor, company_id=company_id)
# Search all confirmed stock_moves and try to assign them
domain = self._get_moves_to_assign_domain()
@@ -548,102 +548,3 @@ class ProcurementGroup(models.Model):
domain = [('company_id', '=', company_id)] if company_id else []
domain += [('product_id.active', '=', True)]
return domain
@api.model
def _procure_orderpoint_confirm(self, use_new_cursor=False, company_id=None):
""" Create procurements based on orderpoints.
:param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing
1000 orderpoints.
This is appropriate for batch jobs only.
"""
self = self.with_company(company_id)
domain = self._get_orderpoint_domain(company_id=company_id)
orderpoints_noprefetch = self.env['stock.warehouse.orderpoint'].search_read(
domain, fields=['id'], order=self._procurement_from_orderpoint_get_order())
orderpoints_noprefetch = [orderpoint['id'] for orderpoint in orderpoints_noprefetch]
for orderpoints_batch in split_every(1000, orderpoints_noprefetch):
if use_new_cursor:
cr = registry(self._cr.dbname).cursor()
self = self.with_env(self.env(cr=cr))
orderpoints_batch = self.env['stock.warehouse.orderpoint'].browse(orderpoints_batch)
orderpoints_exceptions = []
while orderpoints_batch:
# Calculate groups that can be executed together
orderpoints_contexts = defaultdict(lambda: self.env['stock.warehouse.orderpoint'])
procurements = []
for orderpoint in orderpoints_batch:
orderpoint_context = orderpoint._get_product_context()
product_context = frozendict({**self.env.context, **orderpoint_context})
orderpoints_contexts[product_context] |= orderpoint
for orderpoint_context, orderpoints_by_context in orderpoints_contexts.items():
substract_quantity = orderpoints_by_context._quantity_in_progress()
product_quantity = orderpoints_by_context.product_id.with_context(orderpoint_context)._product_available()
for orderpoint in orderpoints_by_context:
op_product_virtual = product_quantity[orderpoint.product_id.id]['virtual_available']
if op_product_virtual is None:
continue
if float_compare(op_product_virtual, orderpoint.product_min_qty, precision_rounding=orderpoint.product_uom.rounding) <= 0:
qty = max(orderpoint.product_min_qty, orderpoint.product_max_qty) - op_product_virtual
remainder = orderpoint.qty_multiple > 0 and qty % orderpoint.qty_multiple or 0.0
if float_compare(remainder, 0.0, precision_rounding=orderpoint.product_uom.rounding) > 0:
qty += orderpoint.qty_multiple - remainder
if float_compare(qty, 0.0, precision_rounding=orderpoint.product_uom.rounding) < 0:
continue
qty -= substract_quantity[orderpoint.id]
qty_rounded = float_round(qty, precision_rounding=orderpoint.product_uom.rounding)
if qty_rounded > 0:
date = datetime.combine(orderpoint_context.get('to_date'), time.min)
values = orderpoint._prepare_procurement_values(qty_rounded, date=date)
procurements.append(self.env['procurement.group'].Procurement(
orderpoint.product_id, qty_rounded, orderpoint.product_uom,
orderpoint.location_id, orderpoint.name, orderpoint.name,
orderpoint.company_id, values))
try:
with self.env.cr.savepoint():
self.env['procurement.group'].with_context(from_orderpoint=True).run(procurements, raise_user_error=False)
except ProcurementException as errors:
for procurement, error_msg in errors.procurement_exceptions:
orderpoints_exceptions += [(procurement.values.get('orderpoint_id'), error_msg)]
failed_orderpoints = self.env['stock.warehouse.orderpoint'].concat(*[o[0] for o in orderpoints_exceptions])
if not failed_orderpoints:
_logger.error('Unable to process orderpoints')
break
orderpoints_batch -= failed_orderpoints
except OperationalError:
if use_new_cursor:
cr.rollback()
continue
else:
raise
else:
orderpoints_batch._post_process_scheduler()
break
# Log an activity on product template for failed orderpoints.
for orderpoint, error_msg in orderpoints_exceptions:
existing_activity = self.env['mail.activity'].search([
('res_id', '=', orderpoint.product_id.product_tmpl_id.id),
('res_model_id', '=', self.env.ref('product.model_product_template').id),
('note', '=', error_msg)])
if not existing_activity:
orderpoint.product_id.product_tmpl_id.activity_schedule(
'mail.mail_activity_data_warning',
note=error_msg,
user_id=orderpoint.product_id.responsible_id.id or SUPERUSER_ID,
)
if use_new_cursor:
cr.commit()
cr.close()
return {}