Here you can see your cash moves.
A cash moves can be either an expense or a payment.
diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py
index 12fdc62c260..63cd0888986 100644
--- a/addons/mail/__openerp__.py
+++ b/addons/mail/__openerp__.py
@@ -71,16 +71,12 @@ Main Features
'installable': True,
'application': True,
'images': [
- 'images/customer_history.jpeg',
+ 'images/inbox.jpeg',
'images/messages_form.jpeg',
'images/messages_list.jpeg',
- 'static/src/img/email_icong.png',
- 'static/src/img/_al.png',
- 'static/src/img/_pincky.png',
- 'static/src/img/groupdefault.png',
- 'static/src/img/attachment.png',
- 'static/src/img/checklist.png',
- 'static/src/img/formatting.png',
+ 'images/email.jpeg',
+ 'images/join_a_group.jpeg',
+ 'images/share_a_message.jpeg',
],
'css': [
'static/src/css/mail.css',
diff --git a/addons/mail/doc/mail_message.rst b/addons/mail/doc/mail_message.rst
index 02baf6c4708..b5e2d8da8b4 100644
--- a/addons/mail/doc/mail_message.rst
+++ b/addons/mail/doc/mail_message.rst
@@ -21,6 +21,8 @@ should inherit from this class.
ClientAction (ir.actions.client)
++++++++++++++++++++++++++++++++
+.. code-block:: xml
+
Inbox
mail.wall
@@ -36,6 +38,7 @@ ClientAction (ir.actions.client)
'mail_thread' widget for field on standard view. (default value like a thread for
record, view on flat mode, no reply, no read/unread)
'mail.widget' it's the root thread, used by 'mail.wall' and 'mail_thread'
+
- ``help`` : Text HTML to display if there are no message
- ``context`` : insert 'default_model' and 'default_res_id'
- ``params`` : options for the widget
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index ec9bc86d6c5..a9d33f4585d 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -85,9 +85,6 @@ class mail_notification(osv.Model):
if notification.read:
continue
partner = notification.partner_id
- # Do not send an email to the writer
- if partner.user_ids and partner.user_ids[0].id == uid:
- continue
# Do not send to partners without email address defined
if not partner.email:
continue
@@ -129,11 +126,20 @@ class mail_notification(osv.Model):
if signature:
body_html = tools.append_content_to_html(body_html, signature, plaintext=True, container_tag='div')
+ # 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)
+ elif msg.author_id:
+ email_from = '%s <%s>' % (msg.author_id.name, msg.author_id.email)
+ else:
+ email_from = msg.email_from
+
mail_values = {
'mail_message_id': msg.id,
'email_to': [],
'auto_delete': True,
'body_html': body_html,
+ 'email_from': email_from,
'state': 'outgoing',
}
mail_values['email_to'] = ', '.join(mail_values['email_to'])
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index 7b8ec0b3579..eb5ec30496e 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -78,6 +78,13 @@ class mail_mail(osv.Model):
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
}
+ def default_get(self, cr, uid, fields, context=None):
+ # protection for `default_type` values leaking from menu action context (e.g. for invoices)
+ # To remove when automatic context propagation is removed in web client
+ if context and context.get('default_type') and context.get('default_type') not in self._all_columns['type'].column.selection:
+ context = dict(context, default_type = None)
+ return super(mail_mail, self).default_get(cr, uid, fields, context=context)
+
def create(self, cr, uid, values, context=None):
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index d75a9199369..9d7237ca907 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -243,7 +243,7 @@ class mail_thread(osv.AbstractModel):
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
- self.message_subscribe_from_parent(cr, uid, [thread_id], values.keys(), context=context)
+ self.message_auto_subscribe(cr, uid, [thread_id], values.keys(), context=context)
# automatic logging unless asked not to (mainly for various testing purpose)
if not context.get('mail_create_nolog'):
@@ -261,7 +261,7 @@ class mail_thread(osv.AbstractModel):
# Perform write, update followers
result = super(mail_thread, self).write(cr, uid, ids, values, context=context)
- self.message_subscribe_from_parent(cr, uid, ids, values.keys(), context=context)
+ self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context)
# Perform the tracking
if tracked_fields:
@@ -1069,7 +1069,24 @@ class mail_thread(osv.AbstractModel):
self.check_access_rights(cr, uid, 'write')
return self.write(cr, SUPERUSER_ID, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
- def message_subscribe_from_parent(self, cr, uid, ids, updated_fields, context=None):
+ def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=['user_id'], context=None):
+ """ Returns the list of relational fields linking to res.users that should
+ trigger an auto subscribe. The default list checks for the fields
+ - called 'user_id'
+ - linking to res.users
+ - with track_visibility set
+ In OpenERP V7, this is sufficent for all major addon such as opportunity,
+ project, issue, recruitment, sale.
+ Override this method if a custom behavior is needed about fields
+ that automatically subscribe users.
+ """
+ user_field_lst = []
+ for name, column_info in self._all_columns.items():
+ if name in auto_follow_fields and name in updated_fields and getattr(column_info.column, 'track_visibility', False) and column_info.column._obj == 'res.users':
+ user_field_lst.append(name)
+ return user_field_lst
+
+ def message_auto_subscribe(self, cr, uid, ids, updated_fields, context=None):
"""
1. fetch project subtype related to task (parent_id.res_model = 'project.task')
2. for each project subtype: subscribe the follower to the task
@@ -1077,13 +1094,16 @@ class mail_thread(osv.AbstractModel):
subtype_obj = self.pool.get('mail.message.subtype')
follower_obj = self.pool.get('mail.followers')
+ # fetch auto_follow_fields
+ user_field_lst = self._message_get_auto_subscribe_fields(cr, uid, updated_fields, context=context)
+
# fetch related record subtypes
related_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context)
subtypes = subtype_obj.browse(cr, uid, related_subtype_ids, context=context)
default_subtypes = [subtype for subtype in subtypes if subtype.res_model == False]
related_subtypes = [subtype for subtype in subtypes if subtype.res_model != False]
relation_fields = set([subtype.relation_field for subtype in subtypes if subtype.relation_field != False])
- if not related_subtypes or not any(relation in updated_fields for relation in relation_fields):
+ if (not related_subtypes or not any(relation in updated_fields for relation in relation_fields)) and not user_field_lst:
return True
for record in self.browse(cr, uid, ids, context=context):
@@ -1105,20 +1125,24 @@ class mail_thread(osv.AbstractModel):
for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
new_followers.setdefault(follower.partner_id.id, set()).add(subtype.parent_id.id)
- if not parent_res_id or not parent_model:
- continue
+ if parent_res_id and parent_model:
+ for subtype in default_subtypes:
+ follower_ids = follower_obj.search(cr, SUPERUSER_ID, [
+ ('res_model', '=', parent_model),
+ ('res_id', '=', parent_res_id),
+ ('subtype_ids', 'in', [subtype.id])
+ ], context=context)
+ for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
+ new_followers.setdefault(follower.partner_id.id, set()).add(subtype.id)
- for subtype in default_subtypes:
- follower_ids = follower_obj.search(cr, SUPERUSER_ID, [
- ('res_model', '=', parent_model),
- ('res_id', '=', parent_res_id),
- ('subtype_ids', 'in', [subtype.id])
- ], context=context)
- for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context):
- new_followers.setdefault(follower.partner_id.id, set()).add(subtype.id)
+ # add followers coming from res.users relational fields that are tracked
+ user_ids = [getattr(record, name).id for name in user_field_lst if getattr(record, name)]
+ for partner_id in [user.partner_id.id for user in self.pool.get('res.users').browse(cr, SUPERUSER_ID, user_ids, context=context)]:
+ new_followers.setdefault(partner_id, None)
for pid, subtypes in new_followers.items():
- self.message_subscribe(cr, uid, [record.id], [pid], list(subtypes), context=context)
+ subtypes = list(subtypes) if subtypes is not None else None
+ self.message_subscribe(cr, uid, [record.id], [pid], subtypes, context=context)
return True
#------------------------------------------------------
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index e3986a390d6..0b3bddffde1 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -105,7 +105,7 @@ openerp.mail = function (session) {
// As it only looks at the extension it is quite approximative.
filetype: function(url){
url = url.filename || url;
- var tokens = url.split('.');
+ var tokens = (url+'').split('.');
if(tokens.length <= 1){
return 'unknown';
}
@@ -218,7 +218,7 @@ openerp.mail = function (session) {
this.author_id = datasets.author_id || false,
this.attachment_ids = datasets.attachment_ids || [],
this.partner_ids = datasets.partner_ids || [];
- this._date = datasets.date;
+ this.date = datasets.date;
this.format_data();
@@ -232,19 +232,20 @@ openerp.mail = function (session) {
else {
this.options.show_read = this.to_read;
this.options.show_unread = !this.to_read;
- this.options.rerender = true;
- this.options.toggle_read = true;
}
+ this.options.rerender = true;
+ this.options.toggle_read = true;
}
- this.parent_thread = parent.messages != undefined ? parent : this.options.root_thread;
+ this.parent_thread = typeof parent.on_message_detroy == 'function' ? parent : this.options.root_thread;
this.thread = false;
},
/* Convert date, timerelative and avatar in displayable data. */
format_data: function () {
//formating and add some fields for render
- if (this._date) {
- this.timerelative = $.timeago(this._date+"Z");
+ this.date = this.date ? session.web.str_to_datetime(this.date) : false;
+ if (this.date && new Date().getTime()-this.date.getTime() < 7*24*60*60*1000) {
+ this.timerelative = $.timeago(this.date);
}
if (this.type == 'email' && (!this.author_id || !this.author_id[0])) {
this.avatar = ('/mail/static/src/img/email_icon.png');
@@ -253,7 +254,7 @@ openerp.mail = function (session) {
} else {
this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
}
- if (this.author_id) {
+ if (this.author_id && this.author_id[1]) {
var email = this.author_id[1].match(/(.*)<(.*@.*)>/);
if (!email) {
this.author_id.push(_.str.escapeHTML(this.author_id[1]), '', this.author_id[1]);
@@ -271,7 +272,7 @@ openerp.mail = function (session) {
var attach = this.attachment_ids[l];
if (!attach.formating) {
attach.url = mail.ChatterUtils.get_attachment_url(this.session, this.id, attach.id);
- attach.filetype = mail.ChatterUtils.filetype(attach.filename);
+ attach.filetype = mail.ChatterUtils.filetype(attach.filename || attach.name);
attach.name = mail.ChatterUtils.breakword(attach.name || attach.filename);
attach.formating = true;
}
@@ -1605,7 +1606,8 @@ openerp.mail = function (session) {
this.node.params = _.extend({
'display_indented_thread': -1,
'show_reply_button': false,
- 'show_read_unread_button': false,
+ 'show_read_unread_button': true,
+ 'read_action': 'unread',
'show_record_name': false,
'show_compact_message': 1,
}, this.node.params);
diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js
index 9f88116acc7..03351c24ac2 100644
--- a/addons/mail/static/src/js/mail_followers.js
+++ b/addons/mail/static/src/js/mail_followers.js
@@ -216,9 +216,12 @@ openerp_mail_followers = function(session, mail) {
display_subtypes:function (data) {
var self = this;
var subtype_list_ul = this.$('.oe_subtype_list');
- subtype_list_ul.empty();
- var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
+ var records = [];
var nb_subtype = 0;
+ subtype_list_ul.empty();
+ if (this.view.datarecord.id) {
+ records = data[this.view.datarecord.id].message_subtype_data;
+ }
_(records).each(function (record) {nb_subtype++;});
if (nb_subtype > 1) {
this.$('hr').show();
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index 63f8a10658f..4693adeb1ee 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -246,7 +246,7 @@
•
-
+
•
diff --git a/addons/membership/wizard/membership_invoice.py b/addons/membership/wizard/membership_invoice.py
index 85a9cc0c85b..2425cd648ea 100644
--- a/addons/membership/wizard/membership_invoice.py
+++ b/addons/membership/wizard/membership_invoice.py
@@ -52,9 +52,16 @@ class membership_invoice(osv.osv_memory):
'amount': data.member_price
}
invoice_list = partner_obj.create_membership_invoice(cr, uid, context.get('active_ids', []), datas=datas, context=context)
-
- res = mod_obj.get_object_reference(cr, uid, 'account', 'view_account_invoice_filter')
-
+
+ try:
+ search_view_id = mod_obj.get_object_reference(cr, uid, 'account', 'view_account_invoice_filter')[1]
+ except ValueError:
+ search_view_id = False
+ try:
+ form_view_id = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')[1]
+ except ValueError:
+ form_view_id = False
+
return {
'domain': [('id', 'in', invoice_list)],
'name': 'Membership Invoices',
@@ -62,7 +69,8 @@ class membership_invoice(osv.osv_memory):
'view_mode': 'tree,form',
'res_model': 'account.invoice',
'type': 'ir.actions.act_window',
- 'search_view_id': res and res[1] or False
+ 'views': [(False, 'tree'), (form_view_id, 'form')],
+ 'search_view_id': search_view_id,
}
membership_invoice()
diff --git a/addons/mrp/__openerp__.py b/addons/mrp/__openerp__.py
index 66806c4f350..6e025ae2d90 100644
--- a/addons/mrp/__openerp__.py
+++ b/addons/mrp/__openerp__.py
@@ -28,7 +28,7 @@
'category': 'Manufacturing',
'sequence': 18,
'summary': 'Manufacturing Orders, Bill of Materials, Routing',
- 'images': ['images/bill_of_materials.jpeg', 'images/manufacturing_order.jpeg', 'images/planning_manufacturing_order.jpeg', 'images/production_analysis.jpeg', 'images/production_dashboard.jpeg','images/routings.jpeg','images/work_centers.jpeg'],
+ 'images': ['images/bill_of_materials.jpeg', 'images/manufacturing_order.jpeg', 'images/planning_manufacturing_order.jpeg', 'images/manufacturing_analysis.jpeg', 'images/production_dashboard.jpeg','images/routings.jpeg','images/work_centers.jpeg'],
'depends': ['product','procurement', 'stock', 'resource', 'purchase','process'],
'description': """
Manage the Manufacturing process in OpenERP
diff --git a/addons/note/__openerp__.py b/addons/note/__openerp__.py
index 797270637e2..2653c558923 100644
--- a/addons/note/__openerp__.py
+++ b/addons/note/__openerp__.py
@@ -57,6 +57,11 @@ Notes can be found in the 'Home' menu.
'css': [
'static/src/css/note.css',
],
+ 'images': [
+ 'images/note_kanban.jpeg',
+ 'images/note.jpeg',
+ 'images/categories_tree.jpeg'
+ ],
'installable': True,
'application': True,
'auto_install': False,
diff --git a/addons/note/note.py b/addons/note/note.py
index 7de91e4703a..2d95b15107a 100644
--- a/addons/note/note.py
+++ b/addons/note/note.py
@@ -29,7 +29,7 @@ class note_stage(osv.osv):
_columns = {
'name': fields.char('Stage Name', translate=True, required=True),
'sequence': fields.integer('Sequence', help="Used to order the note stages"),
- 'user_id': fields.many2one('res.users', 'Owner', help="Owner of the note stage.", required=True),
+ 'user_id': fields.many2one('res.users', 'Owner', help="Owner of the note stage.", required=True, ondelete='cascade'),
'fold': fields.boolean('Folded by Default'),
}
_order = 'sequence asc'
@@ -112,7 +112,7 @@ class note_note(osv.osv):
'date_done': fields.date('Date done'),
'color': fields.integer('Color Index'),
'tag_ids' : fields.many2many('note.tag','note_tags_rel','note_id','tag_id','Tags'),
- 'current_partner_id' : fields.function(_get_my_current_partner),
+ 'current_partner_id' : fields.function(_get_my_current_partner, type="many2one", relation='res.partner', string="Owner"),
}
_defaults = {
'open' : 1,
diff --git a/addons/pad/pad.py b/addons/pad/pad.py
index 12fa8254d00..90f5354054c 100644
--- a/addons/pad/pad.py
+++ b/addons/pad/pad.py
@@ -24,7 +24,7 @@ class pad_common(osv.osv_memory):
# make sure pad server in the form of http://hostname
if not pad["server"]:
- return ''
+ return pad
if not pad["server"].startswith('http'):
pad["server"] = 'http://' + pad["server"]
pad["server"] = pad["server"].rstrip('/')
@@ -96,7 +96,7 @@ class pad_common(osv.osv_memory):
field = v.column
if hasattr(field,'pad_content_field'):
pad = self.pad_generate_url(cr, uid, context)
- default[k] = pad['url']
+ default[k] = pad.get('url')
return super(pad_common, self).copy(cr, uid, id, default, context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py
index d68bd240c4f..cb05cef46c3 100644
--- a/addons/point_of_sale/__openerp__.py
+++ b/addons/point_of_sale/__openerp__.py
@@ -48,7 +48,7 @@ Main Features
* Refund previous sales
""",
'author': 'OpenERP SA',
- 'images': ['images/cash_registers.jpeg', 'images/pos_analysis.jpeg','images/register_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'],
+ 'images': ['images/pos_touch_screen.jpeg', 'images/pos_session.jpeg', 'images/pos_analysis.jpeg','images/sale_order_pos.jpeg','images/product_pos.jpeg'],
'depends': ['sale_stock'],
'data': [
'security/point_of_sale_security.xml',
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 970eb1fbed7..8e47d83aa24 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -853,8 +853,7 @@ class pos_order(osv.osv):
inv_line['price_unit'] = line.price_unit
inv_line['discount'] = line.discount
inv_line['name'] = inv_name
- inv_line['invoice_line_tax_id'] = ('invoice_line_tax_id' in inv_line)\
- and [(6, 0, inv_line['invoice_line_tax_id'])] or []
+ inv_line['invoice_line_tax_id'] = [(6, 0, [x.id for x in line.product_id.taxes_id] )]
inv_line_ref.create(cr, uid, inv_line, context=context)
inv_ref.button_reset_taxes(cr, uid, [inv_id], context=context)
wf_service.trg_validate(uid, 'pos.order', order.id, 'invoice', cr)
@@ -1156,7 +1155,6 @@ class pos_order_line(osv.osv):
prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
- taxes = prod.taxes_id
price = price_unit * (1 - (discount or 0.0) / 100.0)
taxes = account_tax_obj.compute_all(cr, uid, prod.taxes_id, price, qty, product=prod, partner=False)
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 2c086032ec3..d9a4d6d00a9 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -233,7 +233,9 @@
font-style: italic;
cursor:pointer;
}
-
+.point-of-sale .oe_pos_synch-notification.oe_inactive{
+ cursor: default;
+}
.point-of-sale .oe_pos_synch-notification .oe_status_red{
display:inline-block;
cursor:pointer;
@@ -542,7 +544,9 @@
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
- background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
+ /* for some reason the -90deg orientation doesn't match the -webkit-linear-gradient. It should be 180deg here.
+ * webkit also insists on rendering *both* gradients instead of only the native one. So it doesn't looks right. ugh.
+ background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); */
/*background:#FFF;*/
padding: 3px;
padding-top: 15px;
@@ -607,7 +611,9 @@
background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
+ /* troublesome in latest webkit
background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1));
+ */
/*background:#FFF;*/
padding: 3px;
padding-top:15px;
@@ -991,12 +997,20 @@
margin-bottom:10px;
}
.point-of-sale .order .summary .line{
+ float: right;
margin-right:15px;
+ margin-left: 15px;
padding-top:5px;
border-top: solid 2px;
border-color:#777;
}
+.point-of-sale .order .summary .line .subentry{
+ font-size: 10px;
+ font-weight: normal;
+ text-align: center;
+}
.point-of-sale .order .summary .line.empty{
+ text-align: right;
border-color:#BBB;
color:#999;
}
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index 666c61919da..2be58d6ce97 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -40,6 +40,10 @@ function openerp_pos_db(instance, module){
//cache the data in memory to avoid roundtrips to the localstorage
this.cache = {};
+ this.product_by_id = {};
+ this.product_by_ean13 = {};
+ this.product_by_category_id = {};
+
this.category_by_id = {};
this.root_category_id = 0;
this.category_products = {};
@@ -49,6 +53,7 @@ function openerp_pos_db(instance, module){
this.category_search_string = {};
this.packagings_by_id = {};
this.packagings_by_product_id = {};
+ this.packagings_by_ean13 = {};
},
/* returns the category object from its id. If you pass a list of id as parameters, you get
* a list of category objects.
@@ -137,7 +142,6 @@ function openerp_pos_db(instance, module){
/* saves a record store to the database */
save: function(store,data){
var str_data = JSON.stringify(data);
- console.log('Storing '+ Math.round(str_data.length/1024.0)+' KB of data to store: '+store);
localStorage[this.name + '_' + store] = JSON.stringify(data);
this.cache[store] = data;
},
@@ -153,8 +157,7 @@ function openerp_pos_db(instance, module){
return str + '\n';
},
add_products: function(products){
- var stored_products = this.load('products',{});
- var stored_categories = this.load('categories',{});
+ var stored_categories = this.product_by_category_id;
if(!products instanceof Array){
products = [products];
@@ -187,10 +190,11 @@ function openerp_pos_db(instance, module){
}
this.category_search_string[ancestor] += search_string;
}
- stored_products[product.id] = product;
+ this.product_by_id[product.id] = product;
+ if(product.ean13){
+ this.product_by_ean13[product.ean13] = product;
+ }
}
- this.save('products',stored_products);
- this.save('categories',stored_categories);
},
add_packagings: function(packagings){
for(var i = 0, len = packagings.length; i < len; i++){
@@ -200,6 +204,9 @@ function openerp_pos_db(instance, module){
this.packagings_by_product_id[pack.product_id[0]] = [];
}
this.packagings_by_product_id[pack.product_id[0]].push(pack);
+ if(pack.ean13){
+ this.packagings_by_ean13[pack.ean13] = pack;
+ }
}
},
/* removes all the data from the database. TODO : being able to selectively remove data */
@@ -219,31 +226,24 @@ function openerp_pos_db(instance, module){
return count;
},
get_product_by_id: function(id){
- return this.load('products',{})[id];
+ return this.product_by_id[id];
},
get_product_by_ean13: function(ean13){
- var products = this.load('products',{});
- for(var i in products){
- if( products[i] && products[i].ean13 === ean13){
- return products[i];
- }
+ if(this.product_by_ean13[ean13]){
+ return this.product_by_ean13[ean13];
}
- for(var p in this.packagings_by_id){
- var pack = this.packagings_by_id[p];
- if( pack.ean === ean13){
- return products[pack.product_id[0]];
- }
+ var pack = this.packagings_by_ean13[ean13];
+ if(pack){
+ return this.product_by_id[pack.product_id[0]];
}
return undefined;
},
get_product_by_category: function(category_id){
- var stored_categories = this.load('categories',{});
- var stored_products = this.load('products',{});
- var product_ids = stored_categories[category_id];
+ var product_ids = this.product_by_category_id[category_id];
var list = [];
if (product_ids) {
for (var i = 0, len = Math.min(product_ids.length, this.limit); i < len; i++) {
- list.push(stored_products[product_ids[i]]);
+ list.push(this.product_by_id[product_ids[i]]);
}
}
return list;
@@ -275,12 +275,9 @@ function openerp_pos_db(instance, module){
},
remove_order: function(order_id){
var orders = this.load('orders',[]);
- console.log('Remove order:',order_id);
- console.log('Order count:',orders.length);
orders = _.filter(orders, function(order){
return order.id !== order_id;
});
- console.log('Order count:',orders.length);
this.save('orders',orders);
},
get_orders: function(){
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 556ea482aad..d2cf15b54f3 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1,6 +1,24 @@
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
+ // rounds a value with a fixed number of decimals.
+ // round(3.141492,2) -> 3.14
+ function round(value,decimals){
+ var mult = Math.pow(10,decimals || 0);
+ return Math.round(value*mult)/mult;
+ }
+ window.round = round;
+
+ // rounds a value with decimal form precision
+ // round(3.141592,0.025) ->3.125
+ function round_pr(value,precision){
+ if(!precision || precision < 0){
+ throw new Error('round_pr(): needs a precision greater than zero, got '+precision+' instead');
+ }
+ return Math.round(value / precision) * precision;
+ }
+ window.round_pr = round_pr;
+
// The PosModel contains the Point Of Sale's representation of the backend.
// Since the PoS must work in standalone ( Without connection to the server )
@@ -24,8 +42,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy
this.db = new module.PosLS(); // a database used to store the products and categories
this.db.clear('products','categories');
- this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
-
+ this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
// default attributes values. If null, it will be loaded below.
this.set({
@@ -101,8 +118,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}).then(function(company_partners){
self.get('company').contact_address = company_partners[0].contact_address;
- return self.fetch('res.currency',['symbol','position'],[['id','=',self.get('company').currency_id[0]]]);
+ return self.fetch('res.currency',['symbol','position','rounding','accuracy'],[['id','=',self.get('company').currency_id[0]]]);
}).then(function(currencies){
+ console.log('Currency:',currencies[0]);
self.set('currency',currencies[0]);
return self.fetch('product.uom', null, null);
@@ -117,7 +135,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return self.fetch('product.packaging', null, null);
}).then(function(packagings){
self.set('product.packaging',packagings);
-
+
return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
}).then(function(users){
self.set('user_list',users);
@@ -211,7 +229,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// logs the usefull posmodel data to the console for debug purposes
log_loaded_data: function(){
console.log('PosModel data has been loaded:');
- console.log('PosModel: categories:',this.get('categories'));
console.log('PosModel: units:',this.get('units'));
console.log('PosModel: bank_statements:',this.get('bank_statements'));
console.log('PosModel: journals:',this.get('journals'));
@@ -339,19 +356,26 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.product = options.product;
this.price = options.product.get('price');
this.quantity = 1;
+ this.quantityStr = '1';
this.discount = 0;
+ this.discountStr = '0';
this.type = 'unit';
this.selected = false;
},
// sets a discount [0,100]%
set_discount: function(discount){
- this.discount = Math.max(0,Math.min(100,discount));
+ var disc = Math.min(Math.max(parseFloat(discount) || 0, 0),100);
+ this.discount = disc;
+ this.discountStr = '' + disc;
this.trigger('change');
},
// returns the discount [0,100]%
get_discount: function(){
return this.discount;
},
+ get_discount_str: function(){
+ return this.discountStr;
+ },
get_product_type: function(){
return this.type;
},
@@ -359,13 +383,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
// product's unity of measure properties. Quantities greater than zero will not get
// rounded to zero
set_quantity: function(quantity){
- if(_.isNaN(quantity)){
+ if(quantity === 'remove'){
this.order.removeOrderline(this);
- }else if(quantity !== undefined){
- this.quantity = Math.max(0,quantity);
+ return;
+ }else{
+ var quant = Math.max(parseFloat(quantity) || 0, 0);
var unit = this.get_unit();
- if(unit && this.quantity > 0 ){
- this.quantity = Math.max(unit.rounding, Math.round(quantity / unit.rounding) * unit.rounding);
+ if(unit){
+ this.quantity = Math.max(unit.rounding, Math.round(quant / unit.rounding) * unit.rounding);
+ this.quantityStr = this.quantity.toFixed(Math.max(0,Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))));
+ }else{
+ this.quantity = quant;
+ this.quantityStr = '' + this.quantity;
}
}
this.trigger('change');
@@ -374,6 +403,17 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
get_quantity: function(){
return this.quantity;
},
+ get_quantity_str: function(){
+ return this.quantityStr;
+ },
+ get_quantity_str_with_unit: function(){
+ var unit = this.get_unit();
+ if(unit && unit.name !== 'Unit(s)'){
+ return this.quantityStr + ' ' + unit.name;
+ }else{
+ return this.quantityStr;
+ }
+ },
// return the unit of measure of the product
get_unit: function(){
var unit_id = (this.product.get('uos_id') || this.product.get('uom_id'));
@@ -390,15 +430,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
get_product: function(){
return this.product;
},
- // return the base price of this product (for this orderline)
- get_price: function(){
- return this.price;
- },
- // changes the base price of the product for this orderline
- set_price: function(price){
- this.price = price;
- this.trigger('change');
- },
// selects or deselects this orderline
set_selected: function(selected){
this.selected = selected;
@@ -429,7 +460,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
export_as_JSON: function() {
return {
qty: this.get_quantity(),
- price_unit: this.get_price(),
+ price_unit: this.get_unit_price(),
discount: this.get_discount(),
product_id: this.get_product().get('id'),
};
@@ -439,9 +470,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return {
quantity: this.get_quantity(),
unit_name: this.get_unit().name,
- price: this.get_price(),
+ price: this.get_unit_price(),
discount: this.get_discount(),
product_name: this.get_product().get('name'),
+ price_display : this.get_display_price(),
price_with_tax : this.get_price_with_tax(),
price_without_tax: this.get_price_without_tax(),
tax: this.get_tax(),
@@ -449,6 +481,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
product_description_sale: this.get_product().get('description_sale'),
};
},
+ // changes the base price of the product for this orderline
+ set_unit_price: function(price){
+ this.price = round(parseFloat(price) || 0, 2);
+ this.trigger('change');
+ },
+ get_unit_price: function(){
+ var rounding = this.pos.get('currency').rounding;
+ return round_pr(this.price,rounding);
+ },
+ get_display_price: function(){
+ var rounding = this.pos.get('currency').rounding;
+ return round_pr(round_pr(this.get_unit_price() * this.get_quantity(),rounding) * (1- this.get_discount()/100.0),rounding);
+ },
get_price_without_tax: function(){
return this.get_all_prices().priceWithoutTax;
},
@@ -458,9 +503,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
get_tax: function(){
return this.get_all_prices().tax;
},
- get_all_prices: function() {
+ get_all_prices: function(){
var self = this;
- var base = this.get_quantity() * this.price * (1 - (this.get_discount() / 100));
+ var currency_rounding = this.pos.get('currency').rounding;
+ var base = round_pr(this.get_quantity() * this.get_unit_price() * (1.0 - (this.get_discount() / 100.0)), currency_rounding);
var totalTax = base;
var totalNoTax = base;
@@ -474,12 +520,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
if (tax.price_include) {
var tmp;
if (tax.type === "percent") {
- tmp = base - (base / (1 + tax.amount));
+ tmp = base - round_pr(base / (1 + tax.amount),currency_rounding);
} else if (tax.type === "fixed") {
- tmp = tax.amount * self.get_quantity();
+ tmp = round_pr(tax.amount * self.get_quantity(),currency_rounding);
} else {
throw "This type of tax is not supported by the point of sale: " + tax.type;
}
+ tmp = round_pr(tmp,currency_rounding);
taxtotal += tmp;
totalNoTax -= tmp;
} else {
@@ -491,6 +538,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
} else {
throw "This type of tax is not supported by the point of sale: " + tax.type;
}
+ tmp = round_pr(tmp,currency_rounding);
taxtotal += tmp;
totalTax += tmp;
}
@@ -515,7 +563,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
},
//sets the amount of money on this payment line
set_amount: function(value){
- this.amount = value;
+ this.amount = parseFloat(value) || 0;
this.trigger('change');
},
// returns the amount of money on this paymentline
@@ -584,7 +632,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
line.set_quantity(options.quantity);
}
if(options.price !== undefined){
- line.set_price(options.price);
+ line.set_unit_price(options.price);
}
var last_orderline = this.getLastOrderline();
@@ -613,14 +661,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
getName: function() {
return this.get('name');
},
- getTotal: function() {
+ getSubtotal : function(){
+ return (this.get('orderLines')).reduce((function(sum, orderLine){
+ return sum + orderLine.get_display_price();
+ }), 0);
+ },
+ getTotalTaxIncluded: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) {
return sum + orderLine.get_price_with_tax();
}), 0);
},
getDiscountTotal: function() {
return (this.get('orderLines')).reduce((function(sum, orderLine) {
- return sum + (orderLine.get_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
+ return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
}), 0);
},
getTotalTaxExcluded: function() {
@@ -639,10 +692,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}), 0);
},
getChange: function() {
- return this.getPaidTotal() - this.getTotal();
+ return this.getPaidTotal() - this.getTotalTaxIncluded();
},
getDueLeft: function() {
- return this.getTotal() - this.getPaidTotal();
+ return this.getTotalTaxIncluded() - this.getPaidTotal();
},
// sets the type of receipt 'receipt'(default) or 'invoice'
set_receipt_type: function(type){
@@ -698,10 +751,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return {
orderlines: orderlines,
paymentlines: paymentlines,
- total_with_tax: this.getTotal(),
+ subtotal: this.getSubtotal(),
+ total_with_tax: this.getTotalTaxIncluded(),
total_without_tax: this.getTotalTaxExcluded(),
total_tax: this.getTax(),
total_paid: this.getPaidTotal(),
+ total_discount: this.getDiscountTotal(),
change: this.getChange(),
name : this.getName(),
client: client ? client.name : null ,
@@ -743,7 +798,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
return {
name: this.getName(),
amount_paid: this.getPaidTotal(),
- amount_total: this.getTotal(),
+ amount_total: this.getTotalTaxIncluded(),
amount_tax: this.getTax(),
amount_return: this.getChange(),
lines: orderLines,
@@ -800,20 +855,19 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
buffer: (this.get('buffer')) + newChar
});
}
- this.updateTarget();
+ this.trigger('set_value',this.get('buffer'));
},
deleteLastChar: function() {
- var tempNewBuffer = this.get('buffer').slice(0, -1);
-
- if(!tempNewBuffer){
- this.set({ buffer: "0" });
- this.killTarget();
- }else{
- if (isNaN(tempNewBuffer)) {
- tempNewBuffer = "0";
+ if(this.get('buffer') === ""){
+ if(this.get('mode') === 'quantity'){
+ this.trigger('set_value','remove');
+ }else{
+ this.trigger('set_value',this.get('buffer'));
}
- this.set({ buffer: tempNewBuffer });
- this.updateTarget();
+ }else{
+ var newBuffer = this.get('buffer').slice(0,-1) || "";
+ this.set({ buffer: newBuffer });
+ this.trigger('set_value',this.get('buffer'));
}
},
switchSign: function() {
@@ -822,7 +876,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.set({
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
});
- this.updateTarget();
+ this.trigger('set_value',this.get('buffer'));
},
changeMode: function(newMode) {
this.set({
@@ -836,15 +890,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
mode: "quantity"
});
},
- updateTarget: function() {
- var bufferContent, params;
- bufferContent = this.get('buffer');
- if (bufferContent && !isNaN(bufferContent)) {
- this.trigger('set_value', parseFloat(bufferContent));
- }
- },
- killTarget: function(){
- this.trigger('set_value',Number.NaN);
+ resetValue: function(){
+ this.set({buffer:'0'});
},
});
}
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index f8e3382c242..eca4a3c5915 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -368,7 +368,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
template:'ChooseReceiptPopupWidget',
show: function(){
- console.log('show');
this._super();
this.renderElement();
var self = this;
@@ -603,7 +602,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// initiates the connection to the payment terminal and starts the update requests
this.start = function(){
var def = new $.Deferred();
- console.log("START");
self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft())
.done(function(ack){
if(ack === 'ok'){
@@ -613,7 +611,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}else{
console.error('unknown payment request return value:',ack);
}
- console.log("START_END");
def.resolve();
});
return def;
@@ -621,10 +618,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// gets updated status from the payment terminal and performs the appropriate consequences
this.update = function(){
- console.log("UPDATE");
var def = new $.Deferred();
if(self.canceled){
- console.log("UPDATE_END");
return def.resolve();
}
self.pos.proxy.payment_status()
@@ -656,7 +651,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}else{
console.error('unknown status value:',status.status);
}
- console.log("UPDATE_END");
def.resolve();
});
return def;
@@ -664,14 +658,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// cancels a payment.
this.cancel = function(){
- console.log("CANCEL");
if(!self.paid && !self.canceled){
self.canceled = true;
self.pos.proxy.payment_cancel();
self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
self.queue.clear();
}
- console.log("CANCEL_END");
return (new $.Deferred()).resolve();
}
@@ -865,6 +857,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.bindPaymentLineEvents();
this.bind_orderline_events();
this.paymentlinewidgets = [];
+ this.focusedLine = null;
},
show: function(){
this._super();
@@ -894,6 +887,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
});
this.updatePaymentSummary();
+ this.line_refocus();
},
close: function(){
this._super();
@@ -931,17 +925,30 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.bind_orderline_events();
this.renderElement();
},
+ line_refocus: function(lineWidget){
+ if(lineWidget){
+ if(this.focusedLine !== lineWidget){
+ this.focusedLine = lineWidget;
+ }
+ }
+ if(this.focusedLine){
+ this.focusedLine.focus();
+ }
+ },
addPaymentLine: function(newPaymentLine) {
var self = this;
- var l = new module.PaymentlineWidget(null, {
- payment_line: newPaymentLine
+ var l = new module.PaymentlineWidget(this, {
+ payment_line: newPaymentLine,
});
l.on('delete_payment_line', self, function(r) {
self.deleteLine(r);
});
l.appendTo(this.$('#paymentlines'));
this.paymentlinewidgets.push(l);
- this.$('.paymentline-amount input:last').focus();
+ if(this.numpadState){
+ this.numpadState.resetValue();
+ }
+ this.line_refocus(l);
},
renderElement: function() {
this._super();
@@ -958,25 +965,26 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
deleteLine: function(lineWidget) {
this.currentPaymentLines.remove([lineWidget.payment_line]);
+ lineWidget.destroy();
},
updatePaymentSummary: function() {
var currentOrder = this.pos.get('selectedOrder');
var paidTotal = currentOrder.getPaidTotal();
- var dueTotal = currentOrder.getTotal();
+ var dueTotal = currentOrder.getTotalTaxIncluded();
var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
- this.$('#payment-due-total').html(dueTotal.toFixed(2));
- this.$('#payment-paid-total').html(paidTotal.toFixed(2));
- this.$('#payment-remaining').html(remaining.toFixed(2));
- this.$('#payment-change').html(change.toFixed(2));
- if((currentOrder.selected_orderline == undefined))
- remaining = 1
+ this.$('#payment-due-total').html(this.format_currency(dueTotal));
+ this.$('#payment-paid-total').html(this.format_currency(paidTotal));
+ this.$('#payment-remaining').html(this.format_currency(remaining));
+ this.$('#payment-change').html(this.format_currency(change));
+ if(currentOrder.selected_orderline === undefined){
+ remaining = 1; // What is this ?
+ }
if(this.pos_widget.action_bar){
this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0);
}
- this.$('.paymentline-amount input:last').focus();
},
set_numpad_state: function(numpadState) {
if (this.numpadState) {
@@ -998,5 +1006,4 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
this.currentPaymentLines.last().set_amount(val);
},
});
-
}
diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js
index 0aa0a44e7e7..124be4e9f82 100644
--- a/addons/point_of_sale/static/src/js/widget_base.js
+++ b/addons/point_of_sale/static/src/js/widget_base.js
@@ -22,14 +22,20 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of
if(this.pos && this.pos.get('currency')){
this.currency = this.pos.get('currency');
}else{
- this.currency = {symbol: '$', position: 'after'};
+ this.currency = {symbol: '$', position: 'after', rounding: 0.01};
}
+ var decimals = Math.max(0,Math.ceil(Math.log(1.0 / this.currency.rounding) / Math.log(10)));
+
this.format_currency = function(amount){
+ if(typeof amount === 'number'){
+ amount = Math.round(amount*100)/100;
+ amount = amount.toFixed(decimals);
+ }
if(this.currency.position === 'after'){
- return Math.round(amount*100)/100 + ' ' + this.currency.symbol;
+ return amount + ' ' + this.currency.symbol;
}else{
- return this.currency.symbol + ' ' + Math.round(amount*100)/100;
+ return this.currency.symbol + ' ' + amount;
}
}
diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js
index 39ac2529a76..874c387ea23 100644
--- a/addons/point_of_sale/static/src/js/widgets.js
+++ b/addons/point_of_sale/static/src/js/widgets.js
@@ -186,7 +186,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
}else if( mode === 'discount'){
order.getSelectedLine().set_discount(val);
}else if( mode === 'price'){
- order.getSelectedLine().set_price(val);
+ order.getSelectedLine().set_unit_price(val);
}
} else {
this.pos.get('selectedOrder').destroy();
@@ -269,8 +269,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
update_summary: function(){
var order = this.pos.get('selectedOrder');
- var total = order ? order.getTotal() : 0;
- this.$('.summary .value.total').html(this.format_currency(total));
+ var total = order ? order.getTotalTaxIncluded() : 0;
+ var taxes = order ? total - order.getTotalTaxExcluded() : 0;
+ this.$('.summary .total > .value').html(this.format_currency(total));
+ this.$('.summary .total .subentry .value').html(this.format_currency(taxes));
},
set_display_mode: function(mode){
if(this.display_mode !== mode){
@@ -311,24 +313,34 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
},
changeAmount: function(event) {
var newAmount = event.currentTarget.value;
- if (newAmount && !isNaN(newAmount)) {
- this.amount = parseFloat(newAmount);
- this.payment_line.set_amount(this.amount);
+ var amount = parseFloat(newAmount);
+ if(!isNaN(amount)){
+ this.amount = amount;
+ this.payment_line.set_amount(amount);
}
},
changedAmount: function() {
- if (this.amount !== this.payment_line.get_amount())
+ if (this.amount !== this.payment_line.get_amount()){
this.renderElement();
+ }
},
renderElement: function() {
var self = this;
this.name = this.payment_line.get_cashregister().get('journal_id')[1];
this._super();
- this.$('input').keyup(_.bind(this.changeAmount, this));
+ this.$('input').keyup(function(event){
+ self.changeAmount(event);
+ });
this.$('.delete-payment-line').click(function() {
self.trigger('delete_payment_line', self);
});
},
+ focus: function(){
+ var val = this.$('input')[0].value;
+ this.$('input')[0].focus();
+ this.$('input')[0].value = val;
+ this.$('input')[0].select();
+ },
});
module.OrderButtonWidget = module.PosBaseWidget.extend({
@@ -606,20 +618,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
if(this.scrollbar){
this.scrollbar.destroy();
}
-
- this.pos.get('products')
- .chain()
- .map(function(product) {
- var product = new module.ProductWidget(self, {
- model: product,
- weight: self.weight,
- click_product_action: self.click_product_action,
- })
- self.productwidgets.push(product);
- return product;
- })
- .invoke('appendTo', this.$('.product-list'));
-
+ var products = this.pos.get('products').models || [];
+ for(var i = 0, len = products.length; i < len; i++){
+ var product = new module.ProductWidget(self, {
+ model: products[i],
+ click_product_action: this.click_product_action,
+ });
+ this.productwidgets.push(product);
+ product.appendTo(this.$('.product-list'));
+ }
this.scrollbar = new module.ScrollbarWidget(this,{
target_widget: this,
target_selector: '.product-list-scroller',
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 41a061e78b4..3da76ec12d8 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -48,11 +48,17 @@
-
+
+
+
+
+
+
@@ -214,11 +220,7 @@
Total:
-
-
-
-
-
+
@@ -227,31 +229,19 @@
Paid:
-
-
-
-
-
+
Remaining:
-
-
-
-
-
+
Change:
-
-
-
-
-
+
@@ -431,9 +421,13 @@
-
- Total: 0.00 €
-
+
+
+
Total: 0.00 €
+
Taxes: 0.00€
+
+
+
@@ -500,26 +494,26 @@
-
+
-
+
-
-
+
at
-
+
/
-
+
-
With a
- %
+ %
discount
@@ -571,33 +565,36 @@
Shop:
-
+
-
-
+
+
- With a % discount
+ With a % discount
|
-
+
|
-
+
|
+ Subtotal: |
+
+ |
Tax: |
-
+
|
Discount: |
-
+
|
Total: |
-
+
|
@@ -607,14 +604,14 @@
-
+
|
diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py
index 50efe0f500e..1ccc08236c7 100644
--- a/addons/procurement/procurement.py
+++ b/addons/procurement/procurement.py
@@ -103,7 +103,7 @@ class procurement_order(osv.osv):
readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
" a make to order method."),
'note': fields.text('Note'),
- 'message': fields.char('Latest error', size=124, help="Exception occurred while computing procurement orders."),
+ 'message': fields.char('Latest error', help="Exception occurred while computing procurement orders."),
'state': fields.selection([
('draft','Draft'),
('cancel','Cancelled'),
@@ -367,10 +367,22 @@ class procurement_order(osv.osv):
if message:
message = _("Procurement '%s' is in exception: ") % (procurement.name) + message
- cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
+ #temporary context passed in write to prevent an infinite loop
+ ctx_wkf = dict(context or {})
+ ctx_wkf['workflow.trg_write.%s' % self._name] = False
+ self.write(cr, uid, [procurement.id], {'message': message},context=ctx_wkf)
self.message_post(cr, uid, [procurement.id], body=message, context=context)
return ok
+ def _workflow_trigger(self, cr, uid, ids, trigger, context=None):
+ """ Don't trigger workflow for the element specified in trigger
+ """
+ wkf_op_key = 'workflow.%s.%s' % (trigger, self._name)
+ if context and not context.get(wkf_op_key, True):
+ # make sure we don't have a trigger loop while processing triggers
+ return
+ return super(procurement_order,self)._workflow_trigger(cr, uid, ids, trigger, context=context)
+
def action_produce_assign_service(self, cr, uid, ids, context=None):
""" Changes procurement state to Running.
@return: True
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py
index 85e33be777a..c0a516cf555 100644
--- a/addons/project/__openerp__.py
+++ b/addons/project/__openerp__.py
@@ -33,7 +33,10 @@
'images/project_task_tree.jpeg',
'images/project_task.jpeg',
'images/project.jpeg',
- 'images/task_analysis.jpeg'
+ 'images/task_analysis.jpeg',
+ 'images/project_kanban.jpeg',
+ 'images/task_kanban.jpeg',
+ 'images/task_stages.jpeg'
],
'depends': [
'base_setup',
diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml
index 56a21342d9e..025327d70b3 100644
--- a/addons/project/project_data.xml
+++ b/addons/project/project_data.xml
@@ -94,16 +94,19 @@
Task Blocked
project.task
+
Task blocked
Task Done
project.task
+
Task closed
Stage Changed
project.task
+
Stage changed
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index b0b7193b7aa..8eb7281cd24 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -501,6 +501,7 @@ class project_issue(base_stage, osv.osv):
'description': desc,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
+ 'partner_id': msg.get('author_id', False),
'user_id': False,
}
if msg.get('priority'):
diff --git a/addons/project_issue/project_issue_data.xml b/addons/project_issue/project_issue_data.xml
index 55ec90b430d..591e6c04878 100644
--- a/addons/project_issue/project_issue_data.xml
+++ b/addons/project_issue/project_issue_data.xml
@@ -59,16 +59,19 @@ Access all issues from the top Project menu, and access the issues of a specific
Issue Blocked
project.issue
+
Issue blocked
Issue Closed
project.issue
+
Issue closed
Stage Changed
project.issue
+
Stage changed
diff --git a/addons/purchase/purchase_data.xml b/addons/purchase/purchase_data.xml
index 72c3f5ab044..ceb43ba281a 100644
--- a/addons/purchase/purchase_data.xml
+++ b/addons/purchase/purchase_data.xml
@@ -52,10 +52,12 @@
RFQ Confirmed
+
purchase.order
RFQ Approved
+
purchase.order
diff --git a/addons/sale/res_config.py b/addons/sale/res_config.py
index 44733be3e39..a693fd11024 100644
--- a/addons/sale/res_config.py
+++ b/addons/sale/res_config.py
@@ -19,10 +19,14 @@
#
##############################################################################
+import logging
+
from openerp.osv import fields, osv
from openerp import pooler
from openerp.tools.translate import _
+_logger = logging.getLogger(__name__)
+
class sale_configuration(osv.osv_memory):
_inherit = 'sale.config.settings'
@@ -81,8 +85,12 @@ Example: Product: this product is deprecated, do not purchase more than 5.
user = self.pool.get('res.users').browse(cr, uid, uid, context)
res['time_unit'] = user.company_id.project_time_mode_id.id
else:
- product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant')
- res['time_unit'] = product.uom_id.id
+ try:
+ product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant')
+ res['time_unit'] = product.uom_id.id
+ except ValueError:
+ # keep default value in that case
+ _logger.warning("Product with xml_id 'product.product_product_consultant' not found")
return res
def _get_default_time_unit(self, cr, uid, context=None):
@@ -98,16 +106,11 @@ Example: Product: this product is deprecated, do not purchase more than 5.
wizard = self.browse(cr, uid, ids)[0]
if wizard.time_unit:
- product = False
try:
product = ir_model_data.get_object(cr, uid, 'product', 'product_product_consultant')
- except:
- #product with xml_id product_product_consultant has not been found. Don't do anything except logging the exception
- import logging
- _logger = logging.getLogger(__name__)
- _logger.warning("Warning, product with xml_id 'product_product_consultant' hasn't been found")
- if product:
product.write({'uom_id': wizard.time_unit.id, 'uom_po_id': wizard.time_unit.id})
+ except ValueError:
+ _logger.warning("Product with xml_id 'product.product_product_consultant' not found, UoMs not updated!")
if wizard.module_project and wizard.time_unit:
user = self.pool.get('res.users').browse(cr, uid, uid, context)
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 66653c61849..cddde4a5f08 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -926,7 +926,6 @@ class sale_order_line(osv.osv):
elif uom: # whether uos is set or not
default_uom = product_obj.uom_id and product_obj.uom_id.id
q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
- result['product_uom'] = default_uom
if product_obj.uos_id:
result['product_uos'] = product_obj.uos_id.id
result['product_uos_qty'] = qty * product_obj.uos_coeff
diff --git a/addons/sale/sale_data.xml b/addons/sale/sale_data.xml
index 6d7cd043c39..a0e93cbf581 100644
--- a/addons/sale/sale_data.xml
+++ b/addons/sale/sale_data.xml
@@ -48,11 +48,13 @@
Quotation send
sale.order
+
Quotation send
Sales Order Confirmed
sale.order
+
Quotation confirmed
diff --git a/addons/sale/test/sale_order_demo.yml b/addons/sale/test/sale_order_demo.yml
index 7e5a223e8b0..75e17ebeb02 100644
--- a/addons/sale/test/sale_order_demo.yml
+++ b/addons/sale/test/sale_order_demo.yml
@@ -14,3 +14,23 @@
!assert {model: sale.order, id: sale.sale_order_test1, string: The onchange function of product was not correctly triggered}:
- order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
- order_line[0].price_unit == 1350.0
+ - order_line[0].product_uom_qty == 8
+ - order_line[0].product_uom.id == ref('product.product_uom_unit')
+
+-
+ I create another sale order
+-
+ !record {model: sale.order, id: sale_order_test2}:
+ partner_id: base.res_partner_2
+ order_line:
+ - product_id: product.product_product_7
+ product_uom_qty: 16
+ product_uom: product.product_uom_dozen
+-
+ I verify that the onchange was correctly triggered
+-
+ !assert {model: sale.order, id: sale.sale_order_test2, string: The onchange function of product was not correctly triggered}:
+ - order_line[0].name == u'[LCD17] 17\u201d LCD Monitor'
+ - order_line[0].price_unit == 1350.0 * 12
+ - order_line[0].product_uom.id == ref('product.product_uom_dozen')
+ - order_line[0].product_uom_qty == 16
\ No newline at end of file
diff --git a/addons/stock/res_config_view.xml b/addons/stock/res_config_view.xml
index 9cc8553f4ea..3de3104a970 100644
--- a/addons/stock/res_config_view.xml
+++ b/addons/stock/res_config_view.xml
@@ -91,7 +91,7 @@
-
+
diff --git a/addons/web_analytics/static/src/js/web_analytics.js b/addons/web_analytics/static/src/js/web_analytics.js
index 28df9610bbf..c8212f4d213 100644
--- a/addons/web_analytics/static/src/js/web_analytics.js
+++ b/addons/web_analytics/static/src/js/web_analytics.js
@@ -16,7 +16,7 @@ openerp.web_analytics = function(instance) {
var ga = document.createElement('script');
ga.type = 'text/javascript';
ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ ga.src = ('https:' === document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga,s);
})();
@@ -25,16 +25,15 @@ openerp.web_analytics = function(instance) {
/*
* This method initializes the tracker
*/
- init: function() {
- /* Comment this lines when going on production, only used for testing on localhost
- _gaq.push(['_setAccount', 'UA-35793871-1']);
- _gaq.push(['_setDomainName', 'none']);
- */
-
- /* Uncomment this lines when going on production */
+ init: function(webclient) {
+ var self = this;
+ self.initialized = $.Deferred();
_gaq.push(['_setAccount', 'UA-7333765-1']);
_gaq.push(['_setDomainName', '.openerp.com']); // Allow multi-domain
- /**/
+ self.initialize_custom(webclient).then(function() {
+ webclient.on('state_pushed', self, self.on_state_pushed);
+ self.include_tracker();
+ });
},
/*
* This method MUST be overriden by saas_demo and saas_trial in order to
@@ -48,41 +47,53 @@ openerp.web_analytics = function(instance) {
* This method gets the user access level, to be used as CV in GA
*/
_get_user_access_level: function() {
+ if (!instance.session.session_is_valid()) {
+ return "Unauthenticated User";
+ }
if (instance.session.uid === 1) {
return 'Admin User';
- // Make the difference between portal users and anonymous users
- } else if (instance.session.username.indexOf('@') !== -1) {
- if (instance.session.username.indexOf('anonymous') === -1) {
- return 'Portal User';
- } else {
- return 'Anonymous User';
- }
- } else if (instance.session.username.indexOf('anonymous') !== -1) {
- return 'Anonymous User';
- } else {
- return 'Normal User';
}
+ // Make the difference between portal users and anonymous users
+ if (instance.session.username.indexOf('@') !== -1) {
+ return 'Portal User';
+ }
+ if (instance.session.username === 'anonymous') {
+ return 'Anonymous User';
+ }
+ return 'Normal User';
},
+
/*
* This method contains the initialization of all user-related custom variables
* stored in GA. Also other modules can override it to add new custom variables
+ * Must be followed by a call to _push_*() in order to actually send the data
+ * to GA.
*/
initialize_custom: function() {
var self = this;
- return instance.session.rpc("/web/webclient/version_info", {})
+ instance.session.rpc("/web/webclient/version_info", {})
.done(function(res) {
_gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]);
- // Track User Access Level, Custom Variable 4 in GA with visitor level scope
- // Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User'
- _gaq.push(['_setCustomVar', 4, 'User Access Level', self.user_access_level, 1]);
-
- // Track User Type Conversion, Custom Variable 3 in GA with session level scope
- // Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
- _gaq.push(['_setCustomVar', 1, 'User Type Conversion', self._get_user_type(), 2]);
- _gaq.push(['_trackPageview']);
- return;
+ self._push_customvars();
+ self.initialized.resolve(self);
});
+ return self.initialized;
},
+
+ /*
+ * Method called in order to send _setCustomVar to GA
+ */
+ _push_customvars: function() {
+ var self = this;
+ // Track User Access Level, Custom Variable 4 in GA with visitor level scope
+ // Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User'
+ _gaq.push(['_setCustomVar', 4, 'User Access Level', self._get_user_access_level(), 1]);
+
+ // Track User Type Conversion, Custom Variable 3 in GA with session level scope
+ // Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
+ _gaq.push(['_setCustomVar', 1, 'User Type Conversion', self._get_user_type(), 2]);
+ },
+
/*
* Method called in order to send _trackPageview to GA
*/
@@ -142,6 +153,7 @@ openerp.web_analytics = function(instance) {
'action': state.view_type,
'label': url,
});
+ this._push_pageview(url);
}
},
/*
@@ -157,19 +169,19 @@ openerp.web_analytics = function(instance) {
this._super.apply(this, arguments);
var self = this;
this.on('record_created', self, function(r) {
- var url = instance.web_analytics.generateUrl({'model': r.model, 'view_type': 'form'});
+ var url = instance.web_analytics.generateUrl({'model': self.model, 'view_type': 'form'});
t._push_event({
- 'category': r.model,
- 'action': 'form',
+ 'category': self.model,
+ 'action': 'create',
'label': url,
'noninteraction': true,
});
});
this.on('record_saved', self, function(r) {
- var url = instance.web_analytics.generateUrl({'model': r.model, 'view_type': 'form'});
+ var url = instance.web_analytics.generateUrl({'model': self.model, 'view_type': 'form'});
t._push_event({
- 'category': r.model,
- 'action': 'form',
+ 'category': self.model,
+ 'action': 'save',
'label': url,
'noninteraction': true,
});
@@ -181,12 +193,12 @@ openerp.web_analytics = function(instance) {
instance.web.ActionManager.include({
ir_actions_client: function (action, options) {
var url = instance.web_analytics.generateUrl({'action': action.tag});
- var category = action.res_model || action.type;
t._push_event({
- 'category': action.res_model || action.type,
- 'action': action.name || action.tag,
+ 'category': action.type,
+ 'action': action.tag,
'label': url,
});
+ t._push_pageview(url);
return this._super.apply(this, arguments);
},
});
@@ -224,21 +236,12 @@ openerp.web_analytics = function(instance) {
options = {'action': params.action};
}
var url = instance.web_analytics.generateUrl(options);
- if (error.code) {
- t._push_event({
- 'category': error.message,
- 'action': error.data.message,
- 'label': url,
- 'noninteraction': true,
- });
- } else {
- t._push_event({
- 'category': error.type,
- 'action': error.data.debug,
- 'label': url,
- 'noninteraction': true,
- });
- }
+ t._push_event({
+ 'category': options.model || "ir.actions.client",
+ 'action': "error " + (error.code ? error.message + error.data.message : error.type + error.data.debug),
+ 'label': url,
+ 'noninteraction': true,
+ });
this._super.apply(this, arguments);
},
});
@@ -251,43 +254,37 @@ openerp.web_analytics = function(instance) {
instance.web_analytics.generateUrl = function(options) {
var url = '';
- _.each(options, function(value, key) {
- url += '/' + key + '=' + value;
+ var keys = _.keys(options);
+ keys = _.sortBy(keys, function(i) { return i;});
+ _.each(keys, function(key) {
+ url += '/' + key + '/' + options[key];
});
return url;
};
+ // kept for API compatibility
instance.web_analytics.setupTracker = function(wc) {
- var t = wc.tracker;
- return $.when(t._get_user_access_level()).then(function(r) {
- t.user_access_level = r;
- t.initialize_custom().then(function() {
- wc.on('state_pushed', t, t.on_state_pushed);
- t.include_tracker();
- });
- });
+ return wc.tracker.initialized;
};
- // Set correctly the tracker in the current instance
- if (instance.client instanceof instance.web.WebClient) { // not for embedded clients
- instance.webclient.tracker = new instance.web_analytics.Tracker();
- instance.web_analytics.setupTracker(instance.webclient);
- } else if (!instance.client) {
- // client does not already exists, we are in monodb mode
- instance.web.WebClient.include({
- start: function() {
- var d = this._super.apply(this, arguments);
- this.tracker = new instance.web_analytics.Tracker();
- return d;
- },
- show_application: function() {
- var self = this;
- $.when(this.subscribe_deferred).then(function() {
- instance.web_analytics.setupTracker(self);
- });
- this._super();
- },
- });
- }
+ instance.web.Client.include({
+ bind_events: function() {
+ this._super.apply(this, arguments);
+ this.tracker = new instance.web_analytics.Tracker(this);
+ },
+ });
+ instance.web.Session.include({
+ session_authenticate: function() {
+ return $.when(this._super.apply(this, arguments)).then(function() {
+ // the call to bind_events() may have been delayed in some embed
+ // cases, and when that happens we can skip the push, as the
+ // proper one will be done when bind_event is eventually called.
+ if (instance.client && instance.client.tracker) {
+ instance.client.tracker._push_customvars();
+ }
+ });
+ },
+ });
+
};