diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index d8828794475..7c18865b558 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -405,7 +405,7 @@ class AccountInvoice(models.Model): default_model='account.invoice', default_res_id=self.id, default_use_template=bool(template), - default_template_id=template.id, + default_template_id=template and template.id or False, default_composition_mode='comment', mark_invoice_as_sent=True, custom_layout="account.mail_template_data_notification_email_account_invoice" diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 1af8d118375..0d2569593f1 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -220,20 +220,28 @@ class AccountMove(models.Model): raise UserError(_("Cannot create unbalanced journal entry.")) return True + @api.multi + def _reverse_move(self, date=None, journal_id=None): + self.ensure_one() + reversed_move = self.copy(default={ + 'date': date, + 'journal_id': journal_id.id if journal_id else self.journal_id.id, + 'ref': _('reversal of: ') + self.name}) + for acm_line in reversed_move.line_ids: + acm_line.with_context(check_move_validity=False).write({ + 'debit': acm_line.credit, + 'credit': acm_line.debit, + 'amount_currency': -acm_line.amount_currency + }) + return reversed_move + @api.multi def reverse_moves(self, date=None, journal_id=None): date = date or fields.Date.today() reversed_moves = self.env['account.move'] for ac_move in self: - reversed_move = ac_move.copy(default={'date': date, - 'journal_id': journal_id.id if journal_id else ac_move.journal_id.id, - 'ref': _('reversal of: ') + ac_move.name}) - for acm_line in reversed_move.line_ids: - acm_line.with_context(check_move_validity=False).write({ - 'debit': acm_line.credit, - 'credit': acm_line.debit, - 'amount_currency': -acm_line.amount_currency - }) + reversed_move = ac_move._reverse_move(date=date, + journal_id=journal_id) reversed_moves |= reversed_move #unreconcile all lines reversed aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile) diff --git a/addons/anonymization/wizard/anonymize_wizard.py b/addons/anonymization/wizard/anonymize_wizard.py index 15db541a794..a0b355d4330 100644 --- a/addons/anonymization/wizard/anonymize_wizard.py +++ b/addons/anonymization/wizard/anonymize_wizard.py @@ -164,7 +164,7 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel): table_name = self.env[model_name]._table # get the current value - self.env.cr.execute("select id, %s from %s" % (field_name, table_name)) + self.env.cr.execute('select id, "%s" from "%s"' % (field_name, table_name)) for record in self.env.cr.dictfetchall(): data.append({"model_id": model_name, "field_id": field_name, "id": record['id'], "value": record[field_name]}) @@ -196,7 +196,7 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel): if anonymized_value is None: raise UserError('%s: %s' % (error_type, _("Anonymized value can not be empty."))) - sql = "update %(table)s set %(field)s = %%(anonymized_value)s where id = %%(id)s" % { + sql = 'update "%(table)s" set "%(field)s" = %%(anonymized_value)s where id = %%(id)s' % { 'table': table_name, 'field': field_name, } @@ -285,7 +285,7 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel): custom_updates.sort(key=itemgetter('sequence')) queries = [(record['query'], record['query_type']) for record in custom_updates if record['query_type']] elif table_name: - queries = [("update %(table)s set %(field)s = %%(value)s where id = %%(id)s" % { + queries = [('update "%(table)s" set "%(field)s" = %%(value)s where id = %%(id)s' % { 'table': table_name, 'field': line['field_id'], }, 'sql')] diff --git a/addons/base_import/static/csv/products.xls b/addons/base_import/static/csv/products.xls index 935cbdf56ca..9f6b501d161 100644 Binary files a/addons/base_import/static/csv/products.xls and b/addons/base_import/static/csv/products.xls differ diff --git a/addons/fleet/models/fleet.py b/addons/fleet/models/fleet.py index 677400c6fce..7ff6f95dc5d 100644 --- a/addons/fleet/models/fleet.py +++ b/addons/fleet/models/fleet.py @@ -344,7 +344,7 @@ class FleetVehicle(models.Model): if xml_id: res = self.env['ir.actions.act_window'].for_xml_id('fleet', xml_id) res.update( - context=dict(self.env.context, default_vehicle_id=self.id), + context=dict(self.env.context, default_vehicle_id=self.id, group_by=False), domain=[('vehicle_id', '=', self.id)] ) return res @@ -656,5 +656,3 @@ class FleetVehicleLogContract(models.Model): def run_scheduler(self): self.scheduler_manage_auto_costs() self.scheduler_manage_contract_expiration() - - diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 0fdcabf5323..bd4face91ce 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -703,7 +703,7 @@ class Message(models.Model): def _invalidate_documents(self): """ Invalidate the cache of the documents followed by ``self``. """ for record in self: - if record.res_id: + if record.model and record.res_id: self.env[record.model].invalidate_cache(ids=[record.res_id]) @api.model diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 657f9feea14..962a1b586a4 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -130,7 +130,7 @@ var MentionManager = Widget.extend({ get_listener_selection: function (delimiter) { var listener = _.findWhere(this.listeners, {delimiter: delimiter}); if (listener) { - var input_mentions = this.composer.$input.val().match(new RegExp(delimiter+'[^ ]+', 'g')); + var input_mentions = this.composer.$input.val().match(new RegExp(delimiter+'[^ ]+(?= | )', 'g')); return this._validate_selection(listener.selection, input_mentions); } return []; @@ -270,7 +270,7 @@ var MentionManager = Widget.extend({ // create the regex of all mention's names var names = _.pluck(listener.selection, 'name'); var escaped_names = _.map(names, function (str) { - return "("+_.str.escapeRegExp(listener.delimiter+str)+")"; + return "("+_.str.escapeRegExp(listener.delimiter+str)+")(?= | )"; }); var regex_str = escaped_names.join('|'); // extract matches diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less index 0f2dd407575..8f4a059b0c3 100644 --- a/addons/mail/static/src/less/thread.less +++ b/addons/mail/static/src/less/thread.less @@ -177,6 +177,7 @@ border-radius: 5px; color: gray; width: 300px; + display: none; opacity: 0; position: absolute; transition: opacity 0.5s; @@ -186,6 +187,7 @@ } .o_thread_tooltip:hover + .o_thread_tooltip_content { + display: inline; opacity: 1; } } diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index d81dd8d0a6c..c57c079be7a 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -479,8 +479,8 @@ class MrpProduction(models.Model): self.write({'state': 'cancel'}) return True - @api.multi def _cal_price(self, consumed_moves): + self.ensure_one() return True @api.multi diff --git a/addons/mrp/report/mrp_bom_structure_report.py b/addons/mrp/report/mrp_bom_structure_report.py index 09971b5106c..b65b74b05f6 100644 --- a/addons/mrp/report/mrp_bom_structure_report.py +++ b/addons/mrp/report/mrp_bom_structure_report.py @@ -16,12 +16,9 @@ class BomStructureReport(models.AbstractModel): res['pname'] = l.product_id.name_get()[0][1] res['pcode'] = l.product_id.default_code qty_per_bom = l.bom_id.product_qty - if uom and uom != l.product_uom_id: - qty = uom._compute_quantity(qty, l.product_uom_id) - if uom and l.product_uom_id != l.bom_id.product_uom_id: - qty_per_bom = l.bom_id.product_uom_id._compute_quantity(qty_per_bom, l.product_uom_id) - res['pqty'] = (l.product_qty *qty)/ qty_per_bom - elif uom: + if uom: + if uom != l.bom_id.product_uom_id: + qty = uom._compute_quantity(qty, l.bom_id.product_uom_id) res['pqty'] = (l.product_qty *qty)/ qty_per_bom else: #for the first case, the ponderation is right diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index e959d223d79..5076972a3e5 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -189,6 +189,7 @@ td { height: 100%; display: table; border: none; + overflow: hidden; } .pos .window .subwindow{ display: table-row; @@ -213,6 +214,19 @@ td { position: relative; } +.pos .clientlist-screen .window, +.pos .clientlist-screen .full-content .subwindow{ + display: block; +} +.pos .clientlist-screen .full-content .subwindow-container{ + display: block; + height: 100%; +} +.pos .clientlist-screen .full-content .subwindow.collapsed, +.pos .clientlist-screen .full-content .subwindow-container.collapsed{ + height: auto; +} + /* ---- Scrollers ----- */ .pos .scroller-container{ @@ -703,6 +717,7 @@ td { display: inline-block; float: right; text-align: center; + width: 216px; margin: 16px; margin-top: 8px; margin-left: 0px; diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index f9cf49864d2..5b8933c4cc7 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1335,6 +1335,9 @@ var ClientListScreenWidget = ScreenWidget.extend({ var new_height = contents.height(); if(!this.details_visible){ + // resize client list to take into account client details + parent.height('-=' + new_height); + if(clickpos < scroll + new_height + 20 ){ parent.scrollTop( clickpos - 20 ); }else{ @@ -1364,6 +1367,7 @@ var ClientListScreenWidget = ScreenWidget.extend({ }); } else if (visibility === 'hide') { contents.empty(); + parent.height('100%'); if( height > scroll ){ contents.css({height:height+'px'}); contents.animate({height:0},400,function(){ diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py index 6fbdf01fa6e..051d4bee319 100644 --- a/addons/portal/wizard/portal_wizard.py +++ b/addons/portal/wizard/portal_wizard.py @@ -137,7 +137,10 @@ class PortalWizardUser(models.TransientModel): user_portal = None # create a user if necessary, and make sure it is in the portal group if not user: - company_id = wizard_user.partner_id.company_id.id + if wizard_user.partner_id.company_id: + company_id = wizard_user.partner_id.company_id.id + else: + company_id = self.env['res.company']._company_default_get('res.users') user_portal = wizard_user.sudo().with_context(company_id=company_id)._create_user() else: user_portal = user diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 25fe10b5315..13d1e7a740a 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -509,7 +509,7 @@ class SaleOrder(models.Model): amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty)['taxes'][0]['amount'] res[group] += amount if tax.include_base_amount: - base_tax += amount + base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1)['taxes'][0]['amount'] res = sorted(res.items(), key=lambda l: l[0].sequence) res = map(lambda l: (l[0].name, l[1]), res) return res diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py index 413259ce998..4e5eee48591 100644 --- a/addons/sale_timesheet/models/sale_order.py +++ b/addons/sale_timesheet/models/sale_order.py @@ -96,20 +96,14 @@ class SaleOrder(models.Model): @api.multi def action_view_project_project(self): self.ensure_one() - action = self.env.ref('project.open_view_project_all') + action = self.env.ref('project.open_view_project_all').read()[0] form_view_id = self.env.ref('project.edit_project').id - result = { - 'name': action.name, - 'help': action.help, - 'type': action.type, - 'views': [(form_view_id, 'form')], - 'target': 'current', - 'context': action.context, - 'res_model': action.res_model, - 'res_id': self.project_project_id.id, - } - return result + action['views'] = [(form_view_id, 'form')] + action['res_id'] = self.project_project_id.id + action.pop('target', None) + + return action @api.multi def action_view_timesheet(self): diff --git a/addons/stock/models/stock_inventory.py b/addons/stock/models/stock_inventory.py index 93796e7c320..f6c36541990 100644 --- a/addons/stock/models/stock_inventory.py +++ b/addons/stock/models/stock_inventory.py @@ -338,11 +338,11 @@ class InventoryLine(models.Model): theoretical_qty = self.product_id.uom_id._compute_quantity(theoretical_qty, self.product_uom_id) self.theoretical_qty = theoretical_qty - @api.onchange('product_id', 'product_uom_id') - def onchange_product_or_uom(self): + @api.onchange('product_id') + def onchange_product(self): res = {} # If no UoM or incorrect UoM put default one from product - if self.product_id and (not self.product_uom_id or self.product_id.uom_id.category_id != self.product_uom_id.category_id): + if self.product_id: self.product_uom_id = self.product_id.uom_id res['domain'] = {'product_uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]} return res diff --git a/addons/web_kanban/static/src/js/kanban_view.js b/addons/web_kanban/static/src/js/kanban_view.js index 07aa116c634..62bd6d31d4e 100644 --- a/addons/web_kanban/static/src/js/kanban_view.js +++ b/addons/web_kanban/static/src/js/kanban_view.js @@ -710,7 +710,7 @@ var KanbanView = View.extend({ var context = {}; context['default_' + this.group_by_field] = column.id; var name = event.data.value; - this.dataset.name_create(name).done(function(data) { + this.dataset.name_create(name, context).done(function(data) { add_record(data[0]); }).fail(function(error, event) { event.preventDefault(); diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 1d7c24abcb1..7af22085041 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -378,7 +378,7 @@ class Website(models.Model): request.session['sale_order_id'] = sale_order.id if request.website.partner_id.id != partner.id: - partner.write({'last_website_so_id': sale_order_id}) + partner.write({'last_website_so_id': sale_order.id}) if sale_order: diff --git a/doc/cla/individual/anothingguy.md b/doc/cla/individual/anothingguy.md new file mode 100644 index 00000000000..496f7a2b0e8 --- /dev/null +++ b/doc/cla/individual/anothingguy.md @@ -0,0 +1,11 @@ +Vietnam, 2016-10-14 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Hoang Diep Huu mr.diephuuhoang@gmail.com https://github.com/anothingguy diff --git a/doc/cla/individual/aricaldeira.md b/doc/cla/individual/aricaldeira.md new file mode 100644 index 00000000000..e419dc02d86 --- /dev/null +++ b/doc/cla/individual/aricaldeira.md @@ -0,0 +1,11 @@ +Brazil, 2017-01-24 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Aristides José Simões Caldeira aristides.caldeira@tauga.com.br https://github.com/aricaldeira diff --git a/doc/cla/individual/sergiocorato.md b/doc/cla/individual/sergiocorato.md new file mode 100644 index 00000000000..5ac26c3f7ea --- /dev/null +++ b/doc/cla/individual/sergiocorato.md @@ -0,0 +1,9 @@ +Italy, 2017-01-24 + +I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this declaration. + +Signed, + +Sergio Corato sergiocorato@gmail.com https://github.com/sergiocorato diff --git a/odoo/addons/base/res/res.country.state.csv b/odoo/addons/base/res/res.country.state.csv index 8aa76cef90c..cff3856c3d4 100644 --- a/odoo/addons/base/res/res.country.state.csv +++ b/odoo/addons/base/res/res.country.state.csv @@ -414,43 +414,43 @@ state_it_vr,it,"Verona","VR" state_it_vv,it,"Vibo Valentia","VV" state_it_vi,it,"Vicenza","VI" state_it_vt,it,"Viterbo","VT" -state_es_c,es,"A Coruña","C" -state_es_vi,es,"Álava","VI" +state_es_c,es,"A Coruña (La Coruña)","C" +state_es_vi,es,"Araba/Álava","VI" state_es_ab,es,"Albacete","AB" -state_es_a,es,"Alicante","A" +state_es_a,es,"Alacant (Alicante)","A" state_es_al,es,"Almería","AL" state_es_o,es,"Asturias","O" state_es_av,es,"Ávila","AV" state_es_ba,es,"Badajoz","BA" -state_es_pm,es,"Balears","PM" +state_es_pm,es,"Illes Balears (Islas Baleares)","PM" state_es_b,es,"Barcelona","B" state_es_bu,es,"Burgos","BU" state_es_cc,es,"Cáceres","CC" state_es_ca,es,"Cádiz","CA" state_es_s,es,"Cantabria","S" -state_es_cs,es,"Castellón","CS" +state_es_cs,es,"Castelló (Castellón)","CS" state_es_ce,es,"Ceuta","CE" state_es_cr,es,"Ciudad Real","CR" state_es_co,es,"Córdoba","CO" state_es_cu,es,"Cuenca","CU" -state_es_gi,es,"Girona","GI" +state_es_gi,es,"Girona (Gerona)","GI" state_es_gr,es,"Granada","GR" state_es_gu,es,"Guadalajara","GU" -state_es_ss,es,"Guipúzcoa","SS" +state_es_ss,es,"Guipuzkoa (Guipúzcoa)","SS" state_es_h,es,"Huelva","H" state_es_hu,es,"Huesca","HU" state_es_j,es,"Jaén","J" state_es_lo,es,"La Rioja","LO" state_es_gc,es,"Las Palmas","GC" state_es_le,es,"León","LE" -state_es_l,es,"Lleida","L" +state_es_l,es,"Lleida (Lérida)","L" state_es_lu,es,"Lugo","LU" state_es_m,es,"Madrid","M" state_es_ma,es,"Málaga","MA" -state_es_ml,es,"Melilla","ML" +state_es_ml,es,"Melilla","ME" state_es_mu,es,"Murcia","MU" -state_es_na,es,"Navarra","NA" -state_es_or,es,"Ourense","OR" +state_es_na,es,"Nafarroa (Navarra)","NA" +state_es_or,es,"Ourense (Orense)","OR" state_es_p,es,"Palencia","P" state_es_po,es,"Pontevedra","PO" state_es_sa,es,"Salamanca","SA" @@ -461,9 +461,9 @@ state_es_so,es,"Soria","SO" state_es_t,es,"Tarragona","T" state_es_te,es,"Teruel","TE" state_es_to,es,"Toledo","TO" -state_es_v,es,"Valencia","V" +state_es_v,es,"València (Valencia)","V" state_es_va,es,"Valladolid","VA" -state_es_bi,es,"Vizcaya","BI" +state_es_bi,es,"Bizkaia (Vizcaya)","BI" state_es_za,es,"Zamora","ZA" state_es_z,es,"Zaragoza","Z" state_my_jhr,my,"Johor","JHR"