summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-11-30 15:27:59 +0100
committerChristophe Simonis <chs@odoo.com>2017-11-30 15:27:59 +0100
commitab084d580ff0edf9be1d12cec87aeffdd84459b7 (patch)
tree6a19b4656315d94b65c88507bb471ac56d16f3ed
parent3f66814a0d01a81bef5456a07da4b1ac8e2281e7 (diff)
parent98539336a5d40de748764ff0a3c16d8cda77b02f (diff)
[MERGE] forward port branch saas-15 up to 98539336a5d
-rw-r--r--addons/account/models/account.py2
-rw-r--r--addons/account/models/account_journal_dashboard.py15
-rw-r--r--addons/account/models/chart_template.py3
-rw-r--r--addons/account/models/partner.py17
-rw-r--r--addons/event_sale/models/sale_order.py2
-rw-r--r--addons/hr_expense/models/hr_expense.py9
-rw-r--r--addons/hr_expense/views/hr_expense_views.xml7
-rw-r--r--addons/l10n_ch/models/res_bank.py3
-rw-r--r--addons/l10n_fr_certification/models/account.py5
-rw-r--r--addons/mail/models/mail_thread.py4
-rw-r--r--addons/mail/models/res_users.py4
-rw-r--r--addons/mail/security/ir.model.access.csv2
-rw-r--r--addons/mail/tests/test_mail_gateway.py17
-rw-r--r--addons/mrp_repair/report/mrp_repair_templates_repair_order.xml4
-rw-r--r--addons/payment_authorize/models/authorize_request.py54
-rw-r--r--addons/payment_authorize/tests/test_authorize.py19
-rw-r--r--addons/point_of_sale/static/src/js/models.js2
-rw-r--r--addons/point_of_sale/static/src/js/screens.js20
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh6
-rwxr-xr-xaddons/point_of_sale/tools/posbox/posbox_download_images.sh2
-rw-r--r--addons/pos_discount/static/src/js/discount.js5
-rw-r--r--addons/purchase/security/purchase_security.xml7
-rw-r--r--addons/report/static/src/less/layout_clean.less3
-rw-r--r--addons/stock/models/stock_picking.py2
-rw-r--r--addons/stock/wizard/stock_immediate_transfer.py2
-rw-r--r--addons/stock_account/models/stock.py5
-rw-r--r--addons/web/static/src/js/boot.js31
-rw-r--r--addons/web_editor/static/src/js/backend.js4
-rw-r--r--addons/web_editor/static/src/js/widgets.js4
-rw-r--r--addons/website/controllers/main.py2
-rw-r--r--addons/website/data/website_data.xml4
-rw-r--r--addons/website/models/res_partner.py2
-rw-r--r--addons/website/static/src/js/website.snippets.animation.js6
-rw-r--r--addons/website_blog/views/website_blog_templates.xml1
-rw-r--r--addons/website_event/controllers/main.py2
-rw-r--r--addons/website_event/views/event_templates.xml2
-rw-r--r--addons/website_portal/views/website_portal_templates.xml3
-rw-r--r--addons/website_portal_sale/models/payment.py16
-rw-r--r--addons/website_sale/controllers/main.py5
-rw-r--r--addons/website_sale/models/sale_order.py11
-rw-r--r--addons/website_sale/static/src/js/website_sale.js9
-rw-r--r--addons/website_sale/static/src/js/website_sale_validate.js9
-rw-r--r--addons/website_sale/views/templates.xml2
-rw-r--r--addons/website_sale_options/controllers/main.py4
-rw-r--r--doc/cla/corporate/it-projects-llc.md20
-rw-r--r--doc/cla/individual/adekock11.md11
-rw-r--r--doc/cla/individual/elgow.md11
-rw-r--r--doc/cla/individual/flyingL123.md11
-rw-r--r--odoo/addons/base/ir/ir_translation.py10
-rw-r--r--odoo/addons/base/ir/ir_translation_view.xml1
-rw-r--r--odoo/addons/base/res/res_country_data.xml1
-rw-r--r--odoo/addons/base/res/res_currency_data.xml1
-rw-r--r--odoo/addons/test_impex/tests/test_export.py43
-rw-r--r--odoo/models.py16
-rw-r--r--requirements.txt2
55 files changed, 361 insertions, 104 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index cde4781888e..0fdb8213aab 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -525,7 +525,7 @@ class AccountTaxGroup(models.Model):
class AccountTax(models.Model):
_name = 'account.tax'
_description = 'Tax'
- _order = 'sequence'
+ _order = 'sequence,id'
@api.model
def _default_tax_group(self):
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index 54b05af4676..70710f65191 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -206,18 +206,19 @@ class account_journal(models.Model):
number_late += 1
sum_late += cur.compute(result.get('amount_total'), currency) * factor
+ difference = currency.round(last_balance-account_sum) + 0.0
return {
'number_to_reconcile': number_to_reconcile,
- 'account_balance': formatLang(self.env, account_sum, currency_obj=self.currency_id or self.company_id.currency_id),
- 'last_balance': formatLang(self.env, last_balance, currency_obj=self.currency_id or self.company_id.currency_id),
- 'difference': (last_balance-account_sum) and formatLang(self.env, last_balance-account_sum, currency_obj=self.currency_id or self.company_id.currency_id) or False,
+ 'account_balance': formatLang(self.env, currency.round(account_sum) + 0.0, currency_obj=currency),
+ 'last_balance': formatLang(self.env, currency.round(last_balance) + 0.0, currency_obj=currency),
+ 'difference': formatLang(self.env, difference, currency_obj=currency) if difference else False,
'number_draft': number_draft,
'number_waiting': number_waiting,
'number_late': number_late,
- 'sum_draft': formatLang(self.env, sum_draft or 0.0, currency_obj=self.currency_id or self.company_id.currency_id),
- 'sum_waiting': formatLang(self.env, sum_waiting or 0.0, currency_obj=self.currency_id or self.company_id.currency_id),
- 'sum_late': formatLang(self.env, sum_late or 0.0, currency_obj=self.currency_id or self.company_id.currency_id),
- 'currency_id': self.currency_id and self.currency_id.id or self.company_id.currency_id.id,
+ 'sum_draft': formatLang(self.env, currency.round(sum_draft) + 0.0, currency_obj=currency),
+ 'sum_waiting': formatLang(self.env, currency.round(sum_waiting) + 0.0, currency_obj=currency),
+ 'sum_late': formatLang(self.env, currency.round(sum_late) + 0.0, currency_obj=currency),
+ 'currency_id': currency.id,
'bank_statements_source': self.bank_statements_source,
'title': title,
}
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 4965b28e022..da20b325a3b 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -38,8 +38,7 @@ def migrate_tags_on_taxes(cr, registry):
('type_tax_use', '=', tax_template.type_tax_use),
('description', '=', tax_template.description)
])
- if len(tax_id.ids) == 1:
- tax_id.sudo().write({'tag_ids': [(6, 0, tax_template.tag_ids.ids)]})
+ tax_id.sudo().write({'tag_ids': [(6, 0, tax_template.tag_ids.ids)]})
def preserve_existing_tags_on_taxes(cr, registry, module):
''' This is a utility function used to preserve existing previous tags during upgrade of the module.'''
diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py
index 9100dba6d19..a1042ff28a6 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -297,10 +297,16 @@ class ResPartner(models.Model):
partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids)
@api.multi
- def _journal_item_count(self):
+ def _compute_journal_item_count(self):
+ AccountMoveLine = self.env['account.move.line']
for partner in self:
- partner.journal_item_count = self.env['account.move.line'].search_count([('partner_id', '=', partner.id)])
- partner.contracts_count = self.env['account.analytic.account'].search_count([('partner_id', '=', partner.id)])
+ partner.journal_item_count = AccountMoveLine.search_count([('partner_id', '=', partner.id)])
+
+ @api.multi
+ def _compute_contracts_count(self):
+ AccountAnalyticAccount = self.env['account.analytic.account']
+ for partner in self:
+ partner.contracts_count = AccountAnalyticAccount.search_count([('partner_id', '=', partner.id)])
def get_followup_lines_domain(self, date, overdue_only=False, only_unblocked=False):
domain = [('reconciled', '=', False), ('account_id.deprecated', '=', False), ('account_id.internal_type', '=', 'receivable'), '|', ('debit', '!=', 0), ('credit', '!=', 0), ('company_id', '=', self.env.user.company_id.id)]
@@ -374,9 +380,8 @@ class ResPartner(models.Model):
groups='account.group_account_invoice')
currency_id = fields.Many2one('res.currency', compute='_get_company_currency', readonly=True,
string="Currency", help='Utility field to express amount currency')
-
- contracts_count = fields.Integer(compute='_journal_item_count', string="Contracts", type='integer')
- journal_item_count = fields.Integer(compute='_journal_item_count', string="Journal Items", type="integer")
+ contracts_count = fields.Integer(compute='_compute_contracts_count', string="Contracts", type='integer')
+ journal_item_count = fields.Integer(compute='_compute_journal_item_count', string="Journal Items", type="integer")
property_account_payable_id = fields.Many2one('account.account', company_dependent=True,
string="Account Payable", oldname="property_account_payable",
domain="[('internal_type', '=', 'payable'), ('deprecated', '=', False)]",
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index 44801880267..7a2dcd8a745 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -42,7 +42,7 @@ class SaleOrderLine(models.Model):
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
Registration = self.env['event.registration']
- registrations = Registration.search([('sale_order_line_id', 'in', self.ids)])
+ registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
if confirm:
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index c422e613d8d..194408a9118 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -576,4 +576,11 @@ class HrExpenseSheet(models.Model):
def _check_employee(self):
employee_ids = self.expense_line_ids.mapped('employee_id')
if len(employee_ids) > 1 or (len(employee_ids) == 1 and employee_ids != self.employee_id):
- raise ValidationError(_('You cannot add expense lines of another employee.')) \ No newline at end of file
+ raise ValidationError(_('You cannot add expense lines of another employee.'))
+
+ @api.one
+ @api.constrains('expense_line_ids')
+ def _check_payment_mode(self):
+ payment_mode = set(self.expense_line_ids.mapped('payment_mode'))
+ if len(payment_mode) > 1:
+ raise ValidationError(_('You cannot report expenses with different payment modes.')) \ No newline at end of file
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index a8f4f045661..83ed8a014a6 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -465,9 +465,14 @@
<field name="attachment_number" string=" "/>
<button name="action_get_attachment_view" string="View Attachments" type="object" icon="fa-paperclip"/>
<field name="tax_ids" widget="many2many_tags"/>
- <field name="total_amount" sum="Total Amount" widget="monetary"/>
+ <field name="total_amount" widget="monetary"/>
</tree>
</field>
+ <group>
+ <group class="oe_subtotal_footer oe_right" colspan="2" name="expense_total">
+ <field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>
+ </group>
+ </group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
diff --git a/addons/l10n_ch/models/res_bank.py b/addons/l10n_ch/models/res_bank.py
index 0ddc98172a3..28abbc7b058 100644
--- a/addons/l10n_ch/models/res_bank.py
+++ b/addons/l10n_ch/models/res_bank.py
@@ -46,7 +46,8 @@ class ResPartnerBank(models.Model):
for record in self:
if record.acc_type == 'iban':
record.l10n_ch_postal = record._retrieve_l10n_ch_postal(record.sanitized_acc_number)
- record.l10n_ch_postal = record.sanitized_acc_number
+ else:
+ record.l10n_ch_postal = record.sanitized_acc_number
def _retrieve_l10n_ch_postal(self, iban):
""" Reads a swiss postal account number from a an IBAN and returns it as
diff --git a/addons/l10n_fr_certification/models/account.py b/addons/l10n_fr_certification/models/account.py
index 07d5fc04e41..94db3c46273 100644
--- a/addons/l10n_fr_certification/models/account.py
+++ b/addons/l10n_fr_certification/models/account.py
@@ -144,8 +144,9 @@ class AccountJournal(models.Model):
@api.multi
def write(self, vals):
# restrict the operation in case we are trying to write a forbidden field
- if self.company_id.country_id.code == 'FR' and vals.get('update_posted'):
- raise UserError(ERR_MSG % (self._name, 'update_posted'))
+ for rec in self:
+ if rec.company_id.country_id.code == 'FR' and vals.get('update_posted'):
+ raise UserError(ERR_MSG % (self._name, 'update_posted'))
return super(AccountJournal, self).write(vals)
@api.model
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 7a0ec8a65d0..b5aa6616794 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -1365,6 +1365,8 @@ class MailThread(models.AbstractModel):
mail module, and should not contain security or generic html cleaning.
Indeed those aspects should be covered by the html_sanitize method
located in tools. """
+ if not body:
+ return body, attachments
root = lxml.html.fromstring(body)
postprocessed = False
to_remove = []
@@ -1399,7 +1401,7 @@ class MailThread(models.AbstractModel):
# Content-Type: multipart/related;
# boundary="_004_3f1e4da175f349248b8d43cdeb9866f1AMSPR06MB343eurprd06pro_";
# type="text/html"
- if not message.is_multipart() or message.get('content-type', '').startswith("text/"):
+ if message.get_content_maintype() == 'text':
encoding = message.get_content_charset()
body = message.get_payload(decode=True)
body = tools.ustr(body, encoding, errors='replace')
diff --git a/addons/mail/models/res_users.py b/addons/mail/models/res_users.py
index 059e0f521ad..ff9a3abba1c 100644
--- a/addons/mail/models/res_users.py
+++ b/addons/mail/models/res_users.py
@@ -3,6 +3,7 @@
from odoo import _, api, exceptions, fields, models
from odoo.tools import pycompat
+from odoo.addons.base.res.res_users import is_selection_groups
class Users(models.Model):
@@ -62,11 +63,14 @@ class Users(models.Model):
@api.multi
def write(self, vals):
write_res = super(Users, self).write(vals)
+ sel_groups = [vals[k] for k in vals if is_selection_groups(k) and vals[k]]
if vals.get('groups_id'):
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
user_group_ids = [command[1] for command in vals['groups_id'] if command[0] == 4]
user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
self.env['mail.channel'].search([('group_ids', 'in', user_group_ids)])._subscribe_users()
+ elif sel_groups:
+ self.env['mail.channel'].search([('group_ids', 'in', sel_groups)])._subscribe_users()
return write_res
def _create_welcome_message(self):
diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv
index 5040f812d73..1b14cacee0e 100644
--- a/addons/mail/security/ir.model.access.csv
+++ b/addons/mail/security/ir.model.access.csv
@@ -22,7 +22,7 @@ access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,1
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
-access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,,1,1,1,1
+access_mail_message_subtype_user,mail.message.subtype.user,model_mail_message_subtype,base.group_user,1,1,1,1
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,0,0,0,0
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,0,0,0,0
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,0,0,0,0
diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py
index 2a356d9ed13..48f32099592 100644
--- a/addons/mail/tests/test_mail_gateway.py
+++ b/addons/mail/tests/test_mail_gateway.py
@@ -176,6 +176,19 @@ Content-Type: text/html;
--Apple-Mail=_9331E12B-8BD2-4EC7-B53E-01F3FBEC9227--
"""
+MAIL_SINGLE_BINARY = """X-Original-To: raoul@grosbedon.fr
+Delivered-To: raoul@grosbedon.fr
+Received: by mail1.grosbedon.com (Postfix, from userid 10002)
+ id E8166BFACA; Fri, 23 Aug 2013 13:18:01 +0200 (CEST)
+From: "Bruce Wayne" <bruce@wayneenterprises.com>
+Content-Type: application/pdf;
+Content-Disposition: filename=thetruth.pdf
+Content-Transfer-Encoding: base64
+Message-Id: <6BB1FAB2-2104-438E-9447-07AE2C8C4A92@sexample.com>
+Mime-Version: 1.0 (Mac OS X Mail 7.3 \(1878.6\))
+
+SSBhbSB0aGUgQmF0TWFuCg=="""
+
MAIL_MULTIPART_IMAGE = """X-Original-To: raoul@example.com
Delivered-To: micheline@example.com
@@ -349,6 +362,10 @@ class TestMailgateway(TestMail):
res.get('body', ''),
'message_parse: second part of the html version should be in body after parsing multipart/mixed')
+ res = self.env['mail.thread'].message_parse(MAIL_SINGLE_BINARY)
+ self.assertEqual(res['body'], '')
+ self.assertEqual(res['attachments'][0][0], 'thetruth.pdf')
+
@mute_logger('odoo.addons.mail.models.mail_thread')
def test_message_process_cid(self):
new_groups = self.format_and_process(MAIL_MULTIPART_IMAGE, subject='My Frogs', to='groups@example.com')
diff --git a/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml b/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml
index 433df24c6e1..37ac502fd3b 100644
--- a/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml
+++ b/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml
@@ -15,13 +15,13 @@
<strong t-if="o.address_id != o.partner_invoice_id">Invoice address: </strong>
<div t-field="o.partner_invoice_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+ <p t-if="o.partner_invoice_id.vat">VAT: <span t-field="o.partner_invoice_id.vat"/></p>
</div>
<div t-if="o.address_id != o.partner_invoice_id">
<strong>Shipping address :</strong>
<div t-field="o.address_id"
t-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": True, "phone_icons": True}'/>
- <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
+ <p t-if="o.address_id.vat">VAT: <span t-field="o.address_id.vat"/></p>
</div>
</div>
<div class="col-xs-5 col-xs-offset-1">
diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py
index 1ae0e71b6ed..f7aca95511d 100644
--- a/addons/payment_authorize/models/authorize_request.py
+++ b/addons/payment_authorize/models/authorize_request.py
@@ -29,6 +29,34 @@ def strip_ns(xml, ns):
return it.root
+def error_check(elem):
+ """Check if the response sent by Authorize.net contains an error.
+
+ Errors can be a failure to try the transaction (in that case, the transasctionResponse
+ is empty, and the meaningful error message will be in message/code) or a failure to process
+ the transaction (in that case, the message/code content will be generic and the actual error
+ message is in transactionResponse/errors/error/errorText).
+
+ :param etree._Element elem: the root element of the response that will be parsed
+
+ :rtype: tuple (bool, str)
+ :return: tuple containnig a boolean indicating if the response should be considered
+ as an error and the most meaningful error message found in it.
+ """
+ result_code = elem.find('messages/resultCode')
+ msg = 'No meaningful error message found, please check logs or the Authorize.net backend'
+ has_error = result_code is not None and result_code.text == 'Error'
+ if has_error:
+ # accumulate the most meangingful error
+ error = elem.find('transactionResponse/errors/error')
+ error = error if error is not None else elem.find('messages/message')
+ if error is not None:
+ code = error[0].text
+ text = error[1].text
+ msg = '%s: %s' % (code, text)
+ return (has_error, msg)
+
+
class AuthorizeAPI():
"""Authorize.net Gateway API integration.
@@ -38,6 +66,9 @@ class AuthorizeAPI():
- Customer Profile/Payment Profile creation
- Transaction authorization/capture/voiding
"""
+
+ AUTH_ERROR_STATUS = 3
+
def __init__(self, acquirer):
"""Initiate the environment with the acquirer data.
@@ -63,9 +94,6 @@ class AuthorizeAPI():
request.add_header('Content-Type', 'text/xml')
response = urlopen(request).read()
response = strip_ns(response, XMLNS)
- if response.find('messages/resultCode').text == 'Error':
- messages = map(lambda m: m.text, response.findall('messages/message/text'))
- raise ValidationError(_('Authorize.net Error Message(s):\n %s') % '\n'.join(messages))
return response
def _base_tree(self, requestType):
@@ -190,6 +218,11 @@ class AuthorizeAPI():
etree.SubElement(order, "invoiceNumber").text = reference
response = self._authorize_request(root)
res = dict()
+ (has_error, error_msg) = error_check(response)
+ if has_error:
+ res['x_response_code'] = self.AUTH_ERROR_STATUS
+ res['x_response_reason_text'] = error_msg
+ return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'auth_capture'
@@ -220,6 +253,11 @@ class AuthorizeAPI():
etree.SubElement(order, "invoiceNumber").text = reference
response = self._authorize_request(root)
res = dict()
+ (has_error, error_msg) = error_check(response)
+ if has_error:
+ res['x_response_code'] = self.AUTH_ERROR_STATUS
+ res['x_response_reason_text'] = error_msg
+ return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'auth_only'
@@ -245,6 +283,11 @@ class AuthorizeAPI():
etree.SubElement(tx, "refTransId").text = transaction_id
response = self._authorize_request(root)
res = dict()
+ (has_error, error_msg) = error_check(response)
+ if has_error:
+ res['x_response_code'] = self.AUTH_ERROR_STATUS
+ res['x_response_reason_text'] = error_msg
+ return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'prior_auth_capture'
@@ -265,6 +308,11 @@ class AuthorizeAPI():
etree.SubElement(tx, "refTransId").text = transaction_id
response = self._authorize_request(root)
res = dict()
+ (has_error, error_msg) = error_check(response)
+ if has_error:
+ res['x_response_code'] = self.AUTH_ERROR_STATUS
+ res['x_response_reason_text'] = error_msg
+ return res
res['x_response_code'] = response.find('transactionResponse/responseCode').text
res['x_trans_id'] = response.find('transactionResponse/transId').text
res['x_type'] = 'void'
diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py
index 50e4e95fdc4..35be8a99b17 100644
--- a/addons/payment_authorize/tests/test_authorize.py
+++ b/addons/payment_authorize/tests/test_authorize.py
@@ -209,7 +209,7 @@ class AuthorizeForm(AuthorizeCommon):
'acquirer_id': authorize.id,
'type': 'server2server',
'currency_id': self.currency_usd.id,
- 'reference': 'test_ref_%s' % odoo.fields.Date.today(),
+ 'reference': 'test_ref_%s' % int(time.time()),
'payment_token_id': payment_token.id,
'partner_id': self.buyer_id,
@@ -251,3 +251,20 @@ class AuthorizeForm(AuthorizeCommon):
self.assertEqual(transaction.state, 'authorized')
transaction.action_void()
self.assertEqual(transaction.state, 'cancel')
+
+ # try charging an unexisting profile
+ ghost_payment_token = payment_token.copy()
+ ghost_payment_token.authorize_profile = '99999999999'
+ # create normal s2s transaction
+ transaction = self.env['payment.transaction'].create({
+ 'amount': 500,
+ 'acquirer_id': authorize.id,
+ 'type': 'server2server',
+ 'currency_id': self.currency_usd.id,
+ 'reference': 'test_ref_%s' % int(time.time()),
+ 'payment_token_id': ghost_payment_token.id,
+ 'partner_id': self.buyer_id,
+
+ })
+ transaction.authorize_s2s_do_transaction()
+ self.assertEqual(transaction.state, 'error')
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 909a22096ca..d4aaf40f664 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -519,7 +519,7 @@ exports.PosModel = Backbone.Model.extend({
.then(function(){ load_model(index + 1); },
function(err){ loaded.reject(err); });
}catch(err){
- console.error(err.stack);
+ console.error(err.message, err.stack);
loaded.reject(err);
}
},function(err){
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index be7ebc43243..8cf2f5d9a7d 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -73,12 +73,21 @@ var ScreenWidget = PosBaseWidget.extend({
// - if there's a user with a matching barcode, put it as the active 'cashier', go to cashier mode, and return true
// - else : do nothing and return false. You probably want to extend this to show and appropriate error popup...
barcode_cashier_action: function(code){
+ var self = this;
var users = this.pos.users;
for(var i = 0, len = users.length; i < len; i++){
if(users[i].barcode === code.code){
- this.pos.set_cashier(users[i]);
- this.chrome.widget.username.renderElement();
- return true;
+ if (users[i].id !== this.pos.get_cashier().id && users[i].pos_security_pin) {
+ return this.gui.ask_password(users[i].pos_security_pin).then(function(){
+ self.pos.set_cashier(users[i]);
+ self.chrome.widget.username.renderElement();
+ return true;
+ });
+ } else {
+ this.pos.set_cashier(users[i]);
+ this.chrome.widget.username.renderElement();
+ return true;
+ }
}
}
this.barcode_error_action(code);
@@ -1045,10 +1054,10 @@ var ClientListScreenWidget = ScreenWidget.extend({
this.$('.searchbox input').on('keypress',function(event){
clearTimeout(search_timeout);
- var query = this.value;
+ var searchbox = this;
search_timeout = setTimeout(function(){
- self.perform_search(query,event.which === 13);
+ self.perform_search(searchbox.value, event.which === 13);
},70);
});
@@ -2090,4 +2099,3 @@ return {
};
});
-
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
index 00058bef68c..6856f9b4d06 100755
--- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh
@@ -17,8 +17,7 @@ export DEBIAN_FRONTEND=noninteractive
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
-apt-get update
-apt-get -y dist-upgrade
+apt-get update && apt-get -y upgrade
# Do not be too fast to upgrade to more recent firmware and kernel than 4.38
# Firmware 4.44 seems to prevent the LED mechanism from working
@@ -39,6 +38,8 @@ rm -rf /usr/share/doc
pip install pyusb==1.0.0b1
pip install qrcode
pip install evdev
+pip install simplejson
+pip install unittest2
# --upgrade because websocket_client in wheezy is bad:
# https://github.com/docker/compose/issues/1288
@@ -53,7 +54,6 @@ sudo -u postgres createuser -s pi
mkdir /var/log/odoo
chown pi:pi /var/log/odoo
chown pi:pi -R /home/pi/odoo/
-chmod 770 -R /home/pi/odoo/
# logrotate is very picky when it comes to file permissions
chown -R root:root /etc/logrotate.d/
diff --git a/addons/point_of_sale/tools/posbox/posbox_download_images.sh b/addons/point_of_sale/tools/posbox/posbox_download_images.sh
index 09bd79b2103..33beddac041 100755
--- a/addons/point_of_sale/tools/posbox/posbox_download_images.sh
+++ b/addons/point_of_sale/tools/posbox/posbox_download_images.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-wget 'https://downloads.raspberrypi.org/raspbian_lite_latest' -O raspbian.img.zip
+wget 'http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-07-05/2017-07-05-raspbian-jessie-lite.zip' -O raspbian.img.zip
unzip raspbian.img.zip
wget 'https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/kernel-qemu-4.4.13-jessie' -O kernel-qemu
diff --git a/addons/pos_discount/static/src/js/discount.js b/addons/pos_discount/static/src/js/discount.js
index 497573f5107..7ef38d6ffdb 100644
--- a/addons/pos_discount/static/src/js/discount.js
+++ b/addons/pos_discount/static/src/js/discount.js
@@ -1,14 +1,17 @@
odoo.define('pos_discount.pos_discount', function (require) {
"use strict";
+var core = require('web.core');
var screens = require('point_of_sale.screens');
+var _t = core._t;
+
var DiscountButton = screens.ActionButtonWidget.extend({
template: 'DiscountButton',
button_click: function(){
var self = this;
this.gui.show_popup('number',{
- 'title': 'Discount Percentage',
+ 'title': _t('Discount Percentage'),
'value': this.pos.config.discount_pc,
'confirm': function(val) {
val = Math.round(Math.max(0,Math.min(100,val)));
diff --git a/addons/purchase/security/purchase_security.xml b/addons/purchase/security/purchase_security.xml
index 1416745a8bb..4c409175bb4 100644
--- a/addons/purchase/security/purchase_security.xml
+++ b/addons/purchase/security/purchase_security.xml
@@ -51,5 +51,12 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
+ <record model="ir.rule" id="purchase_order_report_comp_rule">
+ <field name="name">Purchase Order Report multi-company</field>
+ <field name="model_id" ref="model_purchase_report"/>
+ <field name="global" eval="True"/>
+ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
+ </record>
+
</data>
</odoo>
diff --git a/addons/report/static/src/less/layout_clean.less b/addons/report/static/src/less/layout_clean.less
index 85e9c2d8491..869e1f4098f 100644
--- a/addons/report/static/src/less/layout_clean.less
+++ b/addons/report/static/src/less/layout_clean.less
@@ -70,7 +70,8 @@
/*Total table*/
.row > .col-xs-4 {
width: 43%;
- margin-top: -20px;
+ position: relative;
+ top: -20px;
> table {
tr:last-child td {
border-top: 1px solid @gray-lighter !important;
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index d6616d38cbe..c1dbb75922c 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -637,7 +637,7 @@ class Picking(models.Model):
move_dict = prod2move_ids[product_id][index]
qty_on_link = min(move_dict['remaining_qty'], qty_to_assign)
self.env['stock.move.operation.link'].create({'move_id': move_dict['move'].id, 'operation_id': operation_id, 'qty': qty_on_link, 'reserved_quant_id': quant_id})
- if move_dict['remaining_qty'] == qty_on_link:
+ if float_compare(move_dict['remaining_qty'], qty_on_link, precision_rounding=move_dict['move'].product_uom.rounding) == 0:
prod2move_ids[product_id].pop(index)
else:
move_dict['remaining_qty'] -= qty_on_link
diff --git a/addons/stock/wizard/stock_immediate_transfer.py b/addons/stock/wizard/stock_immediate_transfer.py
index 365e9dbb382..2183e454023 100644
--- a/addons/stock/wizard/stock_immediate_transfer.py
+++ b/addons/stock/wizard/stock_immediate_transfer.py
@@ -33,4 +33,4 @@ class StockImmediateTransfer(models.TransientModel):
pack.write({'qty_done': pack.product_qty})
else:
pack.unlink()
- self.pick_id.do_transfer()
+ return self.pick_id.do_transfer()
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index edace2f8fff..ee3a3603c52 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -71,7 +71,10 @@ class StockQuant(models.Model):
valuation_update = newprice - quant.cost
# this is where we post accounting entries for adjustment, if needed
# If neg quant period already closed (likely with manual valuation), skip update
- if not quant.company_id.currency_id.is_zero(valuation_update) and move._check_lock_date():
+ lock_date = max(move.company_id.period_lock_date, move.company_id.fiscalyear_lock_date)
+ if self.user_has_groups('account.group_account_manager'):
+ lock_date = move.company_id.fiscalyear_lock_date
+ if not quant.company_id.currency_id.is_zero(valuation_update) and lock_date and move.date[:10] > lock_date:
quant.with_context(force_valuation_amount=valuation_update)._account_entry_move(move)
# update the standard price of the product, only if we would have
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 9d299b1da84..c229029b944 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -10,7 +10,7 @@
* only when the deferred is resolved, and its value is equal to the resolved value.
* The module can be rejected (unloaded). This will be logged in the console as info.
*
- * logs:
+ * logs:
* Missing dependencies:
* These modules do not appear in the page. It is possible that the
* JavaScript file is not in the page or that the module name is wrong
@@ -185,16 +185,27 @@
failed = odoo.__DEBUG__.get_failed_jobs();
var unloaded = _.filter(debug_jobs, function (job) { return job.missing; });
- var log = [(_.isEmpty(failed) ? (_.isEmpty(unloaded) ? 'info' : 'warning' ) : 'error') + ':', 'Some modules could not be started'];
- if (missing.length) log.push('\nMissing dependencies: ', missing);
- if (!_.isEmpty(failed)) log.push('\nFailed modules: ', _.pluck(failed, 'name'));
- if (!_.isEmpty(rejected)) log.push('\nRejected modules: ', rejected);
- if (!_.isEmpty(rejected_linked))log.push('\nRejected linked modules:', rejected_linked);
- if (!_.isEmpty(unloaded)) log.push('\nNon loaded modules: ', _.pluck(unloaded, 'name'));
- if (odoo.debug && !_.isEmpty(debug_jobs)) log.push('\nDebug: ', debug_jobs);
-
if (odoo.debug || !_.isEmpty(failed) || !_.isEmpty(unloaded)) {
- console[_.isEmpty(failed) || _.isEmpty(unloaded) ? 'info' : 'error'].apply(console, log);
+ var log = console[_.isEmpty(failed) || _.isEmpty(unloaded) ? 'info' : 'error'].bind(console);
+ log((_.isEmpty(failed) ? (_.isEmpty(unloaded) ? 'info' : 'warning') : 'error') + ': Some modules could not be started');
+ if (missing.length) {
+ log('Missing dependencies: ', missing);
+ }
+ if (!_.isEmpty(failed)) {
+ log('Failed modules: ', _.pluck(failed, 'name'));
+ }
+ if (!_.isEmpty(rejected)) {
+ log('Rejected modules: ', rejected);
+ }
+ if (!_.isEmpty(rejected_linked)) {
+ log('Rejected linked modules: ', rejected_linked);
+ }
+ if (!_.isEmpty(unloaded)) {
+ log('Non loaded modules: ', _.pluck(unloaded, 'name'));
+ }
+ if (odoo.debug && !_.isEmpty(debug_jobs)) {
+ log('Debug: ', debug_jobs);
+ }
}
}
odoo.__DEBUG__.js_modules = {
diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js
index 6b167c59edd..31e92228767 100644
--- a/addons/web_editor/static/src/js/backend.js
+++ b/addons/web_editor/static/src/js/backend.js
@@ -301,6 +301,8 @@ var FieldTextHtml = AbstractField.extend({
old_initialize_content: function () {
this.$el.closest('.modal-body').css('max-height', 'none');
this.$iframe = this.$el.find('iframe');
+ // deactivate any button to avoid saving a not ready iframe
+ $('.o_cp_buttons, .o_statusbar_buttons').find('button').addClass('o_disabled').attr('disabled', true);
this.document = null;
this.$body = $();
this.$content = $();
@@ -316,6 +318,8 @@ var FieldTextHtml = AbstractField.extend({
this.render();
this.add_button();
this.loaded = true;
+ // reactivate all the buttons when the field's content (the iframe) is loaded
+ $('.o_cp_buttons, .o_statusbar_buttons').find('button').removeClass('o_disabled').attr('disabled', false);
setTimeout(self.resize, 0);
},
on_editor_loaded: function (EditorBar) {
diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js
index 0036a0783b7..5fc48b38071 100644
--- a/addons/web_editor/static/src/js/widgets.js
+++ b/addons/web_editor/static/src/js/widgets.js
@@ -617,7 +617,7 @@ var fontIconsDialog = Widget.extend({
e.stopPropagation();
this.$('#fa-icon').val(e.target.getAttribute('data-id'));
- $(".font-icons-icon").removeClass("o_selected");
+ this.$(".font-icons-icon").removeClass("o_selected");
$(e.target).addClass("o_selected");
},
'dblclick .font-icons-icon': function () {
@@ -743,7 +743,7 @@ var fontIconsDialog = Widget.extend({
continue;
case '': continue;
default:
- $(".font-icons-icon").removeClass("o_selected").filter("[data-alias*=',"+cls+",']").addClass("o_selected");
+ this.$(".font-icons-icon").removeClass("o_selected").filter("[data-alias*=',"+cls+",']").addClass("o_selected");
if (this.alias.indexOf(cls) !== -1) {
this.$('#fa-icon').val(cls);
}
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 1940cba837c..ca2d3522ae6 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -64,7 +64,7 @@ class Website(Home):
@http.route('/', type='http', auth="public", website=True)
def index(self, **kw):
page = 'homepage'
- main_menu = request.env.ref('website.main_menu', raise_if_not_found=False)
+ main_menu = request.website.menu_id or request.env.ref('website.main_menu', raise_if_not_found=False)
if main_menu:
first_menu = main_menu.child_id and main_menu.child_id[0]
if first_menu:
diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml
index 9344ed70e4a..e2d702596a2 100644
--- a/addons/website/data/website_data.xml
+++ b/addons/website/data/website_data.xml
@@ -94,7 +94,7 @@
<record id="website.library_image_02" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">Website Library Image 02</field>
- <field name="datas_fname">deliver.png</field>
+ <field name="datas_fname">deliver.jpg</field>
<field name="res_model">ir.ui.view</field>
<field name="type">url</field>
<field name="url">/website/static/src/img/library/deliver.jpg</field>
@@ -139,7 +139,7 @@
<record id="website.library_image_07" model="ir.attachment">
<field name="public" eval="True"/>
<field name="name">Website Library Image 07</field>
- <field name="datas_fname">mobile_device.png</field>
+ <field name="datas_fname">mobile_device.jpg</field>
<field name="res_model">ir.ui.view</field>
<field name="type">url</field>
<field name="url">/website/static/src/img/library/mobile_device.jpg</field>
diff --git a/addons/website/models/res_partner.py b/addons/website/models/res_partner.py
index d0f4a76b199..d3a3b1bc9f9 100644
--- a/addons/website/models/res_partner.py
+++ b/addons/website/models/res_partner.py
@@ -21,7 +21,7 @@ class Partner(models.Model):
return False
params = {
'center': '%s, %s %s, %s' % (self.street or '', self.city or '', self.zip or '', self.country_id and self.country_id.name_get()[0][1] or ''),
- 'size': "%sx%s" % (height, width),
+ 'size': "%sx%s" % (width, height),
'zoom': zoom,
'sensor': 'false',
'key': google_maps_api_key,
diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js
index d0547dcf9db..1213b986472 100644
--- a/addons/website/static/src/js/website.snippets.animation.js
+++ b/addons/website/static/src/js/website.snippets.animation.js
@@ -413,7 +413,11 @@ animation.registry.share = animation.Class.extend({
animation.registry.media_video = animation.Class.extend({
selector: ".media_iframe_video",
start: function () {
- if (!this.$target.has('> iframe').length) {
+ // TODO: this code should be refactored to make more sense and be better
+ // integrated with Odoo (this refactoring should be done in master).
+ this.$target.find('iframe').remove();
+
+ if (!this.$target.has('.media_iframe_video_size').length) {
var editor = '<div class="css_editable_mode_display">&nbsp;</div>';
var size = '<div class="media_iframe_video_size">&nbsp;</div>';
this.$target.html(editor+size);
diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml
index f6f513668ac..bcaa3a4c2aa 100644
--- a/addons/website_blog/views/website_blog_templates.xml
+++ b/addons/website_blog/views/website_blog_templates.xml
@@ -102,6 +102,7 @@
<t t-set="head">
<link t-att-href="'/blog/%s/feed' % (blog.id)" type="application/atom+xml" rel="alternate" title="Atom Feed"/>
</t>
+ <div class="oe_structure"/>
<div class="container">
<t t-call="website.pager" >
<t t-set="classname">pull-right</t>
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index fe260e997f3..d4be8fb5132 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -149,7 +149,7 @@ class WebsiteEventController(http.Controller):
# page not found
values['path'] = re.sub(r"^website_event\.", '', page)
values['from_template'] = 'website_event.default_page' # .strip('website_event.')
- page = 'website.page_404'
+ page = 'website.%s' % (request.website.is_publisher() and 'page_404' or '404')
return request.render(page, values)
diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml
index 34fcee7f2ac..362ffc3cd0e 100644
--- a/addons/website_event/views/event_templates.xml
+++ b/addons/website_event/views/event_templates.xml
@@ -544,7 +544,7 @@
<h4 class="modal-title" id="myModalLabel"><strong>Attendees</strong></h4>
</div>
<div class="modal-body">
- <div class="">
+ <div class="container">
<t t-set="counter_type" t-value="1"/>
<t t-set="counter" t-value="0"/>
<t t-foreach="tickets" t-as="ticket">
diff --git a/addons/website_portal/views/website_portal_templates.xml b/addons/website_portal/views/website_portal_templates.xml
index b2fe4f5676e..6e3a1b3f271 100644
--- a/addons/website_portal/views/website_portal_templates.xml
+++ b/addons/website_portal/views/website_portal_templates.xml
@@ -198,7 +198,8 @@
<div class="clearfix" />
<div t-attf-class="form-group #{error.get('company_name') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="company_name">Company Name</label>
- <input type="text" name="company_name" class="form-control" t-att-value="company_name or partner.company_name"/>
+ <p t-if="partner.commercial_partner_id.is_company" class="form-control" t-esc="partner.commercial_company_name"/>
+ <input t-else="" type="text" name="company_name" class="form-control" t-att-value="company_name or partner.company_name"/>
</div>
<div t-if="has_check_vat" t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="vat">VAT Number</label>
diff --git a/addons/website_portal_sale/models/payment.py b/addons/website_portal_sale/models/payment.py
index 3302ac87967..176f041847a 100644
--- a/addons/website_portal_sale/models/payment.py
+++ b/addons/website_portal_sale/models/payment.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
-from odoo import api, fields, models
+from odoo import _, api, fields, models
from odoo.tools import float_compare
_logger = logging.getLogger(__name__)
@@ -81,4 +81,16 @@ class PaymentTransaction(models.Model):
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
tx.sale_order_id.force_quotation_send()
else:
- _logger.warning('<%s> transaction MISMATCH for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
+ _logger.warning(
+ '<%s> transaction AMOUNT MISMATCH for order %s (ID %s): expected %r, got %r',
+ acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id,
+ tx.sale_order_id.amount_total, tx.amount,
+ )
+ tx.sale_order_id.message_post(
+ subject=_("Amount Mismatch (%s)") % acquirer_name,
+ body=_("The sale order was not confirmed despite response from the acquirer (%s): SO amount is %r but acquirer replied with %r.") % (
+ acquirer_name,
+ tx.sale_order_id.amount_total,
+ tx.amount,
+ )
+ )
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 5517b3ba508..d3235391fe2 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -373,8 +373,8 @@ class WebsiteSale(http.Controller):
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
request.website.sale_get_order(force_create=1)._cart_update(
product_id=int(product_id),
- add_qty=float(add_qty),
- set_qty=float(set_qty),
+ add_qty=add_qty,
+ set_qty=set_qty,
attributes=self._filter_attributes(**kw),
)
return request.redirect("/shop/cart")
@@ -388,7 +388,6 @@ class WebsiteSale(http.Controller):
if order.state != 'draft':
request.website.sale_reset()
return {}
-
value = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty)
if not order.cart_quantity:
request.website.sale_reset()
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 335e9796b54..743871c5686 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -113,6 +113,17 @@ class SaleOrder(models.Model):
""" Add or set product quantity, add_qty can be negative """
self.ensure_one()
SaleOrderLineSudo = self.env['sale.order.line'].sudo()
+
+ try:
+ if add_qty:
+ add_qty = float(add_qty)
+ except ValueError:
+ add_qty = 1
+ try:
+ if set_qty:
+ set_qty = float(set_qty)
+ except ValueError:
+ set_qty = 0
quantity = 0
order_line = False
if self.state != 'draft':
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index f7a8fa777bd..7d4d64d3932 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -133,6 +133,9 @@ odoo.define('website_sale.website_sale', function (require) {
return;
}
var value = parseInt($input.val() || 0, 10);
+ if (isNaN(value)) {
+ value = 1;
+ }
var $dom = $(this).closest('tr');
//var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
@@ -151,7 +154,11 @@ odoo.define('website_sale.website_sale', function (require) {
'set_qty': value
}).then(function (data) {
$input.data('update_change', false);
- if (value !== parseInt($input.val() || 0, 10)) {
+ var check_value = parseInt($input.val() || 0, 10);
+ if (isNaN(check_value)) {
+ check_value = 1;
+ }
+ if (value !== check_value) {
$input.trigger('change');
return;
}
diff --git a/addons/website_sale/static/src/js/website_sale_validate.js b/addons/website_sale/static/src/js/website_sale_validate.js
index dce80543dc3..614e8901484 100644
--- a/addons/website_sale/static/src/js/website_sale_validate.js
+++ b/addons/website_sale/static/src/js/website_sale_validate.js
@@ -16,8 +16,13 @@ $(document).ready(function () {
return ajax.jsonRpc('/shop/payment/get_status/' + order_id, 'call', {
}).then(function (result) {
_poll_nbr += 1;
- if(result.recall && _poll_nbr <= 5){
- setTimeout(function () { payment_transaction_poll_status(); }, 1000);
+ if(result.recall) {
+ if (_poll_nbr < 20){
+ setTimeout(function () { payment_transaction_poll_status(); }, Math.ceil(_poll_nbr / 3) * 1000);
+ }
+ else {
+ result.message = "<i class='fa fa-warning' />";
+ }
}
$('div.oe_website_sale_tx_status').html(result.message);
});
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index fcf24c81615..339645a46aa 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1471,7 +1471,7 @@
<div class="container oe_website_sale">
<div class="row">
<div class="col-md-9">
- <h1 class="mb32">Order <em t-field="order.name" /> Confirmed</h1>
+ <h1 class="mb32">Order <em t-field="order.name" /></h1>
<div class="thanks_msg">
<h2>Thank you for your order.
<a class="btn btn-primary pull-right hidden-xs" href="/shop/print" target="_blank"><i class="fa fa-print"></i> Print
diff --git a/addons/website_sale_options/controllers/main.py b/addons/website_sale_options/controllers/main.py
index 4c9ba8e6502..1c84f9e460f 100644
--- a/addons/website_sale_options/controllers/main.py
+++ b/addons/website_sale_options/controllers/main.py
@@ -33,8 +33,8 @@ class WebsiteSaleOptions(WebsiteSale):
if add_qty or set_qty:
value = order._cart_update(
product_id=int(product_id),
- add_qty=int(add_qty),
- set_qty=int(set_qty),
+ add_qty=add_qty,
+ set_qty=set_qty,
attributes=attributes,
optional_product_ids=optional_product_ids
)
diff --git a/doc/cla/corporate/it-projects-llc.md b/doc/cla/corporate/it-projects-llc.md
new file mode 100644
index 00000000000..038323173c3
--- /dev/null
+++ b/doc/cla/corporate/it-projects-llc.md
@@ -0,0 +1,20 @@
+Russia, 2017-11-25
+
+IT-Projects LLC 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,
+
+Ivan Yelizariev https://github.com/yelizariev
+
+List of contributors:
+
+Ivan Yelizariev yelizariev@it-projects.info https://github.com/yelizariev
+Ildar Nasyrov nasyrov@it-projects.info https://github.com/iledarn
+Ilmir Karamov karamov@it-projects.info https://github.com/ilmir-k
+Dinar Gabbasov gabbasov@it-projects.info https://github.com/GabbasovDinar
+Artyom Losev losev@it-projects.info https://github.com/ArtyomLosev
+Alexandr Kolushov kolushov@it-projects.info https://github.com/KolushovAlexandr
diff --git a/doc/cla/individual/adekock11.md b/doc/cla/individual/adekock11.md
new file mode 100644
index 00000000000..999190ef207
--- /dev/null
+++ b/doc/cla/individual/adekock11.md
@@ -0,0 +1,11 @@
+South Africa, 2017-11-25
+
+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,
+
+Andre de Kock adekock11@gmail.com https://github.com/adekock11
diff --git a/doc/cla/individual/elgow.md b/doc/cla/individual/elgow.md
new file mode 100644
index 00000000000..930d08bcec9
--- /dev/null
+++ b/doc/cla/individual/elgow.md
@@ -0,0 +1,11 @@
+USA, 16 Nov 2017
+
+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,
+
+Edward Gow elgow@users.noreply.github.com https://github.com/elgow
diff --git a/doc/cla/individual/flyingL123.md b/doc/cla/individual/flyingL123.md
new file mode 100644
index 00000000000..ba93055493d
--- /dev/null
+++ b/doc/cla/individual/flyingL123.md
@@ -0,0 +1,11 @@
+USA, 2017-11-27
+
+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,
+
+Michael Leff leff.michael@gmail.com https://github.com/flyingL123
diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py
index 4ce9038070f..8772039d0ad 100644
--- a/odoo/addons/base/ir/ir_translation.py
+++ b/odoo/addons/base/ir/ir_translation.py
@@ -680,6 +680,16 @@ class IrTranslation(models.Model):
action['context'] = {
'search_default_name': "%s,%s" % (fld.model_name, fld.name),
}
+ else:
+ rec = record
+ try:
+ while fld.related:
+ rec, fld = fld.traverse_related(rec)
+ if rec:
+ action['context'] = {'search_default_name': "%s,%s" % (fld.model_name, fld.name),}
+ except AccessError:
+ pass
+
return action
@api.model
diff --git a/odoo/addons/base/ir/ir_translation_view.xml b/odoo/addons/base/ir/ir_translation_view.xml
index 1c4fc4b748b..5b363b39c42 100644
--- a/odoo/addons/base/ir/ir_translation_view.xml
+++ b/odoo/addons/base/ir/ir_translation_view.xml
@@ -70,6 +70,7 @@
<field name="arch" type="xml">
<tree string="Translations" editable="top" create="false">
<field name="lang" readonly="1"/>
+ <field name="name" readonly="1"/>
<field name="source"/>
<field name="value"/>
<field name="state"/>
diff --git a/odoo/addons/base/res/res_country_data.xml b/odoo/addons/base/res/res_country_data.xml
index 056b03a47ac..16596a477f1 100644
--- a/odoo/addons/base/res/res_country_data.xml
+++ b/odoo/addons/base/res/res_country_data.xml
@@ -425,6 +425,7 @@
<field name="name">Denmark</field>
<field name="code">dk</field>
<field file="base/static/img/country_flags/dk.png" name="image" type="base64" />
+ <field eval="'%(street)s\n%(street2)s\n%(zip)s %(city)s\n%(country_name)s'" name="address_format" />
<field name="currency_id" ref="DKK" />
<field eval="45" name="phone_code" />
</record>
diff --git a/odoo/addons/base/res/res_currency_data.xml b/odoo/addons/base/res/res_currency_data.xml
index a39d049ffe1..8b108fa978d 100644
--- a/odoo/addons/base/res/res_currency_data.xml
+++ b/odoo/addons/base/res/res_currency_data.xml
@@ -266,6 +266,7 @@
<field name="name">ZAR</field>
<field name="symbol">R</field>
<field name="rounding">0.01</field>
+ <field name="position">before</field>
<field name="active" eval="False"/>
</record>
diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py
index 226ba9943f6..74f62bf7189 100644
--- a/odoo/addons/test_impex/tests/test_export.py
+++ b/odoo/addons/test_impex/tests/test_export.py
@@ -381,9 +381,13 @@ class test_o2m(CreatorCase):
def test_multiple_records_name(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value']),
- [[
- u'4', u','.join(self.names)
- ]])
+ [
+ [u'4', u'export.one2many.child:4'],
+ [u'', u'export.one2many.child:42'],
+ [u'', u'export.one2many.child:36'],
+ [u'', u'export.one2many.child:4'],
+ [u'', u'export.one2many.child:13'],
+ ])
def test_multiple_records_id(self):
export = self.export(self.commands, fields=['const', 'value/.id'])
@@ -401,19 +405,23 @@ class test_o2m(CreatorCase):
def test_multiple_records_with_name_before(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value', 'value/value']),
- [[ # exports sub-fields of very first o2m
- u'4', u','.join(self.names), u'4'
- ]])
+ [
+ [u'4', u'export.one2many.child:4', u'4'],
+ ['', u'export.one2many.child:42', u'42'],
+ ['', u'export.one2many.child:36', u'36'],
+ ['', u'export.one2many.child:4', u'4'],
+ ['', u'export.one2many.child:13', u'13'],
+ ])
def test_multiple_records_with_name_after(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value/value', 'value']),
- [ # completely ignores name_get request
- [u'4', u'4', ''],
- ['', u'42', ''],
- ['', u'36', ''],
- ['', u'4', ''],
- ['', u'13', ''],
+ [
+ [u'4', u'4', u'export.one2many.child:4'],
+ ['', u'42', u'export.one2many.child:42'],
+ ['', u'36', u'export.one2many.child:36'],
+ ['', u'4', u'export.one2many.child:4'],
+ ['', u'13', u'export.one2many.child:13'],
])
def test_multiple_subfields_neighbour(self):
@@ -584,10 +592,13 @@ class test_m2m(CreatorCase):
def test_multiple_records_name(self):
self.assertEqual(
self.export(self.commands, fields=['const', 'value']),
- [[ # FIXME: hardcoded comma, import uses config.csv_internal_sep
- # resolution: remove configurable csv_internal_sep
- u'4', u','.join(self.names)
- ]])
+ [
+ [u'4', u'export.many2many.other:4'],
+ ['', u'export.many2many.other:42'],
+ ['', u'export.many2many.other:36'],
+ ['', u'export.many2many.other:4'],
+ ['', u'export.many2many.other:13'],
+ ])
# essentially same as o2m, so boring
diff --git a/odoo/models.py b/odoo/models.py
index 8ce1f01eb6b..f374eeac97f 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -685,22 +685,18 @@ class BaseModel(object):
current[i] = ','.join(xml_ids) or False
continue
- # recursively export the fields that follow name
- fields2 = [(p[1:] if p and p[0] == name else []) for p in fields]
+ # recursively export the fields that follow name; use
+ # 'display_name' where no subfield is exported
+ fields2 = [(p[1:] or ['display_name'] if p and p[0] == name else [])
+ for p in fields]
lines2 = value._export_rows(fields2)
if lines2:
# merge first line with record's main line
for j, val in enumerate(lines2[0]):
if val or isinstance(val, bool):
current[j] = val
- # check value of current field
- if not current[i] and not isinstance(current[i], bool):
- # assign xml_ids, and forget about remaining lines
- xml_ids = [item[1] for item in value.name_get()]
- current[i] = ','.join(xml_ids)
- else:
- # append the other lines at the end
- lines += lines2[1:]
+ # append the other lines at the end
+ lines += lines2[1:]
else:
current[i] = False
diff --git a/requirements.txt b/requirements.txt
index 565a21b84d1..028493da972 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,7 +17,7 @@ passlib==1.6.5
Pillow==3.4.1
psutil==4.3.1
psycogreen==1.0
-psycopg2==2.7.3
+psycopg2==2.7.3.1
pydot==1.2.3
pyldap==2.4.28
pyparsing==2.1.10