[MERGE] forward port of branch saas-3 up to fdc6271
authorChristophe Simonis <chs@odoo.com>
Fri, 12 Sep 2014 16:53:48 +0000 (18:53 +0200)
committerChristophe Simonis <chs@odoo.com>
Fri, 12 Sep 2014 16:53:48 +0000 (18:53 +0200)
18 files changed:
addons/email_template/email_template.py
addons/hr_expense/hr_expense.py
addons/l10n_ro/res_partner.py
addons/mail/mail_thread.py
addons/mrp_repair/mrp_repair.py
addons/point_of_sale/static/src/js/models.js
addons/product/product_view.xml
addons/stock/stock_view.xml
addons/web/controllers/main.py
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/chrome.js
addons/web/static/src/js/data.js
addons/website_event_sale/controllers/main.py
openerp/addons/base/ir/ir_attachment.py
openerp/addons/base/ir/ir_mail_server.py
openerp/osv/orm.py
openerp/tools/mail.py

index f07edb8..f43a8e9 100644 (file)
@@ -145,7 +145,7 @@ class email_template(osv.osv):
         # - img src -> check URL
         # - a href -> check URL
         for node in root.iter():
-            if node.tag == 'a':
+            if node.tag == 'a' and node.get('href'):
                 node.set('href', _process_link(node.get('href')))
             elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
                 node.set('src', _process_link(node.get('src')))
index 0129e5d..2a43380 100644 (file)
@@ -285,7 +285,6 @@ class hr_expense_expense(osv.osv):
             if not mres:
                 continue
             res.append(mres)
-            tax_code_found= False
             
             #Calculate tax according to default tax on product
             taxes = []
@@ -303,32 +302,28 @@ class hr_expense_expense(osv.osv):
                         a = product.categ_id.property_account_expense_categ.id
                     a = fpos_obj.map_account(cr, uid, fpos, a)
                     taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
-                tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
             if not taxes:
                 continue
+            tax_l = []
             #Calculating tax on the line and creating move?
             for tax in tax_obj.compute_all(cr, uid, taxes,
                     line.unit_amount ,
                     line.unit_quantity, line.product_id,
                     exp.user_id.partner_id)['taxes']:
                 tax_code_id = tax['base_code_id']
-                tax_amount = line.total_amount * tax['base_sign']
-                if tax_code_found:
-                    if not tax_code_id:
-                        continue
-                    res.append(self.move_line_get_item(cr, uid, line, context))
-                    res[-1]['price'] = 0.0
-                    res[-1]['account_analytic_id'] = False
-                elif not tax_code_id:
+                if not tax_code_id:
                     continue
-                tax_code_found = True
                 res[-1]['tax_code_id'] = tax_code_id
-                res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
                 ## 
                 is_price_include = tax_obj.read(cr,uid,tax['id'],['price_include'],context)['price_include']
                 if is_price_include:
                     ## We need to deduce the price for the tax
                     res[-1]['price'] = res[-1]['price']  - (tax['amount'] * tax['base_sign'] or 0.0)
+                    # tax amount countains base amount without the tax
+                    tax_amount = (line.total_amount - tax['amount']) * tax['base_sign']
+                else:
+                    tax_amount = line.total_amount * tax['base_sign']
+                res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
                 assoc_tax = {
                              'type':'tax',
                              'name':tax['name'],
@@ -339,7 +334,8 @@ class hr_expense_expense(osv.osv):
                              'tax_code_id': tax['tax_code_id'],
                              'tax_amount': tax['amount'] * tax['base_sign'],
                              }
-                res.append(assoc_tax)
+                tax_l.append(assoc_tax)
+            res += tax_l
         return res
 
     def move_line_get_item(self, cr, uid, line, context=None):
index 79933dc..26fd08b 100644 (file)
@@ -28,26 +28,15 @@ class res_partner(osv.osv):
         'nrc' : fields.char('NRC', size=16, help='Registration number at the Registry of Commerce'),
     }
 
-    # The SQL constraints are no-ops but present only to display the right error message to the
-    # user when the partial unique indexes defined below raise errors/  
-    # The real constraints need to be implemented with PARTIAL UNIQUE INDEXES (see auto_init),
-    # due to the way accounting data is delegated by contacts to their companies in OpenERP 7.0.
-    _sql_constraints = [
-       ('vat_uniq', 'unique (id)', 'The vat of the partner must be unique !'),
-       ('nrc_uniq', 'unique (id)', 'The code of the partner must be unique !')
-    ]
-
     def _auto_init(self, cr, context=None):
         result = super(res_partner, self)._auto_init(cr, context=context)
-        # Real implementation of the vat/nrc constraints: only "commercial entities" need to have
-        # unique numbers, and the condition for being a commercial entity is "is_company or parent_id IS NULL".
-        # Contacts inside a company automatically have a copy of the company's commercial fields
-        # (see _commercial_fields()), so they are automatically consistent.
+        # Remove constrains for vat, nrc on "commercial entities" because is not mandatory by legislation
+        # Even that VAT numbers are unique, the NRC field is not unique, and there are certain entities that
+        # doesn't have a NRC number plus the formatting was changed few times, so we cannot have a base rule for
+        # checking if available and emmited by the Ministry of Finance, only online on their website.
         cr.execute("""
             DROP INDEX IF EXISTS res_partner_vat_uniq_for_companies;
             DROP INDEX IF EXISTS res_partner_nrc_uniq_for_companies;
-            CREATE UNIQUE INDEX res_partner_vat_uniq_for_companies ON res_partner (vat) WHERE is_company OR parent_id IS NULL;
-            CREATE UNIQUE INDEX res_partner_nrc_uniq_for_companies ON res_partner (nrc) WHERE is_company OR parent_id IS NULL;
         """)
         return result
 
index 23c3255..3445772 100644 (file)
@@ -110,7 +110,7 @@ class mail_thread(osv.AbstractModel):
         model = context.get('empty_list_help_model')
         res_id = context.get('empty_list_help_id')
         ir_config_parameter = self.pool.get("ir.config_parameter")
-        catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context)
+        catchall_domain = ir_config_parameter.get_param(cr, SUPERUSER_ID, "mail.catchall.domain", context=context)
         document_name = context.get('empty_list_help_document_name', _('document'))
         alias = None
 
index 54c7a3d..e84efab 100644 (file)
@@ -356,7 +356,7 @@ class mrp_repair(osv.osv):
                         'origin': repair.name,
                         'type': 'out_invoice',
                         'account_id': account_id,
-                        'partner_id': repair.partner_id.id,
+                        'partner_id': repair.partner_invoice_id.id or repair.partner_id.id,
                         'currency_id': repair.pricelist_id.currency_id.id,
                         'comment': repair.quotation_notes,
                         'fiscal_position': repair.partner_id.property_account_position.id
index 0c3c91f..af8e372 100644 (file)
@@ -252,7 +252,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                     // Load the company Logo
 
                     self.company_logo = new Image();
-                    self.company_logo.crossOrigin = 'anonymous';
                     var  logo_loaded = new $.Deferred();
                     self.company_logo.onload = function(){
                         var img = self.company_logo;
@@ -274,13 +273,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                             ctx.drawImage(self.company_logo,0,0, width, height);
                         
                         self.company_logo_base64 = c.toDataURL();
-                        window.logo64 = self.company_logo_base64;
                         logo_loaded.resolve();
                     };
                     self.company_logo.onerror = function(){
                         logo_loaded.reject();
                     };
-                    self.company_logo.src = window.location.origin + '/web/binary/company_logo';
+                    self.company_logo.src = '/web/binary/company_logo'+'?_'+Math.random();
 
                     return logo_loaded;
                 });
index b2d7c5d..162391c 100644 (file)
             <field name="view_mode">kanban,tree,form</field>
             <field name="view_type">form</field>
             <field name="view_id" ref="product_template_kanban_view"/>
+            <field name="context">{"search_default_filter_to_sell":1}</field>
         </record>
 
         <menuitem action="product_template_action"
index f4ef1d6..5dc3b5c 100644 (file)
                     <field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, False)"/>
                     <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/>
                     <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
+                    <field name="product_uos_qty" groups="product.group_uos"/>
                     <field name="product_uos" groups="product.group_uos"/>
                     <button name="%(stock.move_scrap)d"
                         string="Scrap Products" type="action"
                     <field name="product_id"/>
                     <field name="product_uom_qty" on_change="onchange_quantity(product_id, product_uom_qty, product_uom, product_uos)"/>
                     <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
+                    <field name="product_uos_qty" groups="product.group_uos"/>
                     <field name="product_uos" groups="product.group_uos"/>
                     <field name="location_id" groups="stock.group_locations" invisible="1"/>
                     <field name="picking_id" invisible="1" />
index eed5a7d..0d5a4c0 100644 (file)
@@ -119,7 +119,7 @@ def ensure_db(redirect='/web/database/selector'):
         abort_and_redirect(url_redirect)
 
     # if db not provided, use the session one
-    if not db:
+    if not db and http.db_filter([request.session.db]):
         db = request.session.db
 
     # if no database provided and no database in session, use monodb
@@ -545,6 +545,8 @@ class Home(http.Controller):
 
     @http.route('/login', type='http', auth="none")
     def login(self, db, login, key, redirect="/web", **kw):
+        if not http.db_filter([db]):
+            return werkzeug.utils.redirect('/', 303)
         return login_and_redirect(db, login, key, redirect_url=redirect)
 
     @http.route('/web/js/<xmlid>', type='http', auth="public")
@@ -1283,7 +1285,7 @@ class Binary(http.Controller):
         '/logo',
         '/logo.png',
     ], type='http', auth="none")
-    def company_logo(self, dbname=None):
+    def company_logo(self, dbname=None, **kw):
         # TODO add etag, refactor to use /image code for etag
         uid = None
         if request.session.db:
index b120874..5002f46 100644 (file)
 .openerp .oe_application .oe_form_sheet .oe_notebook_page {
   padding: 0 16px;
 }
-.openerp .oe_form > :not(.oe_form_nosheet) header {
+.openerp .oe_form > :not(.oe_form_nosheet) header, .openerp .oe_form > .oe_form_nosheet header {
   padding-left: 2px;
 }
-.openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) {
+.openerp .oe_form > :not(.oe_form_nosheet) header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu), .openerp .oe_form > .oe_form_nosheet header ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu) {
   display: inline-block;
   float: right;
 }
-.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button {
+.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button, .openerp .oe_form > .oe_form_nosheet header .oe_button {
   margin: 3px 2px 1px;
 }
-.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button:first-child {
+.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button:first-child, .openerp .oe_form > .oe_form_nosheet header .oe_button:first-child {
   margin-left: 6px;
 }
 .openerp .oe_form header {
 .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float.oe_readonly, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer.oe_readonly {
   padding: 6px 0px 0px;
   text-align: right;
-  max-width: 100px;
+}
+.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer span {
+  padding: 0px 6px;
 }
 .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
   width: 100% !important;
index 6250d11..d736b57 100644 (file)
@@ -1577,7 +1577,7 @@ $sheet-padding: 16px
                 padding: 0 16px
     // }}}
     // FormView.header {{{
-    .oe_form > :not(.oe_form_nosheet) header
+    .oe_form > :not(.oe_form_nosheet) header, .oe_form > .oe_form_nosheet header
         padding-left: 2px
         ul:not(.oe_tooltip_technical):not(.oe_dropdown_menu)
             display: inline-block
@@ -2148,7 +2148,8 @@ $sheet-padding: 16px
                 &.oe_readonly
                     padding: 6px 0px 0px
                     text-align: right
-                    max-width: 100px
+                span
+                    padding: 0px 6px
                 input
                     width: 100% !important
                     text-align: right
index 197bd4a..f965879 100644 (file)
@@ -1008,7 +1008,7 @@ instance.web.UserMenu =  instance.web.Widget.extend({
         this.update_promise = this.update_promise.then(fct, fct);
     },
     on_menu_help: function() {
-        window.open('http://help.openerp.com', '_blank');
+        window.open('http://help.odoo.com', '_blank');
     },
     on_menu_logout: function() {
         this.trigger('user_logout');
@@ -1037,10 +1037,10 @@ instance.web.UserMenu =  instance.web.Widget.extend({
                     state: JSON.stringify(state),
                     scope: 'userinfo',
                 };
-                instance.web.redirect('https://accounts.openerp.com/oauth2/auth?'+$.param(params));
+                instance.web.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params));
             }).fail(function(result, ev){
                 ev.preventDefault();
-                instance.web.redirect('https://accounts.openerp.com/web');
+                instance.web.redirect('https://accounts.odoo.com/web');
             });
         }
     },
index 6819634..8c7ee8a 100644 (file)
@@ -946,6 +946,8 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
                             sign = -1;
                             field = field.slice(1);
                         }
+                        if(!a[field] && a[field] !== 0){ return sign}
+                        if(!b[field] && b[field] !== 0){ return (sign == -1) ? 1 : -1}
                         //m2o should be searched based on value[1] not based whole value(i.e. [id, value])
                         if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){
                             return sign * compare(a[field][1], b[field][1]);
index 973b2f2..ff697b1 100644 (file)
@@ -43,6 +43,8 @@ class website_event(website_event):
             ticket = ticket_obj.browse(cr, SUPERUSER_ID, ticket_id, context=context)
             request.website.sale_get_order(force_create=1)._cart_update(
                 product_id=ticket.product_id.id, add_qty=quantity, context=dict(context, event_ticket_id=ticket.id))
+            if 'tax_id' in _values:
+                _values['tax_id'] = [(6, 0, _values['tax_id'])]
 
         if not sale:
             return request.redirect("/event/%s" % event_id)
index 42fb05a..b3ae8b3 100644 (file)
@@ -236,6 +236,8 @@ class ir_attachment(osv.osv):
         for model, mids in res_ids.items():
             # ignore attachments that are not attached to a resource anymore when checking access rights
             # (resource was deleted but attachment was not)
+            if not self.pool.get(model):
+                continue
             mids = self.pool[model].exists(cr, uid, mids)
             ima.check(cr, uid, model, mode)
             self.pool[model].check_access_rule(cr, uid, mids, mode, context=context)
index 56a5164..f281a62 100644 (file)
@@ -24,7 +24,7 @@ from email.MIMEBase import MIMEBase
 from email.MIMEMultipart import MIMEMultipart
 from email.Charset import Charset
 from email.Header import Header
-from email.Utils import formatdate, make_msgid, COMMASPACE
+from email.Utils import formatdate, make_msgid, COMMASPACE, parseaddr
 from email import Encoders
 import logging
 import re
@@ -120,6 +120,7 @@ def encode_header_param(param_text):
     return param_text_ascii if param_text_ascii\
          else Charset('utf8').header_encode(param_text_utf8)
 
+# TODO master, remove me, no longer used internaly
 name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>')
 address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)')
 
@@ -143,15 +144,16 @@ def encode_rfc2822_address_header(header_text):
     header_text_ascii = try_coerce_ascii(header_text_utf8)
     if header_text_ascii:
         return header_text_ascii
+
+    name, email = parseaddr(header_text_utf8)
+    if not name:
+      return email
+
     # non-ASCII characters are present, attempt to
     # replace all "Name" patterns with the RFC2047-
     # encoded version
-    def replace(match_obj):
-        name, email = match_obj.group(1), match_obj.group(2)
-        name_encoded = str(Header(name, 'utf-8'))
-        return "%s <%s>" % (name_encoded, email)
-    header_text_utf8 = name_with_email_pattern.sub(replace,
-                                                   header_text_utf8)
+    name_encoded = str(Header(name, 'utf-8'))
+    header_text_utf8 = "%s <%s>" % (name_encoded, email)
     # try again after encoding
     header_text_ascii = try_coerce_ascii(header_text_utf8)
     if header_text_ascii:
index 0e65878..2ea5170 100644 (file)
@@ -2181,13 +2181,19 @@ class BaseModel(object):
         # same ordering, and can be merged in one pass.
         result = []
         known_values = {}
+
+        if len(groupby_list) < 2 and context.get('group_by_no_leaf'):
+            count_attr = '_'
+        else:
+            count_attr = groupby
+        count_attr += '_count'
+
         def append_left(left_side):
             grouped_value = left_side[groupby] and left_side[groupby][0]
             if not grouped_value in known_values:
                 result.append(left_side)
                 known_values[grouped_value] = left_side
             else:
-                count_attr = groupby + '_count'
                 known_values[grouped_value].update({count_attr: left_side[count_attr]})
         def append_right(right_side):
             grouped_value = right_side[0]
@@ -3730,6 +3736,7 @@ class BaseModel(object):
         self.check_access_rights(cr, uid, 'unlink')
 
         ir_property = self.pool.get('ir.property')
+        ir_attachment_obj = self.pool.get('ir.attachment')
 
         # Check if the records are used as default properties.
         domain = [('res_id', '=', False),
@@ -3768,6 +3775,13 @@ class BaseModel(object):
             if ir_value_ids:
                 ir_values_obj.unlink(cr, uid, ir_value_ids, context=context)
 
+            # For the same reason, removing the record relevant to ir_attachment
+            # The search is performed with sql as the search method of ir_attachment is overridden to hide attachments of deleted records
+            cr.execute('select id from ir_attachment where res_model = %s and res_id in %s', (self._name, sub_ids))
+            ir_attachment_ids = [ir_attachment[0] for ir_attachment in cr.fetchall()]
+            if ir_attachment_ids:
+                ir_attachment_obj.unlink(cr, uid, ir_attachment_ids, context=context)
+
         for order, obj_name, store_ids, fields in result_store:
             if obj_name == self._name:
                 effective_store_ids = list(set(store_ids) - set(ids))
index b16a643..1482c42 100644 (file)
@@ -597,7 +597,7 @@ command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
 # Updated in 7.0 to match the model name as well
 # Typical form of references is <timestamp-openerp-record_id-model_name@domain>
 # group(1) = the record ID ; group(2) = the model (if any) ; group(3) = the domain
-reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?.*@(.*)>", re.UNICODE)
+reference_re = re.compile("<.*-open(?:object|erp)-(\\d+)(?:-([\w.]+))?[^>]*@([^>]*)>", re.UNICODE)
 
 # Bounce regex
 # Typical form of bounce is bounce-128-crm.lead-34@domain