diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index bbe399a4647..b1544067bfe 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -422,20 +422,28 @@ class AccountBankStatementLine(models.Model): def button_cancel_reconciliation(self): moves_to_unbind = self.env['account.move'] moves_to_cancel = self.env['account.move'] + payment_to_unreconcile = self.env['account.payment'] for st_line in self: moves_to_unbind |= st_line.journal_entry_ids for move in st_line.journal_entry_ids: if any(line.payment_id for line in move.line_ids): + for line in move.line_ids: + payment_to_unreconcile |= line.payment_id continue moves_to_cancel |= st_line.journal_entry_ids if moves_to_unbind: moves_to_unbind.write({'statement_line_id': False}) + moves_to_unbind.line_ids.filtered(lambda x:x.statement_id == st_line.statement_id).write({'statement_id': False}) + if moves_to_cancel: for move in moves_to_cancel: move.line_ids.remove_move_reconcile() moves_to_cancel.button_cancel() moves_to_cancel.unlink() + if payment_to_unreconcile: + payment_to_unreconcile.write({'state': 'posted'}) + #################################################### # Reconciliation interface methods #################################################### @@ -567,7 +575,18 @@ class AccountBankStatementLine(models.Model): else: domain = [(f, '>', 0), (f, '<', amount)] elif comparator == '=': - domain = [(f, '=', float_round(amount, precision_digits=p))] + if f == 'amount_residual': + domain = [ + '|', (f, '=', float_round(amount, precision_digits=p)), + '&', ('account_id.internal_type', '=', 'liquidity'), + '|', ('debit', '=', amount), ('credit', '=', amount), + ] + else: + domain = [ + '|', (f, '=', float_round(amount, precision_digits=p)), + '&', ('account_id.internal_type', '=', 'liquidity'), + ('amount_currency', '=', amount), + ] else: raise UserError(_("Programmation error : domain_maker_move_line_amount requires comparator '=' or '<'")) domain += [('currency_id', '=', c)] diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 6ffea173cb8..e5f7a394980 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -717,6 +717,9 @@ class AccountInvoice(models.Model): line2[tmp]['credit'] = (am < 0) and -am or 0.0 line2[tmp]['amount_currency'] += l['amount_currency'] line2[tmp]['analytic_line_ids'] += l['analytic_line_ids'] + qty = l.get('quantity') + if qty: + line2[tmp]['quantity'] = line2[tmp].get('quantity', 0.0) + qty else: line2[tmp] = l line = [] diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 3f7347fdabe..84b449d6b8d 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -142,11 +142,22 @@ class account_journal(models.Model): if self.type in ['bank', 'cash']: last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1) last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0 - ac_bnk_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids),('state', '=', 'open')]) - for ac_bnk in ac_bnk_stmt: - for line in ac_bnk.line_ids: - if not line.journal_entry_ids: - number_to_reconcile += 1 + #Get the number of items to reconcile for that bank journal + self.env.cr.execute("""SELECT COUNT(DISTINCT(statement_line_id)) + FROM account_move where statement_line_id + IN (SELECT line.id + FROM account_bank_statement_line AS line + LEFT JOIN account_bank_statement AS st + ON line.statement_id = st.id + WHERE st.journal_id IN %s and st.state = 'open')""", (tuple(self.ids),)) + already_reconciled = self.env.cr.fetchone()[0] + self.env.cr.execute("""SELECT COUNT(line.id) + FROM account_bank_statement_line AS line + LEFT JOIN account_bank_statement AS st + ON line.statement_id = st.id + WHERE st.journal_id IN %s and st.state = 'open'""", (tuple(self.ids),)) + all_lines = self.env.cr.fetchone()[0] + number_to_reconcile = all_lines - already_reconciled # optimization to read sum of balance from account_move_line account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id])) if account_ids: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index f27dc6a23d7..35c2405ad65 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -583,7 +583,14 @@ class AccountMoveLine(models.Model): ] try: amount = float(str) - amount_domain = ['|', ('amount_residual', '=', amount), '|', ('amount_residual_currency', '=', amount), '|', ('amount_residual', '=', -amount), ('amount_residual_currency', '=', -amount)] + amount_domain = [ + '|', ('amount_residual', '=', amount), + '|', ('amount_residual_currency', '=', amount), + '|', ('amount_residual', '=', -amount), + '|', ('amount_residual_currency', '=', -amount), + '&', ('account_id.internal_type', '=', 'liquidity'), + '|', '|', ('debit', '=', amount), ('credit', '=', amount), ('amount_currency', '=', amount), + ] str_domain = expression.OR([str_domain, amount_domain]) except: pass @@ -1067,13 +1074,13 @@ class AccountMoveLine(models.Model): if vals['debit'] != 0.0: vals['debit'] = res['total_excluded'] if vals['credit'] != 0.0: vals['credit'] = -res['total_excluded'] if vals.get('amount_currency'): - vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (amount / res['total_excluded'])) + vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (res['total_excluded']/amount)) # Create tax lines for tax_vals in res['taxes']: if tax_vals['amount']: account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id']) if not account_id: account_id = vals['account_id'] - tax_lines_vals.append({ + temp = { 'account_id': account_id, 'name': vals['name'] + ' ' + tax_vals['name'], 'tax_line_id': tax_vals['id'], @@ -1082,7 +1089,15 @@ class AccountMoveLine(models.Model): 'statement_id': vals.get('statement_id'), 'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0, 'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0, - }) + } + bank = self.env["account.bank.statement"].browse(vals.get('statement_id')) + if bank.currency_id != bank.company_id.currency_id: + ctx = {} + if 'date' in vals: + ctx['date'] = vals['date'] + temp['currency_id'] = bank.currency_id.id + temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).compute(tax_vals['amount'], bank.currency_id, round=True) + tax_lines_vals.append(temp) new_line = super(AccountMoveLine, self).create(vals) for tax_line_vals in tax_lines_vals: @@ -1124,10 +1139,11 @@ class AccountMoveLine(models.Model): msg = _('New expected payment date: ') + vals['expected_pay_date'] + '.\n' + vals.get('internal_note', '') self.invoice_id.message_post(body=msg) #TODO: check it is an internal note (not a regular email)! #when making a reconciliation on an existing liquidity journal item, mark the payment as reconciled - if 'statement_id' in vals and self.payment_id: - # In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement - if all(line.statement_id for line in self.payment_id.move_line_ids.filtered(lambda r: r.id != self.id and r.account_id.internal_type=='liquidity')): - self.payment_id.state = 'reconciled' + for record in self: + if 'statement_id' in vals and record.payment_id: + # In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement + if all(line.statement_id for line in record.payment_id.move_line_ids.filtered(lambda r: r.id != record.id and r.account_id.internal_type=='liquidity')): + record.payment_id.state = 'reconciled' result = super(AccountMoveLine, self).write(vals) if self._context.get('check_move_validity', True): diff --git a/addons/account/report/account_partner_ledger.py b/addons/account/report/account_partner_ledger.py index 6eb84f438dc..b5a47c8c194 100644 --- a/addons/account/report/account_partner_ledger.py +++ b/addons/account/report/account_partner_ledger.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- +from datetime import datetime import time from openerp import api, models +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT class ReportPartnerLedger(models.AbstractModel): @@ -26,7 +28,12 @@ class ReportPartnerLedger(models.AbstractModel): self.env.cr.execute(query, tuple(params)) res = self.env.cr.dictfetchall() sum = 0.0 + lang_code = self.env.context.get('lang') or 'en_US' + lang = self.env['res.lang'] + lang_id = lang._lang_get(lang_code) + date_format = lang.browse(lang_id).date_format for r in res: + r['date'] = datetime.strptime(r['date'], DEFAULT_SERVER_DATE_FORMAT).strftime(date_format) r['displayed_name'] = '-'.join( r[field_name] for field_name in ('move_name', 'ref', 'name') if r[field_name] not in (None, '', '/') diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 9ca93c18f3f..546faee3655 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -36,7 +36,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { if (match) { var barcode = match[1]; - core.bus.trigger('barcode_scanned', barcode); + // Send the target in case there are several barcode widgets on the same page (e.g. + // registering the lot numbers in a stock picking) + core.bus.trigger('barcode_scanned', barcode, this.buffered_key_events[0].target); // Dispatch a barcode_scanned DOM event to elements that have barcode_events="true" set. if (this.buffered_key_events[0].target.getAttribute("barcode_events") === "true") diff --git a/addons/barcodes/static/src/js/barcode_handler_mixin.js b/addons/barcodes/static/src/js/barcode_handler_mixin.js index 5b01516788f..0a9480a979f 100644 --- a/addons/barcodes/static/src/js/barcode_handler_mixin.js +++ b/addons/barcodes/static/src/js/barcode_handler_mixin.js @@ -13,8 +13,13 @@ return { init: function() { var self = this; this._super.apply(this, arguments); - this.__on_barcode_scanned = function () { - self.on_barcode_scanned.apply(self, arguments); + this.__on_barcode_scanned = function (barcode, target) { + // Handle the case where there are several barcode widgets on the same page. Since the + // event is global on the page, all barcode widgets will be triggered. However, we only + // want to keep the event on the target widget. + if ($.contains(target, self.el)) { + self.on_barcode_scanned.call(self, barcode); + } }; this.start_listening(); // Handlers inside a View managed by a ViewManager only listen to barcode events while their view is displayed diff --git a/addons/crm/crm_stage.py b/addons/crm/crm_stage.py index c686b62f3b2..ac453ddc2ff 100644 --- a/addons/crm/crm_stage.py +++ b/addons/crm/crm_stage.py @@ -20,7 +20,7 @@ class crm_stage(osv.Model): _name = "crm.stage" _description = "Stage of case" _rec_name = 'name' - _order = "sequence" + _order = "sequence, name, id" _columns = { 'name': fields.char('Stage Name', required=True, translate=True), diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 293ef38433a..6c92e116689 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -478,7 +478,7 @@ class MassMailing(osv.Model): 'create_date': fields.datetime('Creation Date'), 'sent_date': fields.datetime('Sent Date', oldname='date', copy=False), 'schedule_date': fields.datetime('Schedule in the Future'), - 'body_html': fields.html('Body', translate=True), + 'body_html': fields.html('Body', translate=True, sanitize=False), 'attachment_ids': fields.many2many( 'ir.attachment', 'mass_mailing_ir_attachments_rel', 'mass_mailing_id', 'attachment_id', 'Attachments' diff --git a/addons/point_of_sale/views/pos_order_view.xml b/addons/point_of_sale/views/pos_order_view.xml index 79af97379bc..560b31b3100 100644 --- a/addons/point_of_sale/views/pos_order_view.xml +++ b/addons/point_of_sale/views/pos_order_view.xml @@ -41,7 +41,8 @@ - + + diff --git a/addons/product/product.py b/addons/product/product.py index e0120a3f8a6..c5cf5a7a6a7 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -716,8 +716,9 @@ class product_template(osv.osv): def copy(self, cr, uid, id, default=None, context=None): if default is None: default = {} - template = self.browse(cr, uid, id, context=context) - default['name'] = _("%s (copy)") % (template['name']) + if 'name' not in default: + template = self.browse(cr, uid, id, context=context) + default['name'] = _("%s (copy)") % (template['name']) return super(product_template, self).copy(cr, uid, id, default=default, context=context) _defaults = { diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 4a309a9bb1a..ba613864857 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -98,7 +98,7 @@ class SaleOrder(models.Model): for order in self: order.order_line._compute_tax_id() - name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New')) + name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, states={'draft': [('readonly', False)]}, index=True, default=lambda self: _('New')) origin = fields.Char(string='Source Document', help="Reference of the document that generated this sales order request.") client_order_ref = fields.Char(string='Customer Reference', copy=False) diff --git a/addons/stock/models/stock_location.py b/addons/stock/models/stock_location.py index 396b0a918bc..5763903b071 100644 --- a/addons/stock/models/stock_location.py +++ b/addons/stock/models/stock_location.py @@ -214,6 +214,7 @@ class PushedFlow(models.Model): 'propagate': self.propagate, 'push_rule_id': self.id, 'warehouse_id': self.warehouse_id.id, + 'procurement_id': False, }) move.write({'move_dest_id': new_move.id}) new_move.action_confirm() diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index f03684b0a82..74a2232da2d 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -37,6 +37,7 @@ from openerp.modules import get_resource_path from openerp.tools import topological_sort from openerp.tools.translate import _ from openerp.tools import ustr +from openerp.tools.misc import str2bool from openerp import http from openerp.http import request, serialize_exception as _serialize_exception from openerp.exceptions import AccessError @@ -695,7 +696,7 @@ class Database(http.Controller): def restore(self, master_pwd, backup_file, name, copy=False): try: data = base64.b64encode(backup_file.read()) - request.session.proxy("db").restore(master_pwd, name, data, copy) + request.session.proxy("db").restore(master_pwd, name, data, str2bool(copy)) return http.local_redirect('/web/database/manager') except Exception, e: error = "Database restore error: %s" % e diff --git a/addons/web/doc/search_view.rst b/addons/web/doc/search_view.rst index 12b0eaf3104..76692a0d775 100644 --- a/addons/web/doc/search_view.rst +++ b/addons/web/doc/search_view.rst @@ -241,7 +241,7 @@ with directly by external objects or search view controls .. js:class:: openerp.web.search.Facet A `backbone model`_ representing a single facet of the current - research. May map to a search field, or to a more complex or + search. May map to a search field, or to a more complex or fuzzier input (e.g. a custom filter or an advanced search). .. js:attribute:: category diff --git a/addons/web/static/test/x2many.js b/addons/web/static/test/x2many.js index e053c974fb3..2174ae42994 100644 --- a/addons/web/static/test/x2many.js +++ b/addons/web/static/test/x2many.js @@ -66,10 +66,45 @@ Tour.register({ }, { title: "check the onchange from the o2m to the m2m", - waitFor: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr td:contains(user_test)', + waitFor: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr td:contains(user_test)', + }, + + // add ourself as participant + { + title: "change tab to Participants", + element: '[data-toggle="tab"]:contains(Participants)' + }, + { + title: "click to add participants", + element: '.tab-pane:eq(2).active .o_form_field_x2many_list_row_add a' + }, + { + title: "select Admin", + element: '.modal tr:has(td[data-field="name"]:containsExact(Administrator)) .o_list_record_selector input[type="checkbox"]' + }, + { + title: "save selected participants", + waitFor: '.modal tr:has(td[data-field="name"]:containsExact(Administrator)) .o_list_record_selector input[type="checkbox"]:propChecked', + element: '.o_selectcreatepopup_search_select' + }, + + // save + { + title: "save discussion", + waitFor: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr:has(td:containsExact(Administrator))', + element: 'button.o_form_button_save' + }, + // edit + { + title: "edit discussion", + element: 'button.o_form_button_edit' }, // add message a + { + title: "Select First Tab", + element: 'a[role=tab]:first', + }, { title: "create new message a", element: '.tab-pane:eq(0) .o_form_field_x2many_list_row_add a' @@ -167,15 +202,10 @@ Tour.register({ }, { title: "click to add participants", - element: '.tab-pane:eq(1).active .o_form_field_x2many_list_row_add a' - }, - { - title: "select Admin", - element: '.modal tr:has(td[data-field="name"]:containsExact(Administrator)) .o_list_record_selector input[type="checkbox"]' + element: '.tab-pane:eq(2).active .o_form_field_x2many_list_row_add a' }, { title: "select Demo User", - waitFor: '.modal tr:has(td[data-field="name"]:containsExact(Administrator)) .o_list_record_selector input[type="checkbox"]:propChecked', element: '.modal tr:has(td[data-field="name"]:containsExact(Demo User)) .o_list_record_selector input[type="checkbox"]' }, { @@ -187,8 +217,8 @@ Tour.register({ // save { title: "save discussion", - waitFor: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(2)', - waitNot: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(3)', + waitFor: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(2)', + waitNot: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(3)', element: 'button.o_form_button_save' }, @@ -204,8 +234,8 @@ Tour.register({ }, { title: "check data 3", - waitFor: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(2)', - waitNot: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(3)', + waitFor: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(2)', + waitNot: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(3)', }, // edit @@ -214,7 +244,7 @@ Tour.register({ element: 'button.o_form_button_edit' }, { - title: "change tab to Participants", + title: "change tab to Messages", waitFor: '.o_form_editable', element: '[data-toggle="tab"]:contains(Messages)' }, @@ -344,8 +374,8 @@ Tour.register({ }, { title: "check data 7", - waitFor: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(2)', - waitNot: '.tab-pane:eq(1) .o_form_field.o_view_manager_content tbody tr[date-id]:eq(3)', + waitFor: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr[data-id]:eq(2)', + waitNot: '.tab-pane:eq(2) .o_form_field.o_view_manager_content tbody tr[date-id]:eq(3)', }, // edit diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html index 40479f1c325..a517d1fb4c7 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -209,10 +209,19 @@
-
+ +

In order to avoid conflicts between databases, Odoo needs to know if this database was moved or copied. + If you don't know, answer "This database is a copy".

+
+
+
+
diff --git a/addons/web_settings_dashboard/i18n/web_settings_dashboard.pot b/addons/web_settings_dashboard/i18n/web_settings_dashboard.pot index 6ddf7cee78b..c9a10eb48d7 100644 --- a/addons/web_settings_dashboard/i18n/web_settings_dashboard.pot +++ b/addons/web_settings_dashboard/i18n/web_settings_dashboard.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 9.0c\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-01-28 12:13+0000\n" -"PO-Revision-Date: 2016-01-28 12:13+0000\n" +"POT-Creation-Date: 2016-06-17 08:01+0000\n" +"PO-Revision-Date: 2016-06-17 08:01+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -25,14 +25,21 @@ msgstr "" #. module: web_settings_dashboard #. openerp-web -#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:49 +#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:56 #, python-format msgid "Active User" msgstr "" #. module: web_settings_dashboard #. openerp-web -#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:9 +#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:57 +#, python-format +msgid "Active Users" +msgstr "" + +#. module: web_settings_dashboard +#. openerp-web +#: code:addons/web_settings_dashboard/static/src/xml/dashboard.xml:42 #, python-format msgid "App store" msgstr "" diff --git a/addons/web_settings_dashboard/static/src/xml/dashboard.xml b/addons/web_settings_dashboard/static/src/xml/dashboard.xml index 5f9f9eee309..796629860d7 100644 --- a/addons/web_settings_dashboard/static/src/xml/dashboard.xml +++ b/addons/web_settings_dashboard/static/src/xml/dashboard.xml @@ -52,7 +52,9 @@ - Active Users + + Active User + Active Users
diff --git a/addons/website_livechat/views/website_livechat.xml b/addons/website_livechat/views/website_livechat.xml index bad2c5c651e..fe1b71550c2 100644 --- a/addons/website_livechat/views/website_livechat.xml +++ b/addons/website_livechat/views/website_livechat.xml @@ -157,7 +157,7 @@
- +

diff --git a/addons/website_portal/controllers/main.py b/addons/website_portal/controllers/main.py index a11adac2ab4..05627e83416 100644 --- a/addons/website_portal/controllers/main.py +++ b/addons/website_portal/controllers/main.py @@ -60,6 +60,7 @@ class website_account(http.Controller): error_message = [] mandatory_billing_fields = ["name", "phone", "email", "street2", "city", "country_id"] + optional_billing_fields = ["zipcode", "state_id", "vat", "street"] # Validation for field_name in mandatory_billing_fields: @@ -86,4 +87,9 @@ class website_account(http.Controller): if [err for err in error.values() if err == 'missing']: error_message.append(_('Some required fields are empty.')) + unknown = [k for k in data.iterkeys() if k not in mandatory_billing_fields + optional_billing_fields] + if unknown: + error['common'] = 'Unknown field' + error_message.append("Unknown field '%s'" % ','.join(unknown)) + return error, error_message diff --git a/doc/cla/corporate/Seekda.md b/doc/cla/corporate/Seekda.md new file mode 100644 index 00000000000..0b5c0addde0 --- /dev/null +++ b/doc/cla/corporate/Seekda.md @@ -0,0 +1,17 @@ +Austria, 2016-06-16 + +Seekda GmbH agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Holger Lausen holger.lausen@seekda.com https://github.com/holgerlausen + +List of contributors: + +Holger Lausen holger.lausen@seekda.com https://github.com/holgerlausen +Florian Kisser florian.kisser@seekda.com https://github.com/floriankisser + diff --git a/doc/cla/corporate/microcom.md b/doc/cla/corporate/microcom.md new file mode 100644 index 00000000000..819259990dd --- /dev/null +++ b/doc/cla/corporate/microcom.md @@ -0,0 +1,20 @@ +Canada, 2016-06-09 + +Microcom agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Martin Malorni mmalorni@gmail.com https://github.com/mmalorni + +List of contributors: + +- Vincent Coll vincentgagnnoncoll@gmail.com https://github.com/vincentcoll +- Stéphane Le Cornec stephane.lecornec@gmail.com https://github.com/coleste +- Eric Lemire elemire@users.noreply.github.com https://github.com/elemire +- Martin Malorni mmalorni@gmail.com https://github.com/mmalorni +- Gregory Savoie gs.microcom@gmail.com https://github.com/gregsavoie +- Noreddine Ben Jillali nbj.microcom@gmail.com https://github.com/nbj-microcom diff --git a/doc/cla/corporate/ridingbytes.md b/doc/cla/corporate/ridingbytes.md new file mode 100644 index 00000000000..9abfb539d20 --- /dev/null +++ b/doc/cla/corporate/ridingbytes.md @@ -0,0 +1,15 @@ +Germany, 2016-05-02 + +RIDING BYTES agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Ramon Bartl, rb@ridingbytes.com https://github.com/ridingbytes + +List of contributors: + +Ramon Bartl, rb@ridingbytes.com https://github.com/ramonski diff --git a/doc/cla/corporate/telnet_servizi_srl.md b/doc/cla/corporate/telnet_servizi_srl.md new file mode 100644 index 00000000000..0178e1ce407 --- /dev/null +++ b/doc/cla/corporate/telnet_servizi_srl.md @@ -0,0 +1,16 @@ +Italy, 2016-02-23 + +Telnet Servizi Srl agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Telnet Data github@telnetservizi.it https://github.com/telnetdata + +List of contributors: + +Telnet Data github@telnetservizi.it https://github.com/telnetdata +Matteo Cantarutti cantarutti.matteo@live.it https://github.com/mteok diff --git a/doc/cla/individual/jarmokortetjarvi.md b/doc/cla/individual/jarmokortetjarvi.md new file mode 100644 index 00000000000..523d2857af2 --- /dev/null +++ b/doc/cla/individual/jarmokortetjarvi.md @@ -0,0 +1,11 @@ +Finland, 2016-02-18 + +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, + +Jarmo Kortetjärvi jarmo.kortetjarvi@gmail.com https://github.com/jarmokortetjarvi diff --git a/doc/cla/individual/rossigee.md b/doc/cla/individual/rossigee.md new file mode 100644 index 00000000000..4cc35956206 --- /dev/null +++ b/doc/cla/individual/rossigee.md @@ -0,0 +1,11 @@ +Thailand, 2016-03-26 + +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, + +Ross Golder ross@golder.org https://github.com/rossigee diff --git a/doc/cla/individual/salishaukat.md b/doc/cla/individual/salishaukat.md new file mode 100644 index 00000000000..21f1c734a45 --- /dev/null +++ b/doc/cla/individual/salishaukat.md @@ -0,0 +1,11 @@ + + +Pakistan, 2016-05-05 + +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, + +Sharjeel Ali Shaukat as_ns2002@hotmail.com https://github.com/salishaukat diff --git a/doc/cla/individual/williamjmorenor.md b/doc/cla/individual/williamjmorenor.md new file mode 100644 index 00000000000..b09599fe626 --- /dev/null +++ b/doc/cla/individual/williamjmorenor.md @@ -0,0 +1,9 @@ +Nicaragua, Jun 16th, 2016 + +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, + +William Moreno williamjmorenor@gmail.com https://github.com/williamjmorenor diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst index 4511410fee2..86869b529f1 100644 --- a/doc/reference/orm.rst +++ b/doc/reference/orm.rst @@ -680,7 +680,7 @@ Model Reference .. automethod:: read .. automethod:: read_group - .. rubric:: Research + .. rubric:: Searching .. automethod:: search .. automethod:: search_count diff --git a/doc/reference/views.rst b/doc/reference/views.rst index 6bed888f571..bc2a34e8be6 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -822,7 +822,7 @@ attributes: ``default_group_by`` whether the kanban view should be grouped if no grouping is specified via - the action or the current research. Should be the name of the field to group + the action or the current search. Should be the name of the field to group by when no grouping is otherwise specified ``default_order`` cards sorting order used if the user has not already sorted the records (via diff --git a/openerp/addons/base/ir/ir_model_view.xml b/openerp/addons/base/ir/ir_model_view.xml index a5e47e2d082..c59622bec29 100644 --- a/openerp/addons/base/ir/ir_model_view.xml +++ b/openerp/addons/base/ir/ir_model_view.xml @@ -105,7 +105,7 @@ diff --git a/openerp/addons/base/res/res_currency.py b/openerp/addons/base/res/res_currency.py index b3f13681d27..7ed7ab78e65 100644 --- a/openerp/addons/base/res/res_currency.py +++ b/openerp/addons/base/res/res_currency.py @@ -48,7 +48,7 @@ class Currency(models.Model): self._cr.execute(query, (date, company_id, tuple(self.ids))) currency_rates = dict(self._cr.fetchall()) for currency in self: - currency.rate = currency_rates.get(currency.id, 1.0) + currency.rate = currency_rates.get(currency.id) or 1.0 @api.multi @api.depends('rounding') diff --git a/openerp/addons/test_new_api/ir.model.access.csv b/openerp/addons/test_new_api/ir.model.access.csv index fe025c26751..60cf53bcfa4 100644 --- a/openerp/addons/test_new_api/ir.model.access.csv +++ b/openerp/addons/test_new_api/ir.model.access.csv @@ -4,6 +4,7 @@ access_test_new_api_bravo,access_test_new_api_bravo,model_test_new_api_bravo,,1, access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1 access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1 access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1 +access_emailmessage,test_new_api_emailmessage,test_new_api.model_test_new_api_emailmessage,,1,1,1,1 access_multi,test_new_api_multi,test_new_api.model_test_new_api_multi,,1,1,1,1 access_multi_line,test_new_api_multi_line,test_new_api.model_test_new_api_multi_line,,1,1,1,1 access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1 diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py index 6da662c60a1..ed0dd8fabc0 100644 --- a/openerp/addons/test_new_api/models.py +++ b/openerp/addons/test_new_api/models.py @@ -130,6 +130,9 @@ class Discussion(models.Model): message_concat = fields.Text(string='Message concatenate') important_messages = fields.One2many('test_new_api.message', 'discussion', domain=[('important', '=', True)]) + emails = fields.One2many('test_new_api.emailmessage', 'discussion') + important_emails = fields.One2many('test_new_api.emailmessage', 'discussion', + domain=[('important', '=', True)]) @api.onchange('moderator') def _onchange_moderator(self): @@ -210,6 +213,14 @@ class Message(models.Model): return [('author.partner_id', operator, value)] +class EmailMessage(models.Model): + _name = 'test_new_api.emailmessage' + _inherits = {'test_new_api.message': 'message'} + + message = fields.Many2one('test_new_api.message', 'Message', + required=True, ondelete='cascade') + email_to = fields.Char('To') + class Multi(models.Model): """ Model for testing multiple onchange methods in cascade that modify a one2many field several times. diff --git a/openerp/addons/test_new_api/tests/test_onchange.py b/openerp/addons/test_new_api/tests/test_onchange.py index b2c53115c20..adecda3e4aa 100644 --- a/openerp/addons/test_new_api/tests/test_onchange.py +++ b/openerp/addons/test_new_api/tests/test_onchange.py @@ -12,6 +12,7 @@ class TestOnChange(common.TransactionCase): super(TestOnChange, self).setUp() self.Discussion = self.env['test_new_api.discussion'] self.Message = self.env['test_new_api.message'] + self.EmailMessage = self.env['test_new_api.emailmessage'] def test_default_get(self): """ checking values returned by default_get() """ @@ -88,7 +89,7 @@ class TestOnChange(common.TransactionCase): self.assertEqual(field_onchange.get('messages'), '1') self.assertItemsEqual( strip_prefix('messages.', field_onchange), - ['author', 'body', 'name', 'size'], + ['author', 'body', 'name', 'size', 'important'], ) # modify discussion name @@ -104,6 +105,7 @@ class TestOnChange(common.TransactionCase): 'body': BODY, 'author': USER.id, 'size': len(BODY), + 'important': False, }), ], } @@ -117,12 +119,14 @@ class TestOnChange(common.TransactionCase): 'body': message.body, 'author': message.author.name_get()[0], 'size': message.size, + 'important': message.important, }), (0, 0, { 'name': "[%s] %s" % ("Foo", USER.name), 'body': BODY, 'author': USER.name_get()[0], 'size': len(BODY), + 'important': False, }), ]) @@ -223,3 +227,71 @@ class TestOnChange(common.TransactionCase): self.assertIn('message_concat', result['value']) self.assertEqual(result['value']['message_concat'], "\n".join(["%s:%s" % (m.name, m.body) for m in discussion.messages])) + + def test_onchange_one2many_with_domain_on_related_field(self): + """ test the value of the one2many field when defined with a domain on a related field""" + discussion = self.env.ref('test_new_api.discussion_0') + demo = self.env.ref('base.user_demo') + + # mimic UI behaviour, so we get subfields + # (we need at least subfield: 'important_emails.important') + view_info = self.Discussion.fields_view_get( + view_id=self.env.ref('test_new_api.discussion_form').id, + view_type='form') + field_onchange = self.Discussion._onchange_spec(view_info=view_info) + self.assertEqual(field_onchange.get('messages'), '1') + + BODY = "What a beautiful day!" + USER = self.env.user + + # create standalone email + email = self.EmailMessage.create({ + 'discussion': discussion.id, + 'name': "[%s] %s" % ('', USER.name), + 'body': BODY, + 'author': USER.id, + 'important': False, + 'email_to': demo.email, + }) + + # check if server-side cache is working correctly + self.env.invalidate_all() + self.assertIn(email, discussion.emails) + self.assertNotIn(email, discussion.important_emails) + email.important = True + self.assertIn(email, discussion.important_emails) + + # check that when trigger an onchange, we don't reset important emails + # (force `invalidate_all` as but appear in onchange only when we get a + # cache miss) + self.env.invalidate_all() + self.assertEqual(len(discussion.messages), 4) + values = { + 'name': "Foo Bar", + 'moderator': demo.id, + 'categories': [(4, cat.id) for cat in discussion.categories], + 'messages': [(4, msg.id) for msg in discussion.messages], + 'participants': [(4, usr.id) for usr in discussion.participants], + 'message_changes': 0, + 'important_messages': [(4, msg.id) for msg in discussion.important_messages], + 'important_emails': [(4, eml.id) for eml in discussion.important_emails], + } + result = discussion.onchange(values, 'name', field_onchange) + + # When one2many domain contains non-computed field, things are ok + self.assertEqual(result['value']['important_messages'], + [(5,)] + [(4, msg.id) for msg in discussion.important_messages]) + + # But here with commit 5676d81, we get value of: [(2, email.id)] + self.assertEqual( + result['value']['important_emails'], + [(5,), + (1, email.id, { + 'name': u'[Foo Bar] %s' % USER.name, + 'body': email.body, + 'author': USER.name_get()[0], + 'important': True, + 'email_to': demo.email, + 'size': email.size, + })] + ) diff --git a/openerp/addons/test_new_api/views.xml b/openerp/addons/test_new_api/views.xml index f9d84d38494..9cc5c6ce2ce 100644 --- a/openerp/addons/test_new_api/views.xml +++ b/openerp/addons/test_new_api/views.xml @@ -59,12 +59,35 @@ +
+ + +