def _process(self, cr, uid, action, record_ids, context=None):
""" process the given action on the records """
model = self.pool[action.model_id.model]
-
# modify records
values = {}
- if 'date_action_last' in model._all_columns:
+ if 'date_action_last' in model._fields:
values['date_action_last'] = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
- if action.act_user_id and 'user_id' in model._all_columns:
+ if action.act_user_id and 'user_id' in model._fields:
values['user_id'] = action.act_user_id.id
if values:
model.write(cr, uid, record_ids, values, context=context)
subtype='html',
subtype_alternative='plain',
headers=headers)
- res = ir_mail_server.send_email(cr, uid, msg,
+ try:
+ res = ir_mail_server.send_email(cr, uid, msg,
mail_server_id=mail.mail_server_id.id,
context=context)
-
+ except AssertionError as error:
+ if error.message == ir_mail_server.NO_VALID_RECIPIENT:
+ # No valid recipient found for this particular
+ # mail item -> ignore error to avoid blocking
+ # delivery to next recipients, if any. If this is
+ # the only recipient, the mail will show as failed.
+ _logger.warning("Ignoring invalid recipients for mail.mail %s: %s",
+ mail.message_id, email.get('email_to'))
+ else:
+ raise
if res:
- mail.write({'state': 'sent', 'message_id': res})
+ mail.write({'state': 'sent', 'message_id': res, 'failure_reason': False})
mail_sent = True
# /!\ can't use mail.state here, as mail.refresh() will cause an error
this._super();
var self = this;
- var print_button = this.add_action_button({
- label: _t('Print'),
- icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
- click: function(){ self.print(); },
- });
-
- var finish_button = this.add_action_button({
- label: _t('Next Order'),
- icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
- click: function() { self.finishOrder(); },
- });
-
this.refresh();
- this.print();
+
+ if (!this.pos.get('selectedOrder')._printed) {
+ this.print();
+ }
- //
// The problem is that in chrome the print() is asynchronous and doesn't
// execute until all rpc are finished. So it conflicts with the rpc used
// to send the orders to the backend, and the user is able to go to the next
// 2 seconds is the same as the default timeout for sending orders and so the dialog
// should have appeared before the timeout... so yeah that's not ultra reliable.
- finish_button.set_disabled(true);
+ this.lock_screen(true);
setTimeout(function(){
- finish_button.set_disabled(false);
+ self.lock_screen(false);
}, 2000);
},
+ lock_screen: function(locked) {
+ this._locked = locked;
+ if (locked) {
+ this.$('.next').removeClass('highlight');
+ } else {
+ this.$('.next').addClass('highlight');
+ }
+ },
print: function() {
+ this.pos.get('selectedOrder')._printed = true;
window.print();
},
- finishOrder: function() {
- this.pos.get('selectedOrder').destroy();
+ finish_order: function() {
+ if (!this._locked) {
+ this.pos.get_order().finalize();
+ }
+ },
+ renderElement: function() {
+ var self = this;
+ this._super();
+ this.$('.next').click(function(){
+ self.finish_order();
+ });
+ this.$('.button.print').click(function(){
+ self.print();
+ });
},
refresh: function() {
- var order = this.pos.get('selectedOrder');
- $('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{
+ var order = this.pos.get_order();
+ this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
widget:this,
order: order,
orderlines: order.get('orderLines').models,
<field name="name"/>
<field name="date_planned"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
- <field name="account_analytic_id" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
+ <field name="account_analytic_id" context="{'default_partner_id':parent.partner_id}" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
- <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,parent.state,context)"/>
- <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,parent.state,context)"/>
+ <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
+ <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,parent.state,context)"/>
<field name="price_unit"/>
<field name="taxes_id" widget="many2many_tags" domain="[('parent_id','=',False),('type_tax_use','!=','sale')]"/>
<field name="price_subtotal"/>
<sheet>
<group>
<group>
- <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)"/>
+ <field name="product_id"
- on_change="onchange_product_id(parent.pricelist_id,product_id,0,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,context)" context="{'partner_id': parent.partner_id}"/>
++ on_change="onchange_product_id(parent.pricelist_id,product_id,0,False,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,context)" context="{'partner_id': parent.partner_id}"/>
<label for="product_qty"/>
<div>
- <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,price_unit,'draft',context)" class="oe_inline"/>
- <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,'draft',context)" class="oe_inline"/>
+ <field name="product_qty" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id,parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)" class="oe_inline"/>
+ <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,False,'draft',context)" class="oe_inline"/>
</div>
<field name="price_unit"/>
</group>
self.dataset.ids = [];
if (!groups.length) {
self.no_result();
- return false;
+ return $.when();
}
self.nb_records = 0;
- var remaining = groups.length - 1,
- groups_array = [];
+ var groups_array = [];
return $.when.apply(null, _.map(groups, function (group, index) {
var def = $.when([]);
var dataset = new instance.web.DataSetSearch(self, self.dataset.model,
if(!self.nb_records) {
self.no_result();
}
- self.trigger('kanban_groups_processed');
+ if (self.dataset.index >= self.nb_records){
+ self.dataset.index = self.dataset.size() ? 0 : null;
+ }
- return self.do_add_groups(groups_array);
++ return self.do_add_groups(groups_array).done(function() {
++ self.trigger('kanban_groups_processed');
++ });
});
});
},
<template id="footer_custom" inherit_id="website.layout" name="Footer">
<xpath expr="//div[@id='footer_container']" position="replace">
<div class="oe_structure" id="footer">
- <section class="mt16 mb16">
- <section data-snippet-id='three-columns'>
++ <section>
<div class="container">
<div class="row">
<div class="col-md-4">
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/reopen', type='http', auth="user", methods=['POST'], website=True)
def question_reopen(self, forum, question, **kwarg):
- question.state = 'active'
- request.registry['forum.post'].reopen(request.cr, request.uid, [question.id], context=request.context)
++ question.reopen()
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/delete', type='http', auth="user", methods=['POST'], website=True)
# -*- coding: utf-8 -*-
from datetime import datetime
++import logging
+import math
import uuid
from werkzeug.exceptions import Forbidden
from openerp import SUPERUSER_ID
from openerp.addons.website.models.website import slug
from openerp.exceptions import Warning
-from openerp.osv import osv, fields
-from openerp.tools import html2plaintext
-from openerp.tools.translate import _
+ _logger = logging.getLogger(__name__)
class KarmaError(Forbidden):
""" Karma-related error, used for forum and posts. """
raise KarmaError('Not enough karma to accept or refuse an answer')
# update karma except for self-acceptance
mult = 1 if vals['is_correct'] else -1
- for post in self.browse(cr, uid, ids, context=context):
- if vals['is_correct'] != post.is_correct and post.create_uid.id != uid:
- self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_answer_accepted * mult, context=context)
- self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_answer_accept * mult, context=context)
- if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id'] for key in vals.keys()) and any(not post.can_edit for post in posts):
+ for post in self:
+ if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid:
+ post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * mult)
+ self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult)
+ if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id'] for key in vals.keys()) and any(not post.can_edit for post in self):
raise KarmaError('Not enough karma to edit a post.')
- res = super(Post, self).write(cr, uid, ids, vals, context=context)
+ res = super(Post, self).write(vals)
# if post content modify, notify followers
if 'content' in vals or 'name' in vals:
- for post in posts:
+ for post in self:
if post.parent_id:
body, subtype = _('Answer Edited'), 'website_forum.mt_answer_edit'
- obj_id = post.parent_id.id
+ obj_id = post.parent_id
else:
body, subtype = _('Question Edited'), 'website_forum.mt_question_edit'
- obj_id = post.id
- self.message_post(cr, uid, obj_id, body=body, subtype=subtype, context=context)
+ obj_id = post
+ obj_id.message_post(body=body, subtype=subtype)
return res
-
- def reopen(self, cr, uid, ids, context=None):
- if any(post.parent_id or post.state != 'close'
- for post in self.browse(cr, uid, ids, context=context)):
+ @api.multi
++ def reopen(self):
++ if any(post.parent_id or post.state != 'close' for post in self):
+ return False
+
- reason_offensive = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_7')
- reason_spam = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_8')
- for post in self.browse(cr, uid, ids, context=context):
- if post.closed_reason_id.id in (reason_offensive, reason_spam):
++ reason_offensive = self.env.ref('website_forum.reason_7')
++ reason_spam = self.env.ref('website_forum.reason_8')
++ for post in self:
++ if post.closed_reason_id in (reason_offensive, reason_spam):
+ _logger.info('Upvoting user <%s>, reopening spam/offensive question',
+ post.create_uid.login)
+ # TODO: in master, consider making this a tunable karma parameter
- self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id],
- post.forum_id.karma_gen_question_downvote * -5,
- context=context)
- self.pool['forum.post'].write(cr, SUPERUSER_ID, ids, {'state': 'active'}, context=context)
++ post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_downvote * -5)
+
- def close(self, cr, uid, ids, reason_id, context=None):
- if any(post.parent_id for post in self.browse(cr, uid, ids, context=context)):
++ self.sudo().write({'state': 'active'}}
++
++ @api.multi
+ def close(self, reason_id):
+ if any(post.parent_id for post in self):
return False
+
- reason_offensive = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_7')
- reason_spam = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.reason_8')
++ reason_offensive = self.env.ref('website_forum.reason_7').id
++ reason_spam = self.env.ref('website_forum.reason_8').id
+ if reason_id in (reason_offensive, reason_spam):
- for post in self.browse(cr, uid, ids, context=context):
++ for post in self:
+ _logger.info('Downvoting user <%s> for posting spam/offensive contents',
+ post.create_uid.login)
+ # TODO: in master, consider making this a tunable karma parameter
- self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id],
- post.forum_id.karma_gen_question_downvote * 5,
- context=context)
++ post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_downvote * 5)
+
- self.pool['forum.post'].write(cr, uid, ids, {
+ self.write({
'state': 'close',
- 'closed_uid': uid,
+ 'closed_uid': self._uid,
'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
'closed_reason_id': reason_id,
- }, context=context)
+ })
+ return True
- def unlink(self, cr, uid, ids, context=None):
- posts = self.browse(cr, uid, ids, context=context)
- if any(not post.can_unlink for post in posts):
+ @api.multi
+ def unlink(self):
+ if any(not post.can_unlink for post in self):
raise KarmaError('Not enough karma to unlink a post')
# if unlinking an answer with accepted answer: remove provided karma
- for post in posts:
+ for post in self:
if post.is_correct:
- self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_answer_accepted * -1, context=context)
- self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_answer_accept * -1, context=context)
- return super(Post, self).unlink(cr, uid, ids, context=context)
-
- def vote(self, cr, uid, ids, upvote=True, context=None):
- Vote = self.pool['forum.post.vote']
- vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
+ post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
+ self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
+ return super(Post, self).unlink()
+
+ @api.multi
+ def vote(self, upvote=True):
+ Vote = self.env['forum.post.vote']
+ vote_ids = Vote.search([('post_id', 'in', self._ids), ('user_id', '=', self._uid)])
new_vote = '1' if upvote else '-1'
voted_forum_ids = set()
if vote_ids:
categories = category_obj.browse(cr, uid, category_ids, context=context)
categs = filter(lambda x: not x.parent_id, categories)
+ domain += [('public_categ_ids', 'in', category_ids)]
+ product_obj = pool.get('product.template')
+
+ product_count = product_obj.search_count(cr, uid, domain, context=context)
+ pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
- product_ids = product_obj.search(cr, uid, domain, limit=PPG+10, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
++ product_ids = product_obj.search(cr, uid, domain, limit=PPG, offset=pager['offset'], order='website_published desc, website_sequence desc', context=context)
+ products = product_obj.browse(cr, uid, product_ids, context=context)
+
attributes_obj = request.registry['product.attribute']
attributes_ids = attributes_obj.search(cr, uid, [], context=context)
attributes = attributes_obj.browse(cr, uid, attributes_ids, context=context)
val = {
'name': value.split("\n")[0],
- 'address': escape("\n".join(value.split("\n")[1:])),
+ 'address': escape("\n".join(value.split("\n")[1:])).strip(),
- 'phone': field_browse.phone,
- 'mobile': field_browse.mobile,
- 'fax': field_browse.fax,
- 'city': field_browse.city,
- 'country_id': field_browse.country_id.display_name,
- 'website': field_browse.website,
- 'email': field_browse.email,
+ 'phone': value_rec.phone,
+ 'mobile': value_rec.mobile,
+ 'fax': value_rec.fax,
+ 'city': value_rec.city,
+ 'country_id': value_rec.country_id.display_name,
+ 'website': value_rec.website,
+ 'email': value_rec.email,
'fields': opf,
- 'object': field_browse,
+ 'object': value_rec,
'options': options
}