summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-05-28 15:36:23 +0200
committerChristophe Simonis <chs@odoo.com>2019-05-28 15:52:00 +0200
commitcffd147cfd4767f57dc6deeb0a956361904e1b1b (patch)
tree8a5990635ca60e0f34fc59462ed694847714db5b
parente9350993ca0b81df17308766d7e26d458bc39520 (diff)
parenta20a31486cd0b99755f8e6c27281ac6ba4758b7a (diff)
[MERGE] forward port branch 11.0 up to a20a31486cd
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_action.js9
-rw-r--r--addons/fleet/models/fleet_vehicle.py2
-rw-r--r--addons/hr_expense/models/hr_expense.py35
-rw-r--r--addons/l10n_fr_fec/wizard/account_fr_fec.py4
-rw-r--r--addons/mail/wizard/mail_compose_message.py1
-rw-r--r--addons/payment/models/payment_acquirer.py2
-rw-r--r--addons/payment_authorize/models/authorize_request.py6
-rw-r--r--addons/payment_authorize/models/payment.py21
-rw-r--r--addons/point_of_sale/models/pos_order.py2
-rw-r--r--addons/point_of_sale/static/src/js/models.js9
-rw-r--r--addons/purchase/models/purchase.py4
-rw-r--r--addons/sale/models/account_invoice.py2
-rw-r--r--addons/sale_timesheet/security/sale_timesheet_security.xml2
-rw-r--r--addons/sms/models/mail_thread.py4
-rw-r--r--addons/sms/wizard/send_sms.py19
-rw-r--r--addons/stock/models/product.py4
-rw-r--r--addons/stock/models/stock_move.py5
-rw-r--r--addons/stock/wizard/stock_picking_return_views.xml2
-rw-r--r--addons/test_mail/tests/test_mail_template.py40
-rw-r--r--addons/web/static/src/js/core/ajax.js8
-rw-r--r--addons/web/static/src/js/views/calendar/calendar_controller.js2
-rw-r--r--addons/web/static/src/js/views/list/list_controller.js5
-rw-r--r--addons/web/static/src/less/form_view_extra.less2
-rw-r--r--addons/website_quote/models/sale_order.py2
-rw-r--r--doc/cla/corporate/acsone.md1
-rw-r--r--doc/cla/corporate/vauxoo.md1
-rw-r--r--odoo/addons/test_new_api/tests/test_new_fields.py7
-rw-r--r--odoo/fields.py2
-rw-r--r--odoo/models.py46
-rw-r--r--odoo/tools/translate.py5
30 files changed, 176 insertions, 78 deletions
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js
index 37844f00b3a..c4e0610ec52 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_action.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js
@@ -295,9 +295,12 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
'context': self.model.getContext(),
});
_.each(result.handles, function (handle) {
- self._getWidget(handle).destroy();
- var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;});
- self.widgets.splice(index, 1);
+ var widget = self._getWidget(handle);
+ if (widget) {
+ widget.destroy();
+ var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;});
+ self.widgets.splice(index, 1);
+ }
});
// Get number of widget and if less than constant and if there are more to laod, load until constant
if (self.widgets.length < self.model.defaultDisplayQty
diff --git a/addons/fleet/models/fleet_vehicle.py b/addons/fleet/models/fleet_vehicle.py
index ae8fa16f858..f0edc5a3059 100644
--- a/addons/fleet/models/fleet_vehicle.py
+++ b/addons/fleet/models/fleet_vehicle.py
@@ -219,7 +219,7 @@ class FleetVehicle(models.Model):
changes.append(_("License Plate: from '%s' to '%s'") % (old_license_plate, vals['license_plate']))
if len(changes) > 0:
- self.message_post(body=", ".join(changes))
+ vehicle.message_post(body=", ".join(changes))
return super(FleetVehicle, self).write(vals)
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 1b08fa83d35..239b57ade7a 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -189,6 +189,27 @@ class HrExpense(models.Model):
# ----------------------------------------
@api.multi
+ def _prepare_move_values(self):
+ """
+ This function prepares move values related to an expense
+ """
+ self.ensure_one()
+ journal = self.sheet_id.bank_journal_id if self.payment_mode == 'company_account' else self.sheet_id.journal_id
+ account_date = expense.sheet_id.accounting_date or expense.date
+ move_values = {
+ 'journal_id': journal.id,
+ 'company_id': self.env.user.company_id.id,
+ 'date': account_date,
+ 'ref': self.sheet_id.name,
+ # force the name to the default value, to avoid an eventual 'default_name' in the context
+ # to set it to '' which cause no number to be given to the account.move when posted.
+ 'name': '/',
+ }
+ return move_values
+
+ @api.multi
+
+ @api.multi
def _get_account_move_by_sheet(self):
""" Return a mapping between the expense sheet of current expense and its account move
:returns dict where key is a sheet id, and value is an account move record
@@ -196,18 +217,8 @@ class HrExpense(models.Model):
move_grouped_by_sheet = {}
for expense in self:
# create the move that will contain the accounting entries
- account_date = expense.sheet_id.accounting_date or expense.date
- if expense.sheet_id.id not in move_grouped_by_sheet:
- journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
- move = self.env['account.move'].create({
- 'journal_id': journal.id,
- 'company_id': self.env.user.company_id.id,
- 'date': account_date,
- 'ref': expense.sheet_id.name,
- # force the name to the default value, to avoid an eventual 'default_name' in the context
- # to set it to '' which cause no number to be given to the account.move when posted.
- 'name': '/',
- })
+ if expense.sheet_id.id not in move_group_by_sheet:
+ move = self.env['account.move'].create(expense._prepare_move_values())
move_grouped_by_sheet[expense.sheet_id.id] = move
else:
move = move_grouped_by_sheet[expense.sheet_id.id]
diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py
index b9e8af84869..670109986d6 100644
--- a/addons/l10n_fr_fec/wizard/account_fr_fec.py
+++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py
@@ -306,7 +306,7 @@ class AccountFrFec(models.TransientModel):
END
AS PieceRef,
TO_CHAR(am.date, 'YYYYMMDD') AS PieceDate,
- CASE WHEN aml.name IS NULL THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib,
+ CASE WHEN aml.name IS NULL OR aml.name = '' THEN '/' ELSE replace(replace(aml.name, '|', '/'), '\t', '') END AS EcritureLib,
replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
@@ -389,7 +389,7 @@ class AccountFrFec(models.TransientModel):
rows_length = len(rows)
for i, row in enumerate(rows):
if not i == rows_length - 1:
- row.append(lineterminator)
+ row[-1] += lineterminator
writer.writerow(row)
fecvalue = fecfile.getvalue()
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index c22a8a10cc9..cef6496c352 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -391,6 +391,7 @@ class MailComposer(models.TransientModel):
# ORM handle the assignation of command list on new onchange (api.v8),
# this force the complete replacement of x2many field with
# command and is compatible with onchange api.v7
+ attachment_ids += values.pop('attachment_ids' , [])
values = self._convert_to_write(values)
if attachment_ids:
values.update(attachment_ids=[(6, 0, attachment_ids)])
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index 6026b62484c..b959ccc8ea0 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -650,7 +650,7 @@ class PaymentTransaction(models.Model):
# custom create
custom_method_name = '%s_create' % acquirer.provider
- if hasattr(acquirer, custom_method_name):
+ if hasattr(self, custom_method_name):
values.update(getattr(self, custom_method_name)(values))
# Default value of reference is
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index d9b87437577..109cf455f6d 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -224,6 +224,12 @@ class AuthorizeAPI():
etree.SubElement(customer, "email").text = partner.email or ''
response = self._authorize_request(root)
res = dict()
+ if not response.find('customerProfileId'):
+ _logger.warning(
+ 'Unable to create customer payment profile, data missing from transaction. Transaction_id: %s - Partner_id: %s'
+ % (transaction_id, partner)
+ )
+ return res
res['profile_id'] = response.find('customerProfileId').text
res['payment_profile_id'] = response.find('customerPaymentProfileIdList/numericString').text
root_profile = self._base_tree('getCustomerPaymentProfileRequest')
diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py
index cf55effdd12..0c95a444c64 100644
--- a/addons/payment_authorize/models/payment.py
+++ b/addons/payment_authorize/models/payment.py
@@ -240,17 +240,18 @@ class TxAuthorize(models.Model):
(self.type == 'form_save' or self.acquirer_id.save_token == 'always'):
transaction = AuthorizeAPI(self.acquirer_id)
res = transaction.create_customer_profile_from_tx(self.partner_id, self.acquirer_reference)
- token_id = self.env['payment.token'].create({
- 'authorize_profile': res.get('profile_id'),
- 'name': res.get('name'),
- 'acquirer_ref': res.get('payment_profile_id'),
- 'acquirer_id': self.acquirer_id.id,
- 'partner_id': self.partner_id.id,
- })
- self.payment_token_id = token_id
+ if res:
+ token_id = self.env['payment.token'].create({
+ 'authorize_profile': res.get('profile_id'),
+ 'name': res.get('name'),
+ 'acquirer_ref': res.get('payment_profile_id'),
+ 'acquirer_id': self.acquirer_id.id,
+ 'partner_id': self.partner_id.id,
+ })
+ self.payment_token_id = token_id
- if self.payment_token_id:
- self.payment_token_id.verified = True
+ if self.payment_token_id:
+ self.payment_token_id.verified = True
return True
elif status_code == self._authorize_pending_tx_status:
self.write({
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index b1fc92605e3..342a55fc9b9 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -857,7 +857,7 @@ class PosOrder(models.Model):
# a serialnumber always has a quantity of 1 product, a lot number takes the full quantity of the order line
qty = 1.0
if stock_production_lot.product_id.tracking == 'lot':
- qty = pos_pack_lot.pos_order_line_id.qty
+ qty = abs(pos_pack_lot.pos_order_line_id.qty)
qty_done += qty
pack_lots.append({'lot_id': stock_production_lot.id, 'qty': qty})
else:
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index d39a8987d9d..b5ac7da464d 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1473,7 +1473,7 @@ exports.Orderline = Backbone.Model.extend({
var lots_required = 1;
if (this.product.tracking == 'serial') {
- lots_required = this.quantity;
+ lots_required = Math.abs(this.quantity);
}
return lots_required;
@@ -1876,8 +1876,11 @@ var PacklotlineCollection = Backbone.Collection.extend({
set_quantity_by_lot: function() {
if (this.order_line.product.tracking == 'serial') {
- var valid_lots = this.get_valid_lots();
- this.order_line.set_quantity(valid_lots.length);
+ var valid_lots_quantity = this.get_valid_lots().length;
+ if (this.order_line.quantity < 0){
+ valid_lots_quantity = -valid_lots_quantity;
+ }
+ this.order_line.set_quantity(valid_lots_quantity);
}
}
});
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index b3bac6ad1de..2d22c43abc1 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -766,7 +766,9 @@ class PurchaseOrderLine(models.Model):
for move in self.move_ids.filtered(lambda x: x.state != 'cancel' and not x.location_dest_id.usage == "supplier"):
qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP')
template = {
- 'name': self.name or '',
+ # truncate to 2000 to avoid triggering index limit error
+ # TODO: remove index in master?
+ 'name': (self.name or '')[:2000],
'product_id': self.product_id.id,
'product_uom': self.product_uom.id,
'date': self.order_id.date_approve,
diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py
index 0a2406a0968..c705c793c93 100644
--- a/addons/sale/models/account_invoice.py
+++ b/addons/sale/models/account_invoice.py
@@ -41,7 +41,7 @@ class AccountInvoice(models.Model):
self.partner_shipping_id = addr and addr.get('delivery')
if self.env.context.get('type', 'out_invoice') == 'out_invoice':
company = self.company_id or self.env.user.company_id
- self.comment = company.with_context(lang=self.partner_id.lang).sale_note
+ self.comment = company.with_context(lang=self.partner_id.lang).sale_note or (self._origin.company_id == company and self.comment)
@api.multi
def action_invoice_paid(self):
diff --git a/addons/sale_timesheet/security/sale_timesheet_security.xml b/addons/sale_timesheet/security/sale_timesheet_security.xml
index b4b5dde9754..fecc5292e63 100644
--- a/addons/sale_timesheet/security/sale_timesheet_security.xml
+++ b/addons/sale_timesheet/security/sale_timesheet_security.xml
@@ -11,7 +11,7 @@
<field name="perm_create" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_unlink" eval="1"/>
- <field name="perm_read" eval="0"/>
+ <field name="perm_read" eval="1"/>
</record>
</data>
diff --git a/addons/sms/models/mail_thread.py b/addons/sms/models/mail_thread.py
index 1fb62a72de4..a439487e4e9 100644
--- a/addons/sms/models/mail_thread.py
+++ b/addons/sms/models/mail_thread.py
@@ -39,8 +39,8 @@ class MailThread(models.AbstractModel):
if not partners:
partners = self._get_default_sms_recipients()
- # Collect numbers, we will consider the message to be sent if at least one number can be found
- numbers = list(set([i.mobile for i in partners if i.mobile]))
+ # Collect numbers, we will consider the message to be sent if at least one number can be found
+ numbers = list(set([i.mobile for i in partners if i.mobile]))
if numbers:
try:
diff --git a/addons/sms/wizard/send_sms.py b/addons/sms/wizard/send_sms.py
index 15d284e0879..f25bedb72ab 100644
--- a/addons/sms/wizard/send_sms.py
+++ b/addons/sms/wizard/send_sms.py
@@ -44,8 +44,8 @@ class SendSMS(models.TransientModel):
return number
if not phonenumbers.is_possible_number(phone_nbr) or not phonenumbers.is_valid_number(phone_nbr):
return number
- phone_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL
- return phonenumbers.format_number(phone_nbr, phone_fmt).replace(' ', '')
+ phone_fmt = phonenumbers.PhoneNumberFormat.E164
+ return phonenumbers.format_number(phone_nbr, phone_fmt)
else:
return number
@@ -61,12 +61,11 @@ class SendSMS(models.TransientModel):
@api.model
def default_get(self, fields):
result = super(SendSMS, self).default_get(fields)
-
active_model = self.env.context.get('active_model')
- model = self.env[active_model]
- records = self._get_records(model)
- if getattr(records, '_get_default_sms_recipients'):
+ if not self.env.context.get('default_recipients') and active_model and hasattr(self.env[active_model], '_get_default_sms_recipients'):
+ model = self.env[active_model]
+ records = self._get_records(model)
partners = records._get_default_sms_recipients()
phone_numbers = []
no_phone_partners = []
@@ -83,12 +82,12 @@ class SendSMS(models.TransientModel):
return result
def action_send_sms(self):
- numbers = self.recipients.split(',')
+ numbers = [number.strip() for number in self.recipients.split(',') if number.strip()]
active_model = self.env.context.get('active_model')
- model = self.env[active_model]
- records = self._get_records(model)
- if getattr(records, 'message_post_send_sms'):
+ if active_model and hasattr(self.env[active_model], 'message_post_send_sms'):
+ model = self.env[active_model]
+ records = self._get_records(model)
records.message_post_send_sms(self.message, numbers=numbers)
else:
self.env['sms.api']._send_sms(numbers, self.message)
diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py
index 1dcd88e5cad..82301ecbdfc 100644
--- a/addons/stock/models/product.py
+++ b/addons/stock/models/product.py
@@ -436,6 +436,10 @@ class ProductTemplate(models.Model):
def _is_cost_method_standard(self):
return True
+ @api.depends(
+ 'product_variant_ids',
+ 'product_variant_ids.stock_quant_ids',
+ )
def _compute_quantities(self):
res = self._compute_quantities_dict()
for template in self:
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index f530d81cfcc..1eb1a6a3107 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -465,6 +465,7 @@ class StockMove(models.Model):
else:
view = self.env.ref('stock.view_stock_move_nosuggest_operations')
+ picking_type_id = self.picking_type_id or self.picking_id.picking_type_id
return {
'name': _('Detailed Operations'),
'type': 'ir.actions.act_window',
@@ -477,8 +478,8 @@ class StockMove(models.Model):
'res_id': self.id,
'context': dict(
self.env.context,
- show_lots_m2o=self.has_tracking != 'none' and (self.picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
- show_lots_text=self.has_tracking != 'none' and self.picking_type_id.use_create_lots and not self.picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
+ show_lots_m2o=self.has_tracking != 'none' and (picking_type_id.use_existing_lots or self.state == 'done' or self.origin_returned_move_id.id), # able to create lots, whatever the value of ` use_create_lots`.
+ show_lots_text=self.has_tracking != 'none' and picking_type_id.use_create_lots and not picking_type_id.use_existing_lots and self.state != 'done' and not self.origin_returned_move_id.id,
show_source_location=self.location_id.child_ids,
show_destination_location=self.location_dest_id.child_ids,
show_package=not self.location_id.usage == 'supplier',
diff --git a/addons/stock/wizard/stock_picking_return_views.xml b/addons/stock/wizard/stock_picking_return_views.xml
index cc4dd2de9a3..9339c14f5df 100644
--- a/addons/stock/wizard/stock_picking_return_views.xml
+++ b/addons/stock/wizard/stock_picking_return_views.xml
@@ -27,7 +27,7 @@
<group>
<field name="parent_location_id" invisible="1"/>
<field name="original_location_id" invisible="1"/>
- <field name="location_id" options="{'no_create': True, 'no_open': True}" groups="stock.group_stock_multi_locations"/>
+ <field name="location_id" options="{'no_create': True, 'no_open': True}" groups="stock.group_stock_multi_locations" required="1"/>
</group>
<footer>
<button name="create_returns" string="Return" type="object" class="btn-primary"/>
diff --git a/addons/test_mail/tests/test_mail_template.py b/addons/test_mail/tests/test_mail_template.py
index 388e669b730..55ddbb7b667 100644
--- a/addons/test_mail/tests/test_mail_template.py
+++ b/addons/test_mail/tests/test_mail_template.py
@@ -70,6 +70,46 @@ class TestMailTemplate(BaseFunctionalTest, MockEmails, TestRecipients):
body_content=self.test_record.email_from,
attachments=[('first.txt', b'My first attachment', 'text/plain'), ('second.txt', b'My second attachment', 'text/plain')])
+ def test_composer_template_onchange_attachments(self):
+ """Tests that all attachments are added to the composer,
+ static attachments are not duplicated and while reports are re-generated,
+ and that intermediary attachments are dropped."""
+
+ composer = self.env['mail.compose.message'].create({})
+ report_template = self.env.ref('web.action_report_externalpreview')
+ template_1 = self.email_template.copy({
+ 'report_template': report_template.id,
+ })
+ template_2 = self.email_template.copy({
+ 'attachment_ids': False,
+ 'report_template': report_template.id,
+ })
+
+ onchange_templates = [template_1, template_2, template_1]
+ attachments_onchange = []
+ # template_1 has two static attachments and one dynamically generated report,
+ # template_2 only has the report, so we should get 3, 1, 3 attachments
+ attachment_numbers = [3, 1, 3]
+
+ for template in onchange_templates:
+ onchange = composer.onchange_template_id(
+ template.id, 'comment', 'mail.test', self.test_record.id
+ )
+ values = composer._convert_to_record(composer._convert_to_cache(onchange['value']))
+ attachments = values['attachment_ids']
+ composer.attachment_ids = attachments # we apply the onchange
+ attachments_onchange.append(attachments)
+
+ self.assertEqual(
+ [len(attachments) for attachments in attachments_onchange],
+ attachment_numbers,
+ )
+
+ self.assertTrue(
+ len(attachments_onchange[0] & attachments_onchange[2]) == 2,
+ "The two static attachments on the template should be common to the two onchanges"
+ )
+
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_post_post_w_template(self):
self.test_record.sudo(self.user_employee).message_post_with_template(self.email_template.id, composition_mode='comment')
diff --git a/addons/web/static/src/js/core/ajax.js b/addons/web/static/src/js/core/ajax.js
index f1f73bb5a28..cbd02e0755e 100644
--- a/addons/web/static/src/js/core/ajax.js
+++ b/addons/web/static/src/js/core/ajax.js
@@ -22,7 +22,13 @@ function genericJsonRpc (fct_name, params, settings, fct) {
core.bus.trigger('rpc:result', data, result);
if (result.error !== undefined) {
if (result.error.data.arguments[0] !== "bus.Bus not available in test mode") {
- console.error("Server application error", JSON.stringify(result.error));
+ console.error(
+ "Server application error\n",
+ "Error code:", result.error.code, "\n",
+ "Error message:", result.error.message, "\n",
+ "Error data message:\n", result.error.data.message, "\n",
+ "Error data debug:\n", result.error.data.debug
+ );
}
return $.Deferred().reject("server", result.error);
} else {
diff --git a/addons/web/static/src/js/views/calendar/calendar_controller.js b/addons/web/static/src/js/views/calendar/calendar_controller.js
index d49c7c33f5c..4525a739df9 100644
--- a/addons/web/static/src/js/views/calendar/calendar_controller.js
+++ b/addons/web/static/src/js/views/calendar/calendar_controller.js
@@ -156,7 +156,7 @@ var CalendarController = AbstractController.extend({
* @returns {Deferred}
*/
_updateRecord: function (record) {
- return this.model.updateRecord(record).then(this.reload.bind(this));
+ return this.model.updateRecord(record).always(this.reload.bind(this));
},
//--------------------------------------------------------------------------
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 459f3db56ff..96a13c90b11 100644
--- a/addons/web/static/src/js/views/list/list_controller.js
+++ b/addons/web/static/src/js/views/list/list_controller.js
@@ -361,8 +361,9 @@ var ListController = BasicController.extend({
* @returns {Deferred}
*/
_update: function () {
- this._toggleSidebar();
- return this._super.apply(this, arguments);
+ return this._super.apply(this, arguments)
+ .then(this._toggleSidebar.bind(this))
+ .then(this._updateButtons.bind(this, 'readonly'));
},
/**
* This helper simply makes sure that the control panel buttons matches the
diff --git a/addons/web/static/src/less/form_view_extra.less b/addons/web/static/src/less/form_view_extra.less
index 61967ea96fe..465ef8e37ea 100644
--- a/addons/web/static/src/less/form_view_extra.less
+++ b/addons/web/static/src/less/form_view_extra.less
@@ -132,7 +132,7 @@
border-right: 1px solid #ddd;
}
.o_td_label + td {
- padding: 2px 36px 2px 8px;
+ padding: 0px 36px 0px 8px;
}
.o_field_widget.o_text_overflow {
width: 1px!important; // hack to make the table layout believe it is a small element (so that the table does not grow too much) ...
diff --git a/addons/website_quote/models/sale_order.py b/addons/website_quote/models/sale_order.py
index 40add3901c7..e5e24874615 100644
--- a/addons/website_quote/models/sale_order.py
+++ b/addons/website_quote/models/sale_order.py
@@ -120,7 +120,7 @@ class SaleOrder(models.Model):
for line in template.quote_line:
discount = 0
if self.pricelist_id:
- price = self.pricelist_id.with_context(uom=line.product_uom_id.id).get_product_price(line.product_id, 1, False)
+ price = self.pricelist_id.with_context(uom=line.product_uom_id.id).get_product_price(line.product_id, line.product_uom_qty, False)
if self.pricelist_id.discount_policy == 'without_discount' and line.price_unit:
discount = (line.price_unit - price) / line.price_unit * 100
price = line.price_unit
diff --git a/doc/cla/corporate/acsone.md b/doc/cla/corporate/acsone.md
index d4ee1947927..25b0e1b1c8a 100644
--- a/doc/cla/corporate/acsone.md
+++ b/doc/cla/corporate/acsone.md
@@ -27,3 +27,4 @@ Thomas Binsfeld thomas.binsfeld@acsone.eu https://github.com/ThomasBinsfeld
Benjamin Willig benjamin.willig@acsone.eu https://github.com/benwillig
François Honoré francois.honore@acsone.eu https://github.com/acsonefho
Denis Robinet denis.robinet@acsone.eu https://github.com/RobinetDenisAcsone
+Benoit Aimont benoit.aimont@acsone.eu https://github.com/baimont
diff --git a/doc/cla/corporate/vauxoo.md b/doc/cla/corporate/vauxoo.md
index 512104ee2a6..4cf99d78176 100644
--- a/doc/cla/corporate/vauxoo.md
+++ b/doc/cla/corporate/vauxoo.md
@@ -44,3 +44,4 @@ Alexander Olivares alexander@vauxoo.com https://github.com/alxolivares
Jose Manuel Robles josemanuel@vauxoo.com https://github.com/keylor2906
Erick Birbe erick@vauxoo.com https://github.com/ebirbe
Tomas Alvarez tomas@vauxoo.com https://github.com/tomeyro
+Carmen Liliana Miranda González carmen@vauxoo.com https://github.com/CarmenMiranda
diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py
index d778f8166dc..5c568f873c6 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -249,11 +249,12 @@ class TestFields(common.TransactionCase):
self.assertEqual(c.display_name, 'B / C')
self.assertEqual(d.display_name, 'B / C / D')
- b.name = 'X'
+ # rename several records to trigger several recomputations at once
+ (d + c + b).write({'name': 'X'})
self.assertEqual(a.display_name, 'A')
self.assertEqual(b.display_name, 'X')
- self.assertEqual(c.display_name, 'X / C')
- self.assertEqual(d.display_name, 'X / C / D')
+ self.assertEqual(c.display_name, 'X / X')
+ self.assertEqual(d.display_name, 'X / X / X')
# delete b; both c and d are deleted in cascade; c should also be marked
# to recompute, but recomputation should not fail...
diff --git a/odoo/fields.py b/odoo/fields.py
index f74f9e53a8e..70466bf3c61 100644
--- a/odoo/fields.py
+++ b/odoo/fields.py
@@ -1053,6 +1053,8 @@ class Field(MetaField('DummyField', (object,), {})):
recs = record._recompute_check(self)
if recs:
# recompute the value (only in cache)
+ if self.recursive:
+ recs = record
self.compute_value(recs)
# HACK: if result is in the wrong cache, copy values
if recs.env != env:
diff --git a/odoo/models.py b/odoo/models.py
index c9d89c175ed..a6cf0357ec5 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -5048,6 +5048,19 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
if subnames else record[name]
)
+ def has_changed(self, name):
+ """ Return whether a field on record has changed. """
+ record = self['<record>']
+ subnames = self['<tree>'][name]
+ if not subnames:
+ return self[name] != record[name]
+ else:
+ return len(self[name]) != len(record[name]) or any(
+ line_snapshot.has_changed(subname)
+ for line_snapshot in self[name]
+ for subname in subnames
+ )
+
def diff(self, other):
""" Return the values in ``self`` that differ from ``other``.
Requires record cache invalidation for correct output!
@@ -5125,25 +5138,24 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
result = {}
- # process names in order (or the keys of values if no name given)
- while todo:
- name = todo.pop(0)
- if name in done:
- continue
- done.add(name)
-
- with env.do_in_onchange():
+ # process names in order
+ with env.do_in_onchange():
+ while todo:
# apply field-specific onchange methods
- if field_onchange.get(name):
- record._onchange_eval(name, field_onchange[name], result)
-
- # make a snapshot (this forces evaluation of computed fields)
- snapshot1 = Snapshot(record, nametree)
+ for name in todo:
+ if field_onchange.get(name):
+ record._onchange_eval(name, field_onchange[name], result)
+ done.add(name)
+
+ # determine which fields to process for the next pass
+ todo = [
+ name
+ for name in nametree
+ if name not in done and snapshot0.has_changed(name)
+ ]
- # determine which fields have been modified
- for name in nametree:
- if snapshot1[name] != snapshot0[name]:
- todo.append(name)
+ # make the snapshot with the final values of record
+ snapshot1 = Snapshot(record, nametree)
# determine values that have changed by comparing snapshots
self.invalidate_cache()
diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py
index cfa4cac05a3..424117c98a7 100644
--- a/odoo/tools/translate.py
+++ b/odoo/tools/translate.py
@@ -957,8 +957,11 @@ def trans_generate(lang, modules, cr):
extra_comments = extra_comments or []
if not module: return
src_file = open(fabsolutepath, 'rb')
+ options = {}
+ if extract_method == 'python':
+ options['encoding'] = 'UTF-8'
try:
- for extracted in extract.extract(extract_method, src_file, keywords=extract_keywords):
+ for extracted in extract.extract(extract_method, src_file, keywords=extract_keywords, options=options):
# Babel 0.9.6 yields lineno, message, comments
# Babel 1.3 yields lineno, message, comments, context
lineno, message, comments = extracted[:3]