diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index 82828965d52..6379bd87fbc 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -179,8 +179,8 @@ class account_journal(models.Model):
account_ids = tuple(ac for ac in [self.default_debit_account_id.id, self.default_credit_account_id.id] if ac)
if account_ids:
amount_field = 'balance' if not self.currency_id else 'amount_currency'
- query = """SELECT sum(%s) FROM account_move_line WHERE account_id in %%s;""" % (amount_field,)
- self.env.cr.execute(query, (account_ids,))
+ query = """SELECT sum(%s) FROM account_move_line WHERE account_id in %%s AND date <= %%s;""" % (amount_field,)
+ self.env.cr.execute(query, (account_ids, fields.Date.today(),))
query_results = self.env.cr.dictfetchall()
if query_results and query_results[0].get('sum') != None:
account_sum = query_results[0].get('sum')
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index d0dccab7844..40503c62388 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1399,7 +1399,9 @@ class AccountMoveLine(models.Model):
@api.model
def _query_get(self, domain=None):
context = dict(self._context or {})
- domain = domain and safe_eval(str(domain)) or []
+ domain = domain or []
+ if not isinstance(domain, (list, tuple)):
+ domain = safe_eval(domain)
date_field = 'date'
if context.get('aged_balance'):
diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py
index de32aaa5f3d..25e6449c7af 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -356,7 +356,7 @@ class ResPartner(models.Model):
@api.multi
def mark_as_reconciled(self):
self.env['account.partial.reconcile'].check_access_rights('write')
- return self.sudo().write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
+ return self.sudo().with_context(company_id=self.env.user.company_id.id).write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
@api.one
def _get_company_currency(self):
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js
index cdf32c4010e..27f327febb1 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -22,6 +22,9 @@ var db = {
{id: 4, display_name: "partner 4", image: 'DDD', customer: true},
{id: 8, display_name: "Agrolait", image: 'EEE', customer: true},
{id: 12, display_name: "Camptocamp", image: 'FFF', supplier: true, property_account_receivable_id: 287, property_account_payable_id: 287},
+ // add more to have 'Search More' option
+ {id: 98, display_name: "partner 98", image: 'YYY', customer: true},
+ {id: 99, display_name: "partner 99", image: 'ZZZ', customer: true},
],
mark_as_reconciled: function () {
return $.when();
@@ -738,12 +741,18 @@ QUnit.module('account', {
});
QUnit.test('Reconciliation change partner', function (assert) {
- assert.expect(10);
+ assert.expect(13);
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
testUtils.addMockEnvironment(clientAction, {
data: this.params.data,
+ archs: {
+ 'res.partner,false,list': '',
+ 'res.partner,false,search': '' +
+ '' +
+ '',
+ },
});
clientAction.appendTo($('#qunit-fixture'));
@@ -758,6 +767,15 @@ QUnit.module('account', {
assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp");
assert.strictEqual(widget.$('.match table tr.mv_line').length, 3, "camptocamp should have 3 propositions for reconciliation");
+ // Simulate changing partner with SelectCreateDialog
+ widget = clientAction.widgets[1];
+ assert.strictEqual($('.modal').length, 0, "shouldn't have any opened modal");
+ widget.$('.o_input_dropdown input').trigger('click');
+ $('.ui-autocomplete .ui-menu-item a:contains(Search More):eq(1)').trigger('mouseenter').trigger('click');
+ assert.strictEqual($('.modal').length, 1, "should open a SelectCreateDialog");
+ $('.modal table.o_list_view td:contains(Camptocamp)').click();
+ assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp");
+
widget = clientAction.widgets[2];
widget.$('.accounting_view thead td:first').trigger('click');
widget.$('.accounting_view .mv_line .cell_label').trigger('click');
diff --git a/addons/account_asset/models/account_invoice.py b/addons/account_asset/models/account_invoice.py
index d4b65d6b020..192c984b265 100644
--- a/addons/account_asset/models/account_invoice.py
+++ b/addons/account_asset/models/account_invoice.py
@@ -107,6 +107,7 @@ class AccountInvoiceLine(models.Model):
self.asset_category_id = self.product_id.product_tmpl_id.deferred_revenue_category_id.id
elif invoice.type == 'in_invoice':
self.asset_category_id = self.product_id.product_tmpl_id.asset_category_id.id
+ self.onchange_asset_category_id()
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
def get_invoice_line_account(self, type, product, fpos, company):
diff --git a/addons/account_tax_python/models/account_tax.py b/addons/account_tax_python/models/account_tax.py
index 7ab0c2bbe1f..8b438b82859 100644
--- a/addons/account_tax_python/models/account_tax.py
+++ b/addons/account_tax_python/models/account_tax.py
@@ -37,9 +37,9 @@ class AccountTaxPython(models.Model):
@api.multi
def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None):
- taxes = self.env['account.tax']
+ taxes = self.filtered(lambda r: r.amount_type != 'code')
company = self.env.user.company_id
- for tax in self:
+ for tax in self.filtered(lambda r: r.amount_type == 'code'):
localdict = {'price_unit': price_unit, 'quantity': quantity, 'product': product, 'partner': partner, 'company': company}
safe_eval(tax.python_applicable, localdict, mode="exec", nocopy=True)
if localdict.get('result', False):
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py
index 02256abaa22..a0f7d79288c 100644
--- a/addons/account_voucher/models/account_voucher.py
+++ b/addons/account_voucher/models/account_voucher.py
@@ -82,7 +82,7 @@ class AccountVoucher(models.Model):
@api.model
def _get_currency(self):
- journal = self.env['account.journal'].browse(self._context.get('journal_id', False))
+ journal = self.env['account.journal'].browse(self.env.context.get('default_journal_id', False))
if journal.currency_id:
return journal.currency_id.id
return self.env.user.company_id.currency_id.id
diff --git a/addons/base_setup/views/base_config_settings_views.xml b/addons/base_setup/views/base_config_settings_views.xml
index a0dab58c570..d97c0d421e5 100644
--- a/addons/base_setup/views/base_config_settings_views.xml
+++ b/addons/base_setup/views/base_config_settings_views.xml
@@ -23,7 +23,7 @@
diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py
index 3b6b648a9bd..b2525706da5 100644
--- a/addons/base_vat/models/res_partner.py
+++ b/addons/base_vat/models/res_partner.py
@@ -98,7 +98,11 @@ class ResPartner(models.Model):
@api.constrains('vat', 'commercial_partner_country_id')
def check_vat(self):
- if self.env.user.company_id.vat_check_vies:
+ if self.env.context.get('company_id'):
+ company = self.env['res.company'].browse(self.env.context['company_id'])
+ else:
+ company = self.env.user.company_id
+ if company.vat_check_vies:
# force full VIES online check
check_func = self.vies_vat_check
else:
@@ -121,7 +125,11 @@ class ResPartner(models.Model):
self.ensure_one()
vat_no = "'CC##' (CC=Country Code, ##=VAT Number)"
vat_no = _ref_vat.get(country_code) or vat_no
- if self.env.user.company_id.vat_check_vies:
+ if self.env.context.get('company_id'):
+ company = self.env['res.company'].browse(self.env.context['company_id'])
+ else:
+ company = self.env.user.company_id
+ if company.vat_check_vies:
return '\n' + _('The VAT number [%s] for partner [%s] either failed the VIES VAT validation check or did not respect the expected format %s.') % (self.vat, self.name, vat_no)
return '\n' + _('The VAT number [%s] for partner [%s] does not seem to be valid. \nNote: the expected format is %s') % (self.vat, self.name, vat_no)
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py
index e80a2727282..7b7275f9a1d 100644
--- a/addons/calendar/models/calendar.py
+++ b/addons/calendar/models/calendar.py
@@ -1409,7 +1409,8 @@ class Meeting(models.Model):
partners_to_notify.append(event_attendees_changes['removed_partners'].ids)
self.env['calendar.alarm_manager'].notify_next_alarm(partners_to_notify)
- if (values.get('start_date') or values.get('start_datetime')) and values.get('active', True):
+ if (values.get('start_date') or values.get('start_datetime') or
+ (values.get('start') and self.env.context.get('from_ui'))) and values.get('active', True):
for current_meeting in all_meetings:
if attendees_create:
attendees_create = attendees_create[current_meeting.id]
diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py
index a91b9ec3dc1..44c6b611d74 100644
--- a/addons/crm/wizard/base_partner_merge.py
+++ b/addons/crm/wizard/base_partner_merge.py
@@ -163,7 +163,7 @@ class MergePartnerAutomatic(models.TransientModel):
except psycopg2.Error:
# updating fails, most likely due to a violated unique constraint
# keeping record with nonexistent partner_id is useless, better delete it
- query = 'DELETE FROM %(table)s WHERE %(column)s IN %%s' % query_dic
+ query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic
self._cr.execute(query, (tuple(src_partners.ids),))
@api.model
diff --git a/addons/google_calendar/models/google_calendar.py b/addons/google_calendar/models/google_calendar.py
index ac91835f530..590619ae936 100644
--- a/addons/google_calendar/models/google_calendar.py
+++ b/addons/google_calendar/models/google_calendar.py
@@ -247,7 +247,8 @@ class GoogleCalendar(models.AbstractModel):
if not self.get_need_synchro_attendee():
data.pop("attendees")
if isCreating:
- other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids if other_att.google_internal_event_id]
+ other_google_ids = [other_att.google_internal_event_id for other_att in event.attendee_ids
+ if other_att.google_internal_event_id and not other_att.google_internal_event_id.startswith('_')]
if other_google_ids:
data["id"] = other_google_ids[0]
return data
@@ -608,7 +609,8 @@ class GoogleCalendar(models.AbstractModel):
('event_id.final_date', '>', fields.Datetime.to_string(self.get_minTime())),
])
for att in my_attendees:
- other_google_ids = [other_att.google_internal_event_id for other_att in att.event_id.attendee_ids if other_att.google_internal_event_id and other_att.id != att.id]
+ other_google_ids = [other_att.google_internal_event_id for other_att in att.event_id.attendee_ids if
+ other_att.google_internal_event_id and other_att.id != att.id and not other_att.google_internal_event_id.startswith('_')]
for other_google_id in other_google_ids:
if self.get_one_event_synchro(other_google_id):
att.write({'google_internal_event_id': other_google_id})
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index e5b95282faf..1d0aec5901b 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -52,7 +52,7 @@ class PurchaseOrder(models.Model):
if any(float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) == -1 for line in order.order_line):
order.invoice_status = 'to invoice'
- elif all(float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) >= 0 for line in order.order_line):
+ elif all(float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) >= 0 for line in order.order_line) and order.invoice_ids:
order.invoice_status = 'invoiced'
else:
order.invoice_status = 'no'
@@ -677,7 +677,9 @@ class PurchaseOrderLine(models.Model):
order = line.order_id
price_unit = line.price_unit
if line.taxes_id:
- price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_excluded']
+ price_unit = line.taxes_id.with_context(round=False).compute_all(
+ price_unit, currency=line.order_id.currency_id, quantity=1.0, product=line.product_id, partner=line.order_id.partner_id
+ )['total_excluded']
if line.product_uom.id != line.product_id.uom_id.id:
price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
if order.currency_id != order.company_id.currency_id:
diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py
index ad5b81d9bdf..91a9061fe58 100644
--- a/addons/stock/models/procurement.py
+++ b/addons/stock/models/procurement.py
@@ -190,17 +190,21 @@ class ProcurementOrder(models.Model):
def run(self, autocommit=False):
# TDE CLEANME: unused context key procurement_auto_defer remove
new_self = self.filtered(lambda order: order.state not in ['running', 'done', 'cancel'])
- res = super(ProcurementOrder, new_self).run(autocommit=autocommit)
+ res = True
+ if new_self:
+ res = super(ProcurementOrder, new_self).run(autocommit=autocommit)
- # after all the procurements are run, check if some created a draft stock move that needs to be confirmed
- # (we do that in batch because it fasts the picking assignation and the picking state computation)
- new_self.filtered(lambda order: order.state == 'running' and order.rule_id.action == 'move').mapped('move_ids').filtered(lambda move: move.state == 'draft').action_confirm()
+ # after all the procurements are run, check if some created a draft stock move that needs to be confirmed
+ # (we do that in batch because it fasts the picking assignation and the picking state computation)
+ move_ids = new_self.filtered(lambda order: order.state == 'running' and order.rule_id.action == 'move').mapped('move_ids').filtered(lambda move: move.state == 'draft')
+ if move_ids:
+ move_ids.action_confirm()
- # TDE FIXME: action_confirm in stock_move already call run() ... necessary ??
- # If procurements created other procurements, run the created in batch
- new_procurements = self.search([('move_dest_id.procurement_id', 'in', new_self.ids)], order='id')
- if new_procurements:
- res = new_procurements.run(autocommit=autocommit)
+ # TDE FIXME: action_confirm in stock_move already call run() ... necessary ??
+ # If procurements created other procurements, run the created in batch
+ new_procurements = self.search([('move_dest_id.procurement_id', 'in', new_self.ids)], order='id')
+ if new_procurements:
+ res = new_procurements.run(autocommit=autocommit)
return res
@api.multi
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index cadeaea8bb8..f8a1f8b9dd0 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -82,7 +82,7 @@ class StockMoveLine(models.Model):
class StockMove(models.Model):
_inherit = "stock.move"
- to_refund = fields.Boolean(string="To Refund (update SO/PO)",
+ to_refund = fields.Boolean(string="To Refund (update SO/PO)", copy=False,
help='Trigger a decrease of the delivered/received quantity in the associated Sale Order/Purchase Order')
value = fields.Float()
remaining_qty = fields.Float()
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 9b1e930cef3..c2bd4f5d5b8 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -401,8 +401,8 @@ var FieldMany2One = AbstractField.extend({
initial_ids: ids ? _.map(ids, function (x) { return x[0]; }) : undefined,
initial_view: view,
disable_multiple_selection: true,
- on_selected: function (element_ids) {
- self.reinitialize({id: element_ids[0]});
+ on_selected: function (records) {
+ self.reinitialize(records[0]);
self.activate();
}
})).open();
@@ -1150,15 +1150,16 @@ var FieldMany2Many = FieldX2Many.extend({
title: _t("Add: ") + this.string,
no_create: this.nodeOptions.no_create || !this.activeActions.create,
fields_view: this.attrs.views.form,
- on_selected: function (res_ids) {
- var new_ids = _.difference(res_ids, self.value.res_ids);
- if (new_ids.length) {
- var values = _.map(new_ids, function (id) {
+ on_selected: function (records) {
+ var resIDs = _.pluck(records, 'id');
+ var newIDs = _.difference(resIDs, self.value.res_ids);
+ if (newIDs.length) {
+ var values = _.map(newIDs, function (id) {
return {id: id};
});
self._setValue({
operation: 'ADD_M2M',
- ids: values
+ ids: values,
});
}
}
diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js
index eb6d7176884..4374421d7b3 100644
--- a/addons/web/static/src/js/views/calendar/calendar_model.js
+++ b/addons/web/static/src/js/views/calendar/calendar_model.js
@@ -2,6 +2,7 @@ odoo.define('web.CalendarModel', function (require) {
"use strict";
var AbstractModel = require('web.AbstractModel');
+var Context = require('web.Context');
var core = require('web.core');
var fieldUtils = require('web.field_utils');
var session = require('web.session');
@@ -298,11 +299,12 @@ return AbstractModel.extend({
data[k] = dateToServer(data[k]);
}
}
+ var context = new Context(this.data.context, {from_ui: true});
return this._rpc({
model: this.modelName,
method: 'write',
args: [[record.id], data],
- context: this.data.context
+ context: context
});
},
diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js
index 2465b1ee3c5..7222d792c69 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -87,9 +87,20 @@ var ListController = BasicController.extend({
* @returns {number[]} list of res_ids
*/
getSelectedIds: function () {
+ return _.map(this.getSelectedRecords(), function (record) {
+ return record.res_id;
+ });
+ },
+ /**
+ * Returns the list of currently selected records (with the check boxes on
+ * the left)
+ *
+ * @returns {Object[]} list of records
+ */
+ getSelectedRecords: function () {
var self = this;
return _.map(this.selectedRecords, function (db_id) {
- return self.model.get(db_id, {raw: true}).res_id;
+ return self.model.get(db_id, {raw: true});
});
},
/**
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index 876c37fbd59..645540b7a57 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -238,8 +238,11 @@ var SelectCreateListController = ListController.extend({
// row of the list) such that it triggers up 'select_record' with its res_id.
custom_events: _.extend({}, ListController.prototype.custom_events, {
open_record: function (event) {
- var selected_record = this.model.get(event.data.id);
- this.trigger_up('select_record', {id: selected_record.res_id});
+ var selectedRecord = this.model.get(event.data.id);
+ this.trigger_up('select_record', {
+ id: selectedRecord.res_id,
+ display_name: selectedRecord.data.display_name,
+ });
},
}),
});
@@ -251,7 +254,7 @@ var SelectCreateDialog = ViewDialog.extend({
custom_events: _.extend({}, ViewDialog.prototype.custom_events, {
select_record: function (event) {
if (!this.options.readonly) {
- this.on_selected([event.data.id]);
+ this.on_selected([event.data]);
this.close();
}
},
@@ -369,7 +372,14 @@ var SelectCreateDialog = ViewDialog.extend({
disabled: true,
close: true,
click: function () {
- self.on_selected(self.list_controller.getSelectedIds());
+ var records = self.list_controller.getSelectedRecords();
+ var values = _.map(records, function (record) {
+ return {
+ id: record.res_id,
+ display_name: record.data.display_name,
+ };
+ });
+ self.on_selected(values);
},
});
}
@@ -399,7 +409,11 @@ var SelectCreateDialog = ViewDialog.extend({
var self = this;
var dialog = new FormViewDialog(this, _.extend({}, this.options, {
on_saved: function (record) {
- self.on_selected([record.res_id]);
+ var values = [{
+ id: record.res_id,
+ display_name: record.data.display_name,
+ }];
+ self.on_selected(values);
},
})).open();
dialog.on('closed', this, this.close.bind(this));
diff --git a/addons/web/static/src/js/widgets/debug_manager.js b/addons/web/static/src/js/widgets/debug_manager.js
index c74ce95ac1b..a57e452de4b 100644
--- a/addons/web/static/src/js/widgets/debug_manager.js
+++ b/addons/web/static/src/js/widgets/debug_manager.js
@@ -169,11 +169,11 @@ var DebugManager = Widget.extend({
title: _t('Select a view'),
disable_multiple_selection: true,
domain: [['type', '!=', 'qweb'], ['type', '!=', 'search']],
- on_selected: function (element_ids) {
+ on_selected: function (records) {
self._rpc({
model: 'ir.ui.view',
method: 'search_read',
- domain: [['id', '=', element_ids[0]]],
+ domain: [['id', '=', records[0].id]],
fields: ['name', 'model', 'type'],
limit: 1,
})
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 45110c7cf9a..2020e0124f3 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -3463,7 +3463,6 @@ QUnit.module('relational_fields', {
'',
mockRPC: function (method, args) {
if (args.method === 'write') {
- // debugger;
assert.deepEqual(args.args[1].turtles, [
[1, 3, {display_name: 'coucou', turtle_foo: 'has changed', turtle_int: 42}],
], "correct commands should be sent (only send changed values)");
diff --git a/doc/cla/individual/d4n.md b/doc/cla/individual/d4n.md
new file mode 100644
index 00000000000..080d8ba9789
--- /dev/null
+++ b/doc/cla/individual/d4n.md
@@ -0,0 +1,12 @@
+Germany, 2017-09-12
+
+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,
+
+Dan Čermák dan.cermak@cgc-instruments.com https://github.com/D4N
+Dan Čermák dan.cermak@cgc-instruments.de https://github.com/D4N