From 3609ba10f2d00a608d8f32334ae1b9a25b34f037 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 12 Aug 2014 11:02:15 +0200 Subject: [PATCH 1/3] [FIX] procurement: refactor scheduler so it continues even if a procurement failed If the mrp scheduler fails during the treatment of a procurement (due to a postgresql error, like an update lock), we let it try the other procurements, instead of stopping at the first procurement which fails. Besides, we retry to treat the procurements which failed, until the list of the remaining procurements is always the same (meaning something is wrong and block the procurements treatment). --- addons/procurement/schedulers.py | 168 ++++++++++++++++--------------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/addons/procurement/schedulers.py b/addons/procurement/schedulers.py index 07280b38ae7..326a9642293 100644 --- a/addons/procurement/schedulers.py +++ b/addons/procurement/schedulers.py @@ -28,6 +28,7 @@ from openerp.osv import fields from openerp.tools.translate import _ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT from openerp import tools +from psycopg2 import OperationalError class procurement_order(osv.osv): _inherit = 'procurement.order' @@ -70,56 +71,45 @@ class procurement_order(osv.osv): cr.commit() company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id maxdate = (datetime.today() + relativedelta(days=company.schedule_range)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - start_date = fields.datetime.now() - offset = 0 - report = [] - report_total = 0 - report_except = 0 - report_later = 0 + prev_ids = [] while True: - ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_order')], offset=offset, limit=500, order='priority, date_planned', context=context) + ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_order'), ('date_planned', '<', maxdate)], limit=500, order='priority, date_planned', context=context) for proc in procurement_obj.browse(cr, uid, ids, context=context): - if maxdate >= proc.date_planned: + try: wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr) - else: - offset += 1 - report_later += 1 - if proc.state == 'exception': - report.append(_('PROC %d: on order - %3.2f %-5s - %s') % \ - (proc.id, proc.product_qty, proc.product_uom.name, - proc.product_id.name)) - report_except += 1 - report_total += 1 - if use_new_cursor: - cr.commit() - if not ids: + if use_new_cursor: + cr.commit() + except OperationalError: + if use_new_cursor: + cr.rollback() + continue + else: + raise + if not ids or prev_ids == ids: break - offset = 0 + else: + prev_ids = ids ids = [] + prev_ids = [] while True: - report_ids = [] - ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_stock')], offset=offset) + ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_stock'), ('date_planned', '<', maxdate)], limit=500) for proc in procurement_obj.browse(cr, uid, ids): - if maxdate >= proc.date_planned: + try: wf_service.trg_validate(uid, 'procurement.order', proc.id, 'button_check', cr) - report_ids.append(proc.id) - else: - report_later += 1 - report_total += 1 - if proc.state == 'exception': - report.append(_('PROC %d: from stock - %3.2f %-5s - %s') % \ - (proc.id, proc.product_qty, proc.product_uom.name, - proc.product_id.name,)) - report_except += 1 - - - if use_new_cursor: - cr.commit() - offset += len(ids) - if not ids: break - end_date = fields.datetime.now() + if use_new_cursor: + cr.commit() + except OperationalError: + if use_new_cursor: + cr.rollback() + continue + else: + raise + if not ids or prev_ids == ids: + break + else: + prev_ids = ids if use_new_cursor: cr.commit() @@ -228,54 +218,68 @@ class procurement_order(osv.osv): procurement_obj = self.pool.get('procurement.order') wf_service = netsvc.LocalService("workflow") - offset = 0 ids = [1] + prev_ids = [] if automatic: self.create_automatic_op(cr, uid, context=context) - while ids: - ids = orderpoint_obj.search(cr, uid, [], offset=offset, limit=100) + orderpoint_ids = orderpoint_obj.search(cr, uid, []) + while orderpoint_ids: + ids = orderpoint_ids[:100] + del orderpoint_ids[:100] for op in orderpoint_obj.browse(cr, uid, ids, context=context): - prods = self._product_virtual_get(cr, uid, op) - if prods is None: - continue - if prods < op.product_min_qty: - qty = max(op.product_min_qty, op.product_max_qty)-prods - - reste = qty % op.qty_multiple - if reste > 0: - qty += op.qty_multiple - reste - - if qty <= 0: + try: + prods = self._product_virtual_get(cr, uid, op) + if prods is None: continue - if op.product_id.type not in ('consu'): - if op.procurement_draft_ids: - # Check draft procurement related to this order point - pro_ids = [x.id for x in op.procurement_draft_ids] - procure_datas = procurement_obj.read( - cr, uid, pro_ids, ['id', 'product_qty'], context=context) - to_generate = qty - for proc_data in procure_datas: - if to_generate >= proc_data['product_qty']: - wf_service.trg_validate(uid, 'procurement.order', proc_data['id'], 'button_confirm', cr) - procurement_obj.write(cr, uid, [proc_data['id']], {'origin': op.name}, context=context) - to_generate -= proc_data['product_qty'] - if not to_generate: - break - qty = to_generate + if prods < op.product_min_qty: + qty = max(op.product_min_qty, op.product_max_qty)-prods + + reste = qty % op.qty_multiple + if reste > 0: + qty += op.qty_multiple - reste + + if qty <= 0: + continue + if op.product_id.type not in ('consu'): + if op.procurement_draft_ids: + # Check draft procurement related to this order point + pro_ids = [x.id for x in op.procurement_draft_ids] + procure_datas = procurement_obj.read( + cr, uid, pro_ids, ['id', 'product_qty'], context=context) + to_generate = qty + for proc_data in procure_datas: + if to_generate >= proc_data['product_qty']: + wf_service.trg_validate(uid, 'procurement.order', proc_data['id'], 'button_confirm', cr) + procurement_obj.write(cr, uid, [proc_data['id']], {'origin': op.name}, context=context) + to_generate -= proc_data['product_qty'] + if not to_generate: + break + qty = to_generate + + if qty: + proc_id = procurement_obj.create(cr, uid, + self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context), + context=context) + wf_service.trg_validate(uid, 'procurement.order', proc_id, + 'button_confirm', cr) + wf_service.trg_validate(uid, 'procurement.order', proc_id, + 'button_check', cr) + orderpoint_obj.write(cr, uid, [op.id], + {'procurement_id': proc_id}, context=context) + if use_new_cursor: + cr.commit() + except OperationalError: + if use_new_cursor: + orderpoint_ids.append(op.id) + cr.rollback() + continue + else: + raise + if prev_ids == ids: + break + else: + prev_ids = ids - if qty: - proc_id = procurement_obj.create(cr, uid, - self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context), - context=context) - wf_service.trg_validate(uid, 'procurement.order', proc_id, - 'button_confirm', cr) - wf_service.trg_validate(uid, 'procurement.order', proc_id, - 'button_check', cr) - orderpoint_obj.write(cr, uid, [op.id], - {'procurement_id': proc_id}, context=context) - offset += len(ids) - if use_new_cursor: - cr.commit() if use_new_cursor: cr.commit() cr.close() From 2abddd5311a78be58eb81ea5d6875ad9c04493e3 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 12 Aug 2014 12:58:38 +0200 Subject: [PATCH 2/3] [FIX] l10n_be_coda: set a meaningful filename default value --- addons/l10n_be_coda/wizard/account_coda_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/l10n_be_coda/wizard/account_coda_import.py b/addons/l10n_be_coda/wizard/account_coda_import.py index 65ab03b441c..9334c9d23fc 100644 --- a/addons/l10n_be_coda/wizard/account_coda_import.py +++ b/addons/l10n_be_coda/wizard/account_coda_import.py @@ -49,7 +49,7 @@ class account_coda_import(osv.osv_memory): return tmp_account_id _defaults = { - 'coda_fname': lambda *a: '', + 'coda_fname': 'coda.txt', 'temporary_account_id': _get_default_tmp_account, } From 245bb4ebdfeccb05134b3ff943c9fd0fb01173c2 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 12 Aug 2014 13:40:45 +0200 Subject: [PATCH 3/3] [FIX] mail: to/from/reply-to addresses formated according to RFC2822 Use formataddr method from email.utils lib, which do the job correctly --- addons/email_template/tests/test_mail.py | 2 +- addons/mail/mail_followers.py | 6 ++++-- addons/mail/mail_mail.py | 11 +++++------ addons/mail/tests/test_mail_features.py | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index 2556864df7c..f46ee1cdc97 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -231,7 +231,7 @@ class test_message_compose(TestMailBase): # force send: take email_recipients into account email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, force_send=True, context=context) sent_emails = self._build_email_kwargs_list - email_to_lst = ['"Followers of Pigs" ', '"Followers of Pigs" ', '"Followers of Pigs" '] + email_to_lst = ['Followers of Pigs ', 'Followers of Pigs ', 'Followers of Pigs '] self.assertEqual(len(sent_emails), 3, 'email_template: send_mail: 3 valid email recipients -> should send 3 emails') for email in sent_emails: self.assertEqual(len(email['email_to']), 1, 'email_template: send_mail: email_recipient should send email to one recipient at a time') diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index fcd32ec7315..f4c74b0c996 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -18,6 +18,8 @@ # along with this program. If not, see # ############################################################################## +from email.utils import formataddr + from openerp.osv import osv, fields from openerp import tools, SUPERUSER_ID @@ -155,9 +157,9 @@ class mail_notification(osv.Model): # email_from: partner-user alias or partner email or mail.message email_from if msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].alias_domain and msg.author_id.user_ids[0].alias_name: - email_from = '%s <%s@%s>' % (msg.author_id.name, msg.author_id.user_ids[0].alias_name, msg.author_id.user_ids[0].alias_domain) + email_from = formataddr((msg.author_id.name, '%s@%s' % (msg.author_id.user_ids[0].alias_name, msg.author_id.user_ids[0].alias_domain))) elif msg.author_id: - email_from = '%s <%s>' % (msg.author_id.name, msg.author_id.email) + email_from = formataddr((msg.author_id.name, msg.author_id.email)) else: email_from = msg.email_from diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index b558f4729be..1c53d0477a7 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -22,6 +22,7 @@ import base64 import logging import re +from email.utils import formataddr from urllib import urlencode from urlparse import urljoin @@ -216,10 +217,9 @@ class mail_mail(osv.Model): if email_reply_to and mail.model and mail.res_id: document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0] if document_name: - # sanitize document name - sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1]) # generate reply to - email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to) + + email_reply_to = formataddr((_('Followers of %s') % document_name[1], email_reply_to)) return email_reply_to @@ -240,10 +240,9 @@ class mail_mail(osv.Model): # 2. if 'partner' is specified, but no related document: Partner Name # 3; fallback on mail.email_to that we split to have an email addresses list if partner and mail.record_name: - sanitized_record_name = re.sub(r'[^\w+.]+', '-', mail.record_name) - email_to = [_('"Followers of %s" <%s>') % (sanitized_record_name, partner.email)] + email_to = [formataddr((_('Followers of %s') % mail.record_name, partner.email))] elif partner: - email_to = ['%s <%s>' % (partner.name, partner.email)] + email_to = [formataddr((partner.name, partner.email))] else: email_to = tools.email_split(mail.email_to) diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index ef6e54d1a40..2dd552f5c32 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -336,7 +336,7 @@ class test_mail(TestMailBase): # Test: notifications emails: to a and b, c is email only, r is author # test_emailto = ['Administrator ', 'Bert Tartopoils '] - test_emailto = ['"Followers of -Pigs-" ', '"Followers of -Pigs-" '] + test_emailto = [u'"Followers of \\"Pigs\\" !\xf9 $%-" ', u'"Followers of \\"Pigs\\" !\xf9 $%-" '] self.assertEqual(len(sent_emails), 2, 'message_post: notification emails wrong number of send emails') self.assertEqual(set([m['email_to'][0] for m in sent_emails]), set(test_emailto), @@ -348,7 +348,7 @@ class test_mail(TestMailBase): 'message_post: notification email sent to more than one email address instead of a precise partner') self.assertIn(sent_email['email_to'][0], test_emailto, 'message_post: notification email email_to incorrect') - self.assertEqual(sent_email['reply_to'], '"Followers of -Pigs-" ', + self.assertEqual(sent_email['reply_to'], u'"Followers of \\"Pigs\\" !\xf9 $%-" ', 'message_post: notification email reply_to incorrect') self.assertEqual(_subject, sent_email['subject'], 'message_post: notification email subject incorrect') @@ -410,7 +410,7 @@ class test_mail(TestMailBase): # Test: emails send by server (to a, b, c, d) # test_emailto = [u'Administrator ', u'Bert Tartopoils ', u'Carine Poilvache ', u'D\xe9d\xe9 Grosbedon '] - test_emailto = [u'"Followers of Pigs" ', u'"Followers of Pigs" ', u'"Followers of Pigs" ', u'"Followers of Pigs" '] + test_emailto = [u'Followers of Pigs ', u'Followers of Pigs ', u'Followers of Pigs ', u'Followers of Pigs '] # self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect') for sent_email in sent_emails: self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon ', @@ -419,7 +419,7 @@ class test_mail(TestMailBase): 'message_post: notification email sent to more than one email address instead of a precise partner') self.assertIn(sent_email['email_to'][0], test_emailto, 'message_post: notification email email_to incorrect') - self.assertEqual(sent_email['reply_to'], '"Followers of Pigs" ', + self.assertEqual(sent_email['reply_to'], 'Followers of Pigs ', 'message_post: notification email reply_to incorrect: should name Followers of Pigs, and have raoul email') self.assertEqual(_mail_subject, sent_email['subject'], 'message_post: notification email subject incorrect')