diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 776111613ec..2a9dd559fb4 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -1038,6 +1038,8 @@ class account_move_line(osv.osv): all_moves = list(set(all_moves) - set(move_ids)) if unlink_ids: if opening_reconciliation: + raise osv.except_osv(_('Warning!'), + _('Opening Entries have already been generated. Please run "Cancel Closing Entries" wizard to cancel those entries and then run this wizard.')) obj_move_rec.write(cr, uid, unlink_ids, {'opening_reconciliation': False}) obj_move_rec.unlink(cr, uid, unlink_ids) if len(all_moves) >= 2: diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index 382102710bb..b983dbfd4b3 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -141,6 +141,7 @@ class crm_phonecall(osv.osv): 'partner_phone' : call.partner_phone, 'partner_mobile' : call.partner_mobile, 'priority': call.priority, + 'opportunity_id': call.opportunity_id and call.opportunity_id.id or False, } new_id = self.create(cr, uid, vals, context=context) if action == 'log': diff --git a/addons/delivery/delivery.py b/addons/delivery/delivery.py index 33db829ac80..0281c2787fb 100644 --- a/addons/delivery/delivery.py +++ b/addons/delivery/delivery.py @@ -19,11 +19,14 @@ # ############################################################################## +import logging import time from openerp.osv import fields,osv from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp +_logger = logging.getLogger(__name__) + class delivery_carrier(osv.osv): _name = "delivery.carrier" _description = "Carrier" @@ -51,14 +54,24 @@ class delivery_carrier(osv.osv): for carrier in self.browse(cr, uid, ids, context=context): order_id=context.get('order_id',False) price=False + available = False if order_id: order = sale_obj.browse(cr, uid, order_id, context=context) carrier_grid=self.grid_get(cr,uid,[carrier.id],order.partner_shipping_id.id,context) if carrier_grid: - price=grid_obj.get_price(cr, uid, carrier_grid, order, time.strftime('%Y-%m-%d'), context) + try: + price=grid_obj.get_price(cr, uid, carrier_grid, order, time.strftime('%Y-%m-%d'), context) + available = True + except osv.except_osv, e: + # no suitable delivery method found, probably configuration error + _logger.error("Carrier %s: %s\n%s" % (carrier.name, e.name, e.value)) + price = 0.0 else: price = 0.0 - res[carrier.id]=price + res[carrier.id] = { + 'price': price, + 'available': available + } return res _columns = { @@ -66,7 +79,9 @@ class delivery_carrier(osv.osv): 'partner_id': fields.many2one('res.partner', 'Transport Company', required=True, help="The partner that is doing the delivery service."), 'product_id': fields.many2one('product.product', 'Delivery Product', required=True), 'grids_id': fields.one2many('delivery.grid', 'carrier_id', 'Delivery Grids'), - 'price' : fields.function(get_price, string='Price'), + 'available' : fields.function(get_price, string='Available',type='boolean', multi='price', + help="Is the carrier method possible with the current order."), + 'price' : fields.function(get_price, string='Price', multi='price'), 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the delivery carrier without removing it."), 'normal_price': fields.float('Normal Price', help="Keep empty if the pricing depends on the advanced pricing per destination"), 'free_if_more_than': fields.boolean('Free If Order Total Amount Is More Than', help="If the order is more expensive than a certain amount, the customer can benefit from a free shipping"), diff --git a/addons/hr_attendance/hr_attendance.py b/addons/hr_attendance/hr_attendance.py index a7950fef8d0..4887d41a52c 100644 --- a/addons/hr_attendance/hr_attendance.py +++ b/addons/hr_attendance/hr_attendance.py @@ -61,13 +61,16 @@ class hr_attendance(osv.osv): ('employee_id', '=', obj.employee_id.id), ('name', '<', obj.name), ('action', '=', 'sign_in') ], limit=1, order='name DESC') - last_signin = self.browse(cr, uid, last_signin_id, context=context)[0] + if last_signin_id: + last_signin = self.browse(cr, uid, last_signin_id, context=context)[0] - # Compute time elapsed between sign-in and sign-out - last_signin_datetime = datetime.strptime(last_signin.name, '%Y-%m-%d %H:%M:%S') - signout_datetime = datetime.strptime(obj.name, '%Y-%m-%d %H:%M:%S') - workedhours_datetime = (signout_datetime - last_signin_datetime) - res[obj.id] = ((workedhours_datetime.seconds) / 60) / 60 + # Compute time elapsed between sign-in and sign-out + last_signin_datetime = datetime.strptime(last_signin.name, '%Y-%m-%d %H:%M:%S') + signout_datetime = datetime.strptime(obj.name, '%Y-%m-%d %H:%M:%S') + workedhours_datetime = (signout_datetime - last_signin_datetime) + res[obj.id] = ((workedhours_datetime.seconds) / 60) / 60 + else: + res[obj.id] = False return res _columns = { diff --git a/addons/hr_payroll/hr_payroll.py b/addons/hr_payroll/hr_payroll.py index 4dea994a130..397938f3442 100644 --- a/addons/hr_payroll/hr_payroll.py +++ b/addons/hr_payroll/hr_payroll.py @@ -368,7 +368,7 @@ class hr_payslip(osv.osv): #OR if it starts between the given dates clause_2 = ['&',('date_start', '<=', date_to),('date_start','>=', date_from)] #OR if it starts before the date_from and finish after the date_end (or never finish) - clause_3 = [('date_start','<=', date_from),'|',('date_end', '=', False),('date_end','>=', date_to)] + clause_3 = ['&',('date_start','<=', date_from),'|',('date_end', '=', False),('date_end','>=', date_to)] clause_final = [('employee_id', '=', employee.id),'|','|'] + clause_1 + clause_2 + clause_3 contract_ids = contract_obj.search(cr, uid, clause_final, context=context) return contract_ids diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py index a005eb7ea82..aeb0a2b10ec 100644 --- a/addons/mail/mail_followers.py +++ b/addons/mail/mail_followers.py @@ -149,10 +149,11 @@ class mail_notification(osv.Model): company = "%s" % (website_url, user.company_id.name) else: company = user.company_id.name - sent_by = _('Sent from %(company)s using %(openerp)s') + sent_by = _('Sent by %(company)s using %(odoo)s.') + signature_company = '%s' % (sent_by % { 'company': company, - 'openerp': "Odoo" + 'odoo': "Odoo" }) footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div') @@ -185,8 +186,9 @@ class mail_notification(osv.Model): # compute email body (signature, company data) body_html = message.body - user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None - if user_signature: + # add user signature except for mail groups, where users are usually adding their own signatures already + if user_signature and message.model != 'mail.group': + user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context) body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div') diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index 721593d4d76..a654a7b194b 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -226,8 +226,15 @@ class mail_group(osv.Model): except Exception: headers = {} headers['Precedence'] = 'list' + # avoid out-of-office replies from MS Exchange + # http://blogs.technet.com/b/exchange/archive/2006/10/06/3395024.aspx + headers['X-Auto-Response-Suppress'] = 'OOF' if group.alias_domain and group.alias_name: headers['List-Id'] = '%s.%s' % (group.alias_name, group.alias_domain) headers['List-Post'] = '' % (group.alias_name, group.alias_domain) + # Avoid users thinking it was a personal message + # X-Forge-To: will replace To: after SMTP envelope is determined by ir.mail.server + list_to = '"%s" <%s@%s>' % (group.name, group.alias_name, group.alias_domain) + headers['X-Forge-To'] = list_to res['headers'] = '%s' % headers return res diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index fdd87bbcfab..84e0d4252b1 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -467,14 +467,10 @@ class test_mail(TestMail): 'message_post: notification email subject incorrect') self.assertIn(_body1, sent_email['body'], 'message_post: notification email body incorrect') - self.assertIn(user_raoul.signature, sent_email['body'], - 'message_post: notification email body should contain the sender signature') self.assertIn('Pigs rules', sent_email['body_alternative'], 'message_post: notification email body alternative should contain the body') self.assertNotIn('

', sent_email['body_alternative'], 'message_post: notification email body alternative still contains html') - self.assertIn(html2plaintext(user_raoul.signature), sent_email['body_alternative'], - 'message_post: notification email body alternative should contain the sender signature') self.assertFalse(sent_email['references'], 'message_post: references should be False when sending a message that is not a reply') @@ -538,14 +534,10 @@ class test_mail(TestMail): 'message_post: notification email subject incorrect') self.assertIn(html_sanitize(_body2), sent_email['body'], 'message_post: notification email does not contain the body') - self.assertIn(user_raoul.signature, sent_email['body'], - 'message_post: notification email body should contain the sender signature') self.assertIn('Pigs rocks', sent_email['body_alternative'], 'message_post: notification email body alternative should contain the body') self.assertNotIn('

', sent_email['body_alternative'], 'message_post: notification email body alternative still contains html') - self.assertIn(html2plaintext(user_raoul.signature), sent_email['body_alternative'], - 'message_post: notification email body alternative should contain the sender signature') self.assertIn(msg_message_id, sent_email['references'], 'message_post: notification email references lacks parent message message_id') # Test: attachments + download diff --git a/addons/procurement/wizard/schedulers_all.py b/addons/procurement/wizard/schedulers_all.py index 47f4dac440b..5d05291c546 100644 --- a/addons/procurement/wizard/schedulers_all.py +++ b/addons/procurement/wizard/schedulers_all.py @@ -19,11 +19,15 @@ # ############################################################################## +import logging import threading +from openerp import tools from openerp.osv import osv from openerp.api import Environment +_logger = logging.getLogger(__name__) + class procurement_compute_all(osv.osv_memory): _name = 'procurement.order.compute.all' _description = 'Compute all schedulers' @@ -39,8 +43,17 @@ class procurement_compute_all(osv.osv_memory): with Environment.manage(): proc_obj = self.pool.get('procurement.order') #As this function is in a new thread, i need to open a new cursor, because the old one may be closed - + new_cr = self.pool.cursor() + # Avoid to run the scheduler multiple times in the same time + try: + with tools.mute_logger('openerp.sql_db'): + new_cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (scheduler_cron_id,)) + except Exception: + _logger.info('Attempt to run procurement scheduler aborted, as already running') + new_cr.rollback() + new_cr.close() + return {} user = self.pool.get('res.users').browse(new_cr, uid, uid, context=context) comps = [x.id for x in user.company_ids] for comp in comps: diff --git a/addons/product/product.py b/addons/product/product.py index 32b3a0bfbf1..96ce4586b59 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -395,6 +395,7 @@ class product_attribute_price(osv.osv): class product_attribute_line(osv.osv): _name = "product.attribute.line" + _rec_name = 'attribute_id' _columns = { 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete='cascade'), 'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True, ondelete='restrict'), diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index e48c2e61d97..5823f2cab05 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -391,7 +391,7 @@ - + diff --git a/addons/sale/sale.py b/addons/sale/sale.py index b85716b1aff..44f87b7ab03 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -632,7 +632,7 @@ class sale_order(osv.osv): compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1] except ValueError: compose_form_id = False - ctx = dict(context) + ctx = dict() ctx.update({ 'default_model': 'sale.order', 'default_res_id': ids[0], diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py index c3f1d6cade4..3d8cc3e6997 100644 --- a/addons/sale/wizard/sale_make_invoice_advance.py +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -37,6 +37,7 @@ class sale_advance_payment_inv(osv.osv_memory): Use Some Order Lines to invoice a selection of the sales order lines."""), 'qtty': fields.float('Quantity', digits=(16, 2), required=True), 'product_id': fields.many2one('product.product', 'Advance Product', + domain=[('type', '=', 'service')], help="""Select a product of type service which is called 'Advance Product'. You may have to create it and set it as a default value on this field."""), 'amount': fields.float('Advance Amount', digits_compute= dp.get_precision('Account'), diff --git a/addons/sale_service/views/sale_service_view.xml b/addons/sale_service/views/sale_service_view.xml index 0865d95fa0e..59d018ad146 100644 --- a/addons/sale_service/views/sale_service_view.xml +++ b/addons/sale_service/views/sale_service_view.xml @@ -16,7 +16,7 @@ product.template - + diff --git a/addons/website_crm/controllers/main.py b/addons/website_crm/controllers/main.py index b209c70d962..5e2d8e75866 100644 --- a/addons/website_crm/controllers/main.py +++ b/addons/website_crm/controllers/main.py @@ -25,6 +25,10 @@ class contactus(http.Controller): values.update(kwargs=kwargs.items()) return request.website.render("website.contactus", values) + def create_lead(self, request, values): + """ Allow to be overrided """ + return request.registry['crm.lead'].create(request.cr, SUPERUSER_ID, values, request.context) + @http.route(['/crm/contactus'], type='http', auth="public", website=True) def contactus(self, **kwargs): def dict_to_str(title, dictvar): @@ -41,12 +45,10 @@ class contactus(http.Controller): post_description = [] # Info to add after the message values = {} - lead_model = request.registry['crm.lead'] - for field_name, field_value in kwargs.items(): if hasattr(field_value, 'filename'): post_file.append(field_value) - elif field_name in lead_model._all_columns and field_name not in _BLACKLIST: + elif field_name in request.registry['crm.lead']._all_columns and field_name not in _BLACKLIST: values[field_name] = field_value elif field_name not in _TECHNICAL: # allow to add some free fields or blacklisted field like ID post_description.append("%s: %s" % (field_name, field_value)) @@ -80,7 +82,7 @@ class contactus(http.Controller): post_description.append("%s: %s" % ("REFERER", environ.get("HTTP_REFERER"))) values['description'] += dict_to_str(_("Environ Fields: "), post_description) - lead_id = lead_model.create(request.cr, SUPERUSER_ID, dict(values, user_id=False), request.context) + lead_id = self.create_lead(request, dict(values, user_id=False)) if lead_id: for field_value in post_file: attachment_value = { diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index 2d2e5f24606..78d69de971b 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -62,7 +62,7 @@ class WebsiteForum(http.Controller): forum_id = request.registry['forum.forum'].create(request.cr, request.uid, { 'name': forum_name, }, context=request.context) - return request.redirect("/forum/%s" % slug(forum_id)) + return request.redirect("/forum/%s" % forum_id) @http.route('/forum/notification_read', type='json', auth="user", methods=['POST'], website=True) def notification_read(self, **kwargs): @@ -529,7 +529,7 @@ class WebsiteForum(http.Controller): 'website': kwargs.get('website'), 'email': kwargs.get('email'), 'city': kwargs.get('city'), - 'country_id': int(kwargs.get('country')), + 'country_id': int(kwargs.get('country')) if kwargs.get('country') else False, 'website_description': kwargs.get('description'), }, context=request.context) return werkzeug.utils.redirect("/forum/%s/user/%d" % (slug(forum), user.id)) diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index 891ce3b982d..2b28eb465cc 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -400,8 +400,9 @@ class Post(osv.Model): raise KarmaError('Not enough karma to downvote.') Vote = self.pool['forum.post.vote'] - vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], limit=1, context=context) - new_vote = 0 + vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context) + new_vote = '1' if upvote else '-1' + voted_forum_ids = set() if vote_ids: for vote in Vote.browse(cr, uid, vote_ids, context=context): if upvote: @@ -409,9 +410,9 @@ class Post(osv.Model): else: new_vote = '0' if vote.vote == '1' else '-1' Vote.write(cr, uid, vote_ids, {'vote': new_vote}, context=context) - else: + voted_forum_ids.add(vote.post_id.id) + for post_id in set(ids) - voted_forum_ids: for post_id in ids: - new_vote = '1' if upvote else '-1' Vote.create(cr, uid, {'post_id': post_id, 'vote': new_vote}, context=context) return {'vote_count': self._get_vote_count(cr, uid, ids, None, None, context=context)[ids[0]], 'user_vote': new_vote} diff --git a/addons/website_mail/static/src/js/follow.js b/addons/website_mail/static/src/js/follow.js index 18c36378328..a8b351efa6c 100644 --- a/addons/website_mail/static/src/js/follow.js +++ b/addons/website_mail/static/src/js/follow.js @@ -40,17 +40,21 @@ } this.$target.removeClass('has-error'); - openerp.jsonRpc('/website_mail/follow', 'call', { - 'id': +this.$target.data('id'), - 'object': this.$target.data('object'), - 'message_is_follower': this.$target.attr("data-follow") || "off", - 'email': $email.length ? $email.val() : false, - }).then(function (follow) { - self.toggle_subscription(follow, self.email); - }); + var email = $email.length ? $email.val() : false; + if (email) { + openerp.jsonRpc('/website_mail/follow', 'call', { + 'id': +this.$target.data('id'), + 'object': this.$target.data('object'), + 'message_is_follower': this.$target.attr("data-follow") || "off", + 'email': email, + }).then(function (follow) { + self.toggle_subscription(follow, email); + }); + } }, toggle_subscription: function(follow, email) { console.log(follow, email); + follow = follow || (!email && this.$target.attr('data-unsubscribe')); if (follow) { this.$target.find(".js_follow_btn").addClass("hidden"); this.$target.find(".js_unfollow_btn").removeClass("hidden"); @@ -60,8 +64,8 @@ this.$target.find(".js_unfollow_btn").addClass("hidden"); } this.$target.find('input.js_follow_email') - .val(email ? email : "") - .attr("disabled", follow || (email.length && this.is_user) ? "disabled" : false); + .val(email || "") + .attr("disabled", email && (follow || this.is_user) ? "disabled" : false); this.$target.attr("data-follow", follow ? 'on' : 'off'); }, }); diff --git a/addons/website_mail/views/website_mail.xml b/addons/website_mail/views/website_mail.xml index 0ccae00e21d..2b810095231 100644 --- a/addons/website_mail/views/website_mail.xml +++ b/addons/website_mail/views/website_mail.xml @@ -5,7 +5,8 @@