summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-01-07 18:21:13 +0100
committerChristophe Simonis <chs@odoo.com>2016-01-07 18:21:13 +0100
commit4852d6033d8d11aa62434a0df22aa24d5760fdb0 (patch)
tree327deda5210017a1514ddc87a64663cca74f63c6 /addons
parent5ba5172d9922c76b07f2e510dc676760aba89827 (diff)
parent99b6cd8119211414602c739087c57d1966198f8e (diff)
[MERGE] forward port of branch saas-8 up to 99b6cd8
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account.py10
-rw-r--r--addons/account/models/account_invoice.py2
-rw-r--r--addons/account/models/account_move.py25
-rw-r--r--addons/account/models/chart_template.py2
-rw-r--r--addons/account/models/partner.py3
-rw-r--r--addons/account/tests/__init__.py1
-rw-r--r--addons/account/tests/test_product_id_change.py91
-rw-r--r--addons/account/views/account_view.xml27
-rw-r--r--addons/auth_signup/controllers/main.py6
-rw-r--r--addons/auth_signup/i18n/auth_signup.pot76
-rw-r--r--addons/calendar/calendar.py2
-rw-r--r--addons/crm/crm_lead_view.xml1
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity.py4
-rw-r--r--addons/delivery/models/delivery_carrier.py23
-rw-r--r--addons/delivery/views/delivery_view.xml3
-rw-r--r--addons/event_sale/models/sale_order.py4
-rw-r--r--addons/event_sale/views/sale_order.xml6
-rw-r--r--addons/google_calendar/google_calendar.py4
-rw-r--r--addons/hr_expense/__init__.py1
-rw-r--r--addons/hr_expense/report/__init__.py4
-rw-r--r--addons/hr_expense/report/hr_expense_report.py60
-rw-r--r--addons/hr_expense/views/report_expense.xml97
-rw-r--r--addons/hr_payroll/report/report_payslip_details.py2
-rw-r--r--addons/hw_blackbox_be/__init__.py4
-rw-r--r--addons/hw_blackbox_be/__openerp__.py24
-rw-r--r--addons/hw_blackbox_be/controllers/__init__.py4
-rw-r--r--addons/hw_blackbox_be/controllers/main.py162
-rw-r--r--addons/im_livechat/controllers/main.py3
-rw-r--r--addons/im_livechat/models/im_livechat_channel.py2
-rw-r--r--addons/im_livechat/views/im_livechat_channel_templates.xml4
-rw-r--r--addons/l10n_be_invoice_bba/invoice.py2
-rw-r--r--addons/mail/models/mail_mail.py2
-rw-r--r--addons/mail/models/mail_template.py2
-rw-r--r--addons/mail/tests/common.py1
-rw-r--r--addons/mail/tests/test_mail_gateway.py4
-rw-r--r--addons/mail/wizard/mail_compose_message_view.xml1
-rw-r--r--addons/mass_mailing/i18n/mass_mailing.pot34
-rw-r--r--addons/mass_mailing/models/mail_mail.py1
-rw-r--r--addons/mass_mailing/models/mass_mailing.py2
-rw-r--r--addons/mass_mailing/views/mass_mailing.xml3
-rw-r--r--addons/payment_buckaroo/models/buckaroo.py33
-rw-r--r--addons/payment_buckaroo/tests/test_buckaroo.py4
-rw-r--r--addons/point_of_sale/point_of_sale.py4
-rw-r--r--addons/point_of_sale/static/src/js/gui.js11
-rw-r--r--addons/point_of_sale/static/src/js/models.js3
-rw-r--r--addons/point_of_sale/static/src/js/popups.js1
-rw-r--r--addons/point_of_sale/static/src/js/screens.js10
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml9
-rw-r--r--addons/point_of_sale/tools/posbox/configuration/odoo.conf1
-rwxr-xr-xaddons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo2
-rw-r--r--addons/pos_restaurant/static/src/xml/floors.xml6
-rw-r--r--addons/product/product.py2
-rw-r--r--addons/purchase/purchase.py16
-rw-r--r--addons/purchase/stock.py8
-rw-r--r--addons/sale/sale.py12
-rw-r--r--addons/sale/sale_analytic.py3
-rw-r--r--addons/sale_stock/sale_stock.py3
-rw-r--r--addons/sale_timesheet/models/sale_timesheet.py3
-rw-r--r--addons/stock/stock.py3
-rw-r--r--addons/stock_account/stock_account.py2
-rw-r--r--addons/stock_calendar/procurement.py33
-rw-r--r--addons/stock_calendar/resource.py9
-rw-r--r--addons/web/controllers/main.py2
-rw-r--r--addons/web_calendar/static/src/js/web_calendar.js2
-rw-r--r--addons/web_editor/static/src/js/backend.js9
-rw-r--r--addons/web_kanban/static/src/less/kanban_view.less3
-rw-r--r--addons/website/static/src/css/website.css1
-rw-r--r--addons/website/static/src/css/website.sass1
-rw-r--r--addons/website_portal/views/templates.xml4
-rw-r--r--addons/website_sale/models/sale_order.py26
-rw-r--r--addons/website_sale/static/src/js/website_sale.js2
-rw-r--r--addons/website_sale/views/templates.xml9
-rw-r--r--addons/website_sale_delivery/__openerp__.py1
-rw-r--r--addons/website_sale_delivery/models/sale_order.py15
-rw-r--r--addons/website_sale_delivery/security/ir.model.access.csv2
-rw-r--r--addons/website_slides/static/src/less/website_slides.less9
76 files changed, 732 insertions, 241 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 50388f30f49..19842d28fe5 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -177,6 +177,16 @@ class AccountAccount(models.Model):
def mark_as_reconciled(self):
return self.write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)})
+ @api.multi
+ def action_open_reconcile(self):
+ # Open reconciliation view for this account
+ action_context = {'show_mode_selector': False, 'account_ids': [self.id,]}
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'manual_reconciliation_view',
+ 'context': action_context,
+ }
+
class AccountJournal(models.Model):
_name = "account.journal"
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 763290d4619..a47ebeb8a2d 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -1162,7 +1162,7 @@ class AccountInvoiceLine(models.Model):
if not self.product_id:
fpos = self.invoice_id.fiscal_position_id
self.invoice_line_tax_ids = fpos.map_tax(self.account_id.tax_ids).ids
- else:
+ elif not self.price_unit:
self._set_taxes()
@api.onchange('uom_id')
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 3564ba348b7..b59e43a152e 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -213,6 +213,10 @@ class AccountMove(models.Model):
return [x.id for x in reversed_moves]
return True
+ @api.multi
+ def open_reconcile_view(self):
+ return self.line_ids.open_reconcile_view()
+
class AccountMoveLine(models.Model):
_name = "account.move.line"
@@ -421,6 +425,7 @@ class AccountMoveLine(models.Model):
# Note : this short-circuiting is better for performances, but also required
# since postgresql doesn't implement empty list (so 'AND id in ()' is useless)
return []
+ res_ids = res_ids and tuple(res_ids)
assert res_type in ('partner', 'account')
assert account_type in ('payable', 'receivable', None)
@@ -447,21 +452,22 @@ class AccountMoveLine(models.Model):
{3}
{4}
{5}
+ AND l.company_id = {6}
AND EXISTS (
SELECT NULL
FROM account_move_line l
WHERE l.account_id = a.id
- {6}
+ {7}
AND l.amount_residual > 0
)
AND EXISTS (
SELECT NULL
FROM account_move_line l
WHERE l.account_id = a.id
- {6}
+ {7}
AND l.amount_residual < 0
)
- GROUP BY {7} a.id, a.name, a.code, {res_alias}.last_time_entries_checked
+ GROUP BY {8} a.id, a.name, a.code, {res_alias}.last_time_entries_checked
ORDER BY {res_alias}.last_time_entries_checked
) as s
WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked)
@@ -472,6 +478,7 @@ class AccountMoveLine(models.Model):
is_partner and ' ' or "AND at.type <> 'payable' AND at.type <> 'receivable'",
account_type and "AND at.type = %(account_type)s" or '',
res_ids and 'AND ' + res_alias + '.id in %(res_ids)s' or '',
+ self.env.user.company_id.id,
is_partner and 'AND l.partner_id = p.id' or ' ',
is_partner and 'l.partner_id, p.id,' or ' ',
res_alias=res_alias
@@ -1141,6 +1148,18 @@ class AccountMoveLine(models.Model):
tables, where_clause, where_clause_params = query.get_sql()
return tables, where_clause, where_clause_params
+ @api.multi
+ def open_reconcile_view(self):
+ model, action_id = self.pool['ir.model.data'].get_object_reference(self._cr, self._uid, 'account', "action_account_moves_all_a")
+ action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
+ ids = []
+ for aml in self:
+ if aml.account_id.reconcile:
+ ids.extend([r.debit_move_id.id for r in aml.matched_debit_ids] if aml.credit > 0 else [r.credit_move_id.id for r in aml.matched_credit_ids])
+ ids.append(aml.id)
+ action['domain'] = [('id', 'in', ids)]
+ return action
+
class AccountPartialReconcile(models.Model):
_name = "account.partial.reconcile"
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 1d0ec80306c..c094bef3db4 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -749,7 +749,7 @@ class WizardMultiChartsAccounts(models.TransientModel):
# Create the current year earning account (outside of the CoA)
self.env['account.account'].create({
- 'code': '9999',
+ 'code': '999999',
'name': _('Undistributed Profits/Losses'),
'user_type_id': self.env.ref("account.data_unaffected_earnings").id,
'company_id': company.id,})
diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py
index 2bde6466a4c..88850c2153c 100644
--- a/addons/account/models/partner.py
+++ b/addons/account/models/partner.py
@@ -306,7 +306,8 @@ class ResPartner(models.Model):
# generate where clause to include multicompany rules
where_query = account_invoice_report._where_calc([
- ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id)
+ ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id),
+ ('type', 'in', ('out_invoice', 'out_refund'))
])
account_invoice_report._apply_ir_rules(where_query, 'read')
from_clause, where_clause, where_clause_params = where_query.get_sql()
diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py
index e4a6224852d..01e519c02cf 100644
--- a/addons/account/tests/__init__.py
+++ b/addons/account/tests/__init__.py
@@ -12,6 +12,7 @@ from . import test_chart_of_account
from . import test_fiscal_position
from . import test_manual_reconciliation
from . import test_payment
+from . import test_product_id_change
from . import test_reconciliation
from . import test_search
from . import test_tax
diff --git a/addons/account/tests/test_product_id_change.py b/addons/account/tests/test_product_id_change.py
new file mode 100644
index 00000000000..a2008b64bc7
--- /dev/null
+++ b/addons/account/tests/test_product_id_change.py
@@ -0,0 +1,91 @@
+from openerp.addons.account.tests.account_test_classes import AccountingTestCase
+import time
+
+class TestProductIdChange(AccountingTestCase):
+ """Test that when an included tax is mapped by a fiscal position, the included tax must be
+ subtracted to the price of the product.
+ """
+
+ def setUp(self):
+ super(TestProductIdChange, self).setUp()
+ self.invoice_model = self.env['account.invoice']
+ self.fiscal_position_model = self.env['account.fiscal.position']
+ self.fiscal_position_tax_model = self.env['account.fiscal.position.tax']
+ self.tax_model = self.env['account.tax']
+ self.pricelist_model = self.env['product.pricelist']
+ self.res_partner_model = self.env['res.partner']
+ self.product_tmpl_model = self.env['product.template']
+ self.product_model = self.env['product.product']
+ self.invoice_line_model = self.env['account.invoice.line']
+ self.account_receivable = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
+ self.account_revenue = self.env['account.account'].search([('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)], limit=1)
+
+ def test_product_id_change(self):
+ partner = self.res_partner_model.create(dict(name="George"))
+ tax_include_sale = self.tax_model.create(dict(name="Include tax",
+ type_tax_use='sale',
+ amount='21.00',
+ price_include=True))
+ tax_include_purchase = self.tax_model.create(dict(name="Include tax",
+ type_tax_use='purchase',
+ amount='21.00',
+ price_include=True))
+ tax_exclude_sale = self.tax_model.create(dict(name="Exclude tax",
+ type_tax_use='sale',
+ amount='0.00'))
+ tax_exclude_purchase = self.tax_model.create(dict(name="Exclude tax",
+ type_tax_use='purchase',
+ amount='0.00'))
+ product_tmpl = self.product_tmpl_model.create(dict(name="Voiture",
+ list_price='121',
+ standard_price='121',
+ taxes_id=[(6, 0, [tax_include_sale.id])],
+ supplier_taxes_id=[(6, 0, [tax_include_purchase.id])]))
+ product = self.product_model.create(dict(product_tmpl_id=product_tmpl.id))
+ fp = self.fiscal_position_model.create(dict(name="fiscal position", sequence=1))
+ fp_tax_sale = self.fiscal_position_tax_model.create(dict(position_id=fp.id,
+ tax_src_id=tax_include_sale.id,
+ tax_dest_id=tax_exclude_sale.id))
+ fp_tax_purchase = self.fiscal_position_tax_model.create(dict(position_id=fp.id,
+ tax_src_id=tax_include_purchase.id,
+ tax_dest_id=tax_exclude_purchase.id))
+
+ out_invoice = self.invoice_model.create({
+ 'partner_id': partner.id,
+ 'reference_type': 'none',
+ 'name': 'invoice to client',
+ 'account_id': self.account_receivable.id,
+ 'type': 'out_invoice',
+ 'date_invoice': time.strftime('%Y') + '-06-26',
+ 'fiscal_position_id': fp.id,
+ })
+ out_line = self.invoice_line_model.create({
+ 'product_id': product.id,
+ 'quantity': 1,
+ 'price_unit': 121.0,
+ 'invoice_id': out_invoice.id,
+ 'name': 'something out',
+ 'account_id': self.account_revenue.id,
+ })
+
+ in_invoice = self.invoice_model.create({
+ 'partner_id': partner.id,
+ 'reference_type': 'none',
+ 'name': 'invoice to supplier',
+ 'account_id': self.account_receivable.id,
+ 'type': 'in_invoice',
+ 'date_invoice': time.strftime('%Y') + '-06-26',
+ 'fiscal_position_id': fp.id,
+ })
+ in_line = self.invoice_line_model.create({
+ 'product_id': product.id,
+ 'quantity': 1,
+ 'price_unit': 121.0,
+ 'invoice_id': in_invoice.id,
+ 'name': 'something in',
+ 'account_id': self.account_revenue.id,
+ })
+ out_line._onchange_product_id()
+ self.assertEquals(100, out_line.price_unit, "The included tax must be subtracted to the price")
+ in_line._onchange_product_id()
+ self.assertEquals(100, in_line.price_unit, "The included tax must be subtracted to the price")
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml
index 1b6249c4cdb..be22f4e63d7 100644
--- a/addons/account/views/account_view.xml
+++ b/addons/account/views/account_view.xml
@@ -21,7 +21,11 @@
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="currency_id" options="{'no_create': True}" groups="base.group_multi_currency"/>
<field name="internal_type" invisible="1" readonly="1"/>
- <field name="reconcile"/>
+ <label for="reconcile"/>
+ <div>
+ <field name="reconcile"/>
+ <button name="action_open_reconcile" class="oe_link" type="object" string=" -> Reconcile" attrs="{'invisible': [('reconcile', '=', False)]}"/>
+ </div>
<field name="deprecated"/>
</group>
<group>
@@ -1021,7 +1025,18 @@
<field name="amount_currency"/>
</group>
<group string="Reconciliation">
- <field name="reconciled"/>
+ <label for="reconciled"/>
+ <div>
+ <field name="reconciled"/>
+ <field name="matched_debit_ids" invisible="1"/>
+ <field name="matched_credit_ids" invisible="1"/>
+ <button name="open_reconcile_view"
+ class="oe_link"
+ type="object"
+ string="-> View reconciled entries"
+ attrs="{'invisible': [('matched_debit_ids', '=', []),('matched_credit_ids', '=', [])]}">
+ </button>
+ </div>
</group>
<group string="States">
<field name="blocked"/>
@@ -1238,6 +1253,14 @@
<field name="state" widget="statusbar"/>
</header>
<sheet>
+ <div class="oe_button_box">
+ <button name="open_reconcile_view"
+ class="oe_stat_button"
+ icon="fa-bars"
+ type="object"
+ string="View reconciled entries">
+ </button>
+ </div>
<label for="name" class="oe_edit_only" attrs="{'invisible':[('name','=','/')]}"/>
<h1>
<field name="name" readonly="True" attrs="{'invisible':[('name','=','/')]}"/>
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 9ebd4e657b5..779f6e34301 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -34,7 +34,11 @@ class AuthSignupHome(Home):
self.do_signup(qcontext)
return super(AuthSignupHome, self).web_login(*args, **kw)
except (SignupError, AssertionError), e:
- qcontext['error'] = _(e.message)
+ if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]):
+ qcontext["error"] = _("Another user is already registered using this email address.")
+ else:
+ _logger.error(e.message)
+ qcontext['error'] = _("Could not create a new account.")
return request.render('auth_signup.signup', qcontext)
diff --git a/addons/auth_signup/i18n/auth_signup.pot b/addons/auth_signup/i18n/auth_signup.pot
index b229e9027d8..f31ec11d68b 100644
--- a/addons/auth_signup/i18n/auth_signup.pot
+++ b/addons/auth_signup/i18n/auth_signup.pot
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 9.0\n"
+"Project-Id-Version: Odoo Server 9.0c\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-09-14 10:27+0000\n"
-"PO-Revision-Date: 2015-09-14 10:27+0000\n"
+"POT-Creation-Date: 2016-01-06 07:13+0000\n"
+"PO-Revision-Date: 2016-01-06 07:13+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -75,13 +75,19 @@ msgid "Allow external users to sign up"
msgstr ""
#. module: auth_signup
-#: code:addons/auth_signup/controllers/main.py:60
+#: code:addons/auth_signup/controllers/main.py:64
#, python-format
msgid "An email has been sent with credentials to reset your password"
msgstr ""
#. module: auth_signup
-#: code:addons/auth_signup/controllers/main.py:108
+#: code:addons/auth_signup/controllers/main.py:40
+#, python-format
+msgid "Another user is already registered using this email address."
+msgstr ""
+
+#. module: auth_signup
+#: code:addons/auth_signup/controllers/main.py:115
#, python-format
msgid "Authentication Failed."
msgstr ""
@@ -114,7 +120,13 @@ msgid "Connected"
msgstr ""
#. module: auth_signup
-#: code:addons/auth_signup/controllers/main.py:62
+#: code:addons/auth_signup/controllers/main.py:43
+#, python-format
+msgid "Could not create a new account."
+msgstr ""
+
+#. module: auth_signup
+#: code:addons/auth_signup/controllers/main.py:66
#, python-format
msgid "Could not reset your password"
msgstr ""
@@ -156,17 +168,12 @@ msgid "Password reset"
msgstr ""
#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_private
-msgid "Private Profile"
-msgstr ""
-
-#. module: auth_signup
#: model:ir.ui.view,arch_db:auth_signup.login
msgid "Reset Password"
msgstr ""
#. module: auth_signup
-#: code:addons/auth_signup/res_users.py:250
+#: code:addons/auth_signup/res_users.py:253
#, python-format
msgid "Reset password: invalid username or email"
msgstr ""
@@ -228,11 +235,6 @@ msgid "Template user for new users created through signup"
msgstr ""
#. module: auth_signup
-#: model:ir.model.fields,help:auth_signup.field_res_users_website_url
-msgid "The full URL to access the document through the website."
-msgstr ""
-
-#. module: auth_signup
#: model:ir.model.fields,help:auth_signup.field_base_config_settings_auth_signup_reset_password
msgid "This allows users to trigger a password reset from the Login page."
msgstr ""
@@ -243,41 +245,6 @@ msgid "Users"
msgstr ""
#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_published
-msgid "Visible in Website"
-msgstr ""
-
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_description
-msgid "Website Partner Full Description"
-msgstr ""
-
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_short_description
-msgid "Website Partner Short Description"
-msgstr ""
-
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_url
-msgid "Website URL"
-msgstr ""
-
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_meta_description
-msgid "Website meta description"
-msgstr ""
-
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_meta_keywords
-msgid "Website meta keywords"
-msgstr ""
-
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_website_meta_title
-msgid "Website meta title"
-msgstr ""
-
-#. module: auth_signup
#: model:ir.ui.view,arch_db:auth_signup.fields
#: model:ir.ui.view,arch_db:auth_signup.reset_password
msgid "Your Email"
@@ -298,8 +265,3 @@ msgstr ""
msgid "e.g. John Doe"
msgstr ""
-#. module: auth_signup
-#: model:ir.model.fields,field_description:auth_signup.field_res_users_self
-msgid "unknown"
-msgstr ""
-
diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py
index 860c64fee7f..4e7d853194d 100644
--- a/addons/calendar/calendar.py
+++ b/addons/calendar/calendar.py
@@ -1459,7 +1459,7 @@ class calendar_event(osv.Model):
for arg in args:
new_arg = arg
- if arg[0] in ('start_date', 'start_datetime', 'start',) and arg[1] == ">=":
+ if arg[0] in ('stop_date', 'stop_datetime', 'stop',) and arg[1] == ">=":
if context.get('virtual_id', True):
new_args += ['|', '&', ('recurrency', '=', 1), ('final_date', arg[1], arg[2])]
elif arg[0] == "id":
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index 1f16d76c6c7..dc9d6e8457c 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -87,6 +87,7 @@
<group>
<!-- Preload all the partner's information -->
<field name="partner_id" string="Customer"
+ on_change="on_change_partner_id(partner_id)"
options='{"create_name_field": "name"}'
context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id}" groups="base.group_no_one"/>
<field name="partner_name" string="Company Name"/>
diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py
index e621910c71d..b08d8445b3d 100644
--- a/addons/crm/wizard/crm_lead_to_opportunity.py
+++ b/addons/crm/wizard/crm_lead_to_opportunity.py
@@ -121,6 +121,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
context = {}
lead_obj = self.pool['crm.lead']
+ partner_obj = self.pool['res.partner']
w = self.browse(cr, uid, ids, context=context)[0]
opp_ids = [o.id for o in w.opportunity_ids]
@@ -144,6 +145,9 @@ class crm_lead2opportunity_partner(osv.osv_memory):
lead_ids = context.get('active_ids', [])
vals.update({'lead_ids': lead_ids, 'user_ids': [w.user_id.id]})
self._convert_opportunity(cr, uid, ids, vals, context=context)
+ for lead in lead_obj.browse(cr, uid, lead_ids, context=context):
+ if lead.partner_id and lead.partner_id.user_id != lead.user_id:
+ partner_obj.write(cr, uid, [lead.partner_id.id], {'user_id': lead.user_id.id}, context=context)
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
diff --git a/addons/delivery/models/delivery_carrier.py b/addons/delivery/models/delivery_carrier.py
index 4aa8987b5dc..dbf93a92853 100644
--- a/addons/delivery/models/delivery_carrier.py
+++ b/addons/delivery/models/delivery_carrier.py
@@ -3,7 +3,7 @@
import logging
from openerp import api, fields, models, _
-from openerp.exceptions import UserError
+from openerp.exceptions import UserError, ValidationError
_logger = logging.getLogger(__name__)
@@ -52,6 +52,25 @@ class DeliveryCarrier(models.Model):
fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price',help="Keep empty if the pricing depends on the advanced pricing per destination")
shipping_enabled = fields.Boolean(string="Shipping enabled", default=True, help="Uncheck this box to disable package shipping while validating Delivery Orders")
+ @api.multi
+ def name_get(self):
+ display_delivery = self.env.context.get('display_delivery', False)
+ order_id = self.env.context.get('order_id', False)
+ if display_delivery and order_id:
+ order = self.env['sale.order'].browse(order_id)
+ currency = order.pricelist_id.currency_id.name or ''
+ res = []
+ for carrier_id in self.ids:
+ try:
+ r = self.read([carrier_id], ['name', 'price'])[0]
+ res.append((r['id'], r['name'] + ' (' + (str(r['price'])) + ' ' + currency + ')'))
+ except ValidationError:
+ r = self.read([carrier_id], ['name'])[0]
+ res.append((r['id'], r['name']))
+ else:
+ res = super(DeliveryCarrier, self).name_get()
+ return res
+
@api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price')
def _compute_fixed_price(self):
for carrier in self:
@@ -234,6 +253,8 @@ class DeliveryCarrier(models.Model):
quantity += qty
total = (order.amount_total or 0.0) - total_delivery
+ total = order.currency_id.with_context(date=order.date_order).compute(total, order.company_id.currency_id)
+
return self.get_price_from_picking(total, weight, volume, quantity)
def get_price_from_picking(self, total, weight, volume, quantity):
diff --git a/addons/delivery/views/delivery_view.xml b/addons/delivery/views/delivery_view.xml
index 38264a9aee6..6b474b0989f 100644
--- a/addons/delivery/views/delivery_view.xml
+++ b/addons/delivery/views/delivery_view.xml
@@ -171,7 +171,8 @@
</group>
</xpath>
<div name="button_box" position="inside">
- <button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking" attrs="{'invisible': ['|',('carrier_tracking_ref','=',False),('delivery_type','in', ['fixed', 'base_on_rule'])]}" />
+ <button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking"
+ attrs="{'invisible': ['|','|',('carrier_tracking_ref','=',False),('carrier_id', '=', False),('delivery_type','=','grid')]}" />
</div>
<xpath expr="/form/header/button[last()]" position="after">
<button name="send_to_shipper" string="Send to Shipper" type="object" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','!=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index 2d8b38d1987..8c2b8bc0c3f 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -30,8 +30,8 @@ class sale_order_line(osv.osv):
help="Choose an event ticket and it will automatically create a registration for this event ticket."),
# those 2 fields are used for dynamic domains and filled by onchange
# TDE: really necessary ? ...
- 'event_type_id': fields.related('product_id', 'event_type_id', type='many2one', relation="event.type", string="Event Type"),
- 'event_ok': fields.related('product_id', 'event_ok', string='event_ok', type='boolean'),
+ 'event_type_id': fields.related('product_id', 'event_type_id', type='many2one', relation="event.type", string="Event Type", readonly=True),
+ 'event_ok': fields.related('product_id', 'event_ok', string='event_ok', type='boolean', readonly=True),
}
def _prepare_order_line_invoice_line(self, cr, uid, line, account_id=False, context=None):
diff --git a/addons/event_sale/views/sale_order.xml b/addons/event_sale/views/sale_order.xml
index 53fd9706eb4..7a1371de035 100644
--- a/addons/event_sale/views/sale_order.xml
+++ b/addons/event_sale/views/sale_order.xml
@@ -6,13 +6,17 @@
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
- <xpath expr="//field[@name='product_id']" position="after">
+ <xpath expr="//field[@name='order_line']//form//field[@name='product_id']" position="after">
<field name="event_id" domain="['|', ('event_type_id','=', False),('event_type_id', '=', event_type_id),('date_end','&gt;=',time.strftime('%Y-%m-%d 00:00:00'))]" attrs="{'invisible': [('event_ok', '=', False)],'required': [('event_ok', '!=', False)]}"/>
<field name="event_ticket_id" domain="[('event_id', '=', event_id), ('seats_available', '>', 0)]" attrs="{'invisible': [('event_id', '=', False)], 'required': [('event_id', '!=', False)]}"
on_change="onchange_event_ticket_id(event_ticket_id, context)"/>
<field name="event_type_id" invisible="1"/>
<field name="event_ok" invisible="1"/>
</xpath>
+ <xpath expr="//field[@name='order_line']//tree//field[@name='product_id']" position="after">
+ <field name="event_type_id" invisible="1"/>
+ <field name="event_ok" invisible="1"/>
+ </xpath>
</field>
</record>
diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py
index e00b97a8336..81d1bb82f5f 100644
--- a/addons/google_calendar/google_calendar.py
+++ b/addons/google_calendar/google_calendar.py
@@ -232,12 +232,12 @@ class google_calendar(osv.AbstractModel):
"start": {
type: start_date,
vstype: None,
- 'timeZone': context.get('tz', 'UTC'),
+ 'timeZone': context.get('tz') or 'UTC',
},
"end": {
type: final_date,
vstype: None,
- 'timeZone': context.get('tz', 'UTC'),
+ 'timeZone': context.get('tz') or 'UTC',
},
"attendees": attendee_list,
"reminders": {
diff --git a/addons/hr_expense/__init__.py b/addons/hr_expense/__init__.py
index 11529f98733..d84fb182e8f 100644
--- a/addons/hr_expense/__init__.py
+++ b/addons/hr_expense/__init__.py
@@ -2,4 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import models
+import report
import wizard
diff --git a/addons/hr_expense/report/__init__.py b/addons/hr_expense/report/__init__.py
new file mode 100644
index 00000000000..7f74fc905a4
--- /dev/null
+++ b/addons/hr_expense/report/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import hr_expense_report \ No newline at end of file
diff --git a/addons/hr_expense/report/hr_expense_report.py b/addons/hr_expense/report/hr_expense_report.py
new file mode 100644
index 00000000000..1422abf9aa5
--- /dev/null
+++ b/addons/hr_expense/report/hr_expense_report.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import time
+from openerp.osv import osv
+from openerp.tools.misc import formatLang
+from openerp.tools.translate import _
+from openerp.report import report_sxw
+from openerp.exceptions import UserError
+
+
+class report_expense(report_sxw.rml_parse):
+
+ def set_context(self, objects, data, ids, report_type=None):
+ res = super(report_expense, self).set_context(objects, data, ids, report_type=report_type)
+
+ hr_expense_obj = self.pool.get('hr.expense')
+
+ context = {'lang': self.pool.get('res.users').browse(self.cr, self.uid, self.uid).lang}
+ state_field = hr_expense_obj.fields_get(self.cr, self.uid, 'state', context=context)['state']['selection']
+ state_dict = {}
+ for state_tuple in state_field:
+ state_dict[state_tuple[0]] = state_tuple[1]
+
+ expenses_info = {}
+ expenses = hr_expense_obj.search(self.cr, self.uid, [('id', 'in', ids)], order="employee_id, currency_id, state, date")
+ for expense in hr_expense_obj.browse(self.cr, self.uid, expenses):
+ key = expense.employee_id.name + '-' + expense.currency_id.name + '-' + expense.state
+ if expenses_info.get(key):
+ expenses_info[key]['lines'] += expense
+ expenses_info[key]['total_amount'] += expense.total_amount
+ else:
+ expenses_info[key] = {
+ 'employee_name': expense.employee_id.name,
+ 'total_amount': expense.total_amount,
+ 'lines': expense,
+ 'currency': expense.currency_id,
+ 'validator_name': expense.employee_id.parent_id.name,
+ 'notes': [],
+ 'notes_index': {},
+ 'state': state_dict[expense.state],
+ }
+ if expense.description:
+ index = len(expenses_info[key]['notes']) + 1
+ expenses_info[key]['notes'].append({'description': expense.description, 'index':index})
+ expenses_info[key]['notes_index'][expense.id] = index
+
+ # Qweb for-each do not work on dict, so we send a list and we sort it by the name of the employee
+ # that way if we have two sheet for the same employee they will follow in the report
+ self.localcontext.update({
+ 'get_expenses': lambda : [v for k,v in sorted(expenses_info.items())],
+ })
+ return res
+
+
+class report_hr_expense(osv.AbstractModel):
+ _name = 'report.hr_expense.report_expense'
+ _inherit = 'report.abstract_report'
+ _template = 'hr_expense.report_expense'
+ _wrapped_report_class = report_expense
diff --git a/addons/hr_expense/views/report_expense.xml b/addons/hr_expense/views/report_expense.xml
index 27927d6d15c..78a0402a2ad 100644
--- a/addons/hr_expense/views/report_expense.xml
+++ b/addons/hr_expense/views/report_expense.xml
@@ -2,7 +2,7 @@
<odoo>
<template id="report_expense">
<t t-call="report.html_container">
- <t t-foreach="docs" t-as="o">
+ <t t-foreach="get_expenses()" t-as="o">
<t t-call="report.external_layout">
<div class="page">
<h2>HR Expenses</h2>
@@ -10,25 +10,26 @@
<div class="row mt32 mb32">
<div class="col-xs-3">
<strong>Employee:</strong>
- <p t-field="o.employee_id.name"/>
+ <p t-esc="o['employee_name']"/>
</div>
<div class="col-xs-3">
- <strong>Date:</strong>
- <p t-field="o.date"/>
+ <strong>Manager:</strong>
+ <p t-esc="o['validator_name']"/>
</div>
<div class="col-xs-3">
- <strong>Description:</strong>
- <p t-field="o.name"/>
+ <strong>Status:</strong>
+ <p t-esc="o['state']"/>
</div>
<div class="col-xs-3">
- <strong>Validated By:</strong>
- <p t-field="o.employee_id.parent_id.name"/>
+ <strong>Currency:</strong>
+ <p t-field="o['currency'].name"/>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
+ <th>Date</th>
<th>Name</th>
<th>Unit Price</th>
<th class="text-center">Qty</th>
@@ -38,35 +39,46 @@
</tr>
</thead>
<tbody>
- <tr>
- <td>
- <span t-field="o.name"/>
- <span t-field="o.analytic_account_id.name"/>
- </td>
- <td>
- <span t-field="o.unit_amount"/>
- </td>
- <td class="text-center">
- <span t-field="o.quantity"/>
- </td>
- <td groups="product.group_uom">
- <span t-field="o.product_uom_id"/>
- </td>
- <td>
- <t t-foreach="o.tax_ids" t-as="tax">
- <t t-if="tax.description">
- <span t-field="tax.description"/>
- </t>
- <t t-if="not tax.description">
- <span t-field="tax.name"/>
- </t>
- </t>
- </td>
- <td class="text-right">
- <span t-field="o.total_amount"
- t-field-options='{"widget": "monetary", "display_currency":"o.currency_id"}'/>
- </td>
- </tr>
+ <t t-foreach="o['lines']" t-as="line">
+ <tr>
+ <td>
+ <span t-field='line.date'/>
+ <t t-if="line.description">
+ <sup><span t-esc="o['notes_index'][line.id]"/></sup>
+ </t>
+ </td>
+ <td>
+ <span t-field="line.name"/>
+ <t t-if="line.analytic_account_id.name">
+ (<span t-field="line.analytic_account_id.name"/>)
+ </t>
+ </td>
+ <td>
+ <span t-field="line.unit_amount"
+ t-field-options='{"widget": "monetary", "display_currency":"line.currency_id"}'/>
+ </td>
+ <td class="text-center">
+ <span t-field="line.quantity"/>
+ </td>
+ <td groups="product.group_uom">
+ <span t-field="line.product_uom_id"/>
+ </td>
+ <td>
+ <t t-foreach="line.tax_ids" t-as="tax">
+ <t t-if="tax.description">
+ <span t-field="tax.description"/>
+ </t>
+ <t t-if="not tax.description">
+ <span t-field="tax.name"/>
+ </t>
+ </t>
+ </td>
+ <td class="text-right">
+ <span t-field="line.total_amount"
+ t-field-options='{"widget": "monetary", "display_currency":"line.currency_id"}'/>
+ </td>
+ </tr>
+ </t>
</tbody>
</table>
@@ -76,15 +88,18 @@
<tr class="border-black">
<td><strong>Total</strong></td>
<td class="text-right">
- <span t-field="o.total_amount"
- t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
+ <span t-esc="formatLang(o['total_amount'], currency_obj=o['currency'])"/>
</td>
</tr>
</table>
</div>
</div>
-
- <p t-field="o.description"/>
+ <t t-foreach="o['notes']" t-as="note">
+ <span t-esc="note['index']"/>. <span t-esc="note['description']"/>
+ <br/>
+ </t>
+ <br/>
+ <br/>
<p>Certified honest and conform,<br/>(Date and signature).<br/><br/></p>
<p>This document must be dated and signed for reimbursement.</p>
</div>
diff --git a/addons/hr_payroll/report/report_payslip_details.py b/addons/hr_payroll/report/report_payslip_details.py
index ba31bd8b100..f5858f8e7ef 100644
--- a/addons/hr_payroll/report/report_payslip_details.py
+++ b/addons/hr_payroll/report/report_payslip_details.py
@@ -22,7 +22,7 @@ class payslip_details_report(report_sxw.rml_parse):
if not rule_categories:
return []
if rule_categories[0].parent_id:
- rule_categories.insert(0, rule_categories[0].parent_id)
+ rule_categories = rule_categories[0].parent_id | rule_categories
get_recursive_parent(rule_categories)
return rule_categories
diff --git a/addons/hw_blackbox_be/__init__.py b/addons/hw_blackbox_be/__init__.py
new file mode 100644
index 00000000000..bf7fb7b45c1
--- /dev/null
+++ b/addons/hw_blackbox_be/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import controllers
diff --git a/addons/hw_blackbox_be/__openerp__.py b/addons/hw_blackbox_be/__openerp__.py
new file mode 100644
index 00000000000..d33930e2143
--- /dev/null
+++ b/addons/hw_blackbox_be/__openerp__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': 'Blackbox Hardware Driver',
+ 'version': '1.0',
+ 'category': 'Hardware Drivers',
+ 'sequence': 6,
+ 'summary': 'Hardware Driver for Belgian Fiscal Data Modules',
+ 'website': 'https://www.odoo.com/page/point-of-sale',
+ 'description': """
+Fiscal Data Module Hardware Driver
+==================================
+
+This module allows a Point Of Sale client to communicate with a
+connected Belgian Fiscal Data Module.
+""",
+ 'author': 'OpenERP SA',
+ 'depends': ['hw_proxy'],
+ 'external_dependencies': {'python': ['serial']},
+ 'test': [
+ ],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/addons/hw_blackbox_be/controllers/__init__.py b/addons/hw_blackbox_be/controllers/__init__.py
new file mode 100644
index 00000000000..f48716d3a69
--- /dev/null
+++ b/addons/hw_blackbox_be/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import main
diff --git a/addons/hw_blackbox_be/controllers/main.py b/addons/hw_blackbox_be/controllers/main.py
new file mode 100644
index 00000000000..fbc7664cbc0
--- /dev/null
+++ b/addons/hw_blackbox_be/controllers/main.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+import serial
+from os import listdir
+from threading import Thread, Lock
+
+from openerp import http
+
+import openerp.addons.hw_proxy.controllers.main as hw_proxy
+
+_logger = logging.getLogger(__name__)
+
+class Blackbox(Thread):
+ def __init__(self):
+ Thread.__init__(self)
+ self.blackbox_lock = Lock()
+ self.set_status('connecting')
+ self.device_path = self._find_device_path_by_probing()
+
+ def set_status(self, status, messages=[]):
+ self.status = {
+ 'status': status,
+ 'messages': messages
+ }
+
+ def get_status(self):
+ return self.status
+
+ # There is no real way to find a serial device, all you can really
+ # find is the name of the serial to usb interface, which in the
+ # case of the blackbox is not defined because it doesn't always
+ # come with it's own interface (eg. Retail Cleancash SC-B). So, in
+ # order to differentiate between other devices like this, what
+ # we'll do is probe every serial device with an FDM status
+ # request. The first device to give an answer that makes sense
+ # wins.
+ def _find_device_path_by_probing(self):
+ path = "/dev/serial/by-id/"
+ probe_message = self._wrap_low_level_message_around("S000")
+
+ try:
+ devices = listdir(path)
+ except OSError:
+ _logger.warning(path + " doesn't exist")
+ else:
+ for device in listdir(path):
+ path_to_device = path + device
+ _logger.debug("Probing " + device)
+
+ if self._send_to_blackbox(probe_message, 21, path_to_device, just_wait_for_ack=True):
+ _logger.info(device + " will be used as the blackbox")
+ self.set_status("connected", [device])
+ return path_to_device
+
+ _logger.warning("Blackbox could not be found")
+ self.set_status("error", ["Couldn't find the Fiscal Data Module"])
+ return ""
+
+ def _lrc(self, msg):
+ lrc = 0
+
+ for character in msg:
+ byte = ord(character)
+ lrc = (lrc + byte) & 0xFF
+
+ lrc = ((lrc ^ 0xFF) + 1) & 0xFF
+
+ return lrc
+
+ def _wrap_low_level_message_around(self, high_level_message):
+ bcc = self._lrc(high_level_message)
+ high_level_message_bytes = map(ord, high_level_message)
+
+ low_level_message = bytearray()
+ low_level_message.append(0x02)
+ low_level_message.extend(high_level_message_bytes)
+ low_level_message.append(0x03)
+ low_level_message.append(bcc)
+
+ return low_level_message
+
+ def _send_and_wait_for_ack(self, packet, serial):
+ ack = 0
+ MAX_RETRIES = 1
+
+ while ack != 0x06 and int(chr(packet[4])) < MAX_RETRIES:
+ serial.write(packet)
+ ack = serial.read(1)
+
+ # This violates the principle that we do high level
+ # client-side and low level posbox-side but the retry
+ # counter is always in a fixed position in the high level
+ # message so it's safe to do it. Also it would be a pain
+ # to have to throw this all the way back to js just so it
+ # can increment the retry counter and then try again.
+ packet = packet[:4] + str(int(packet[4]) + 1) + packet[5:]
+
+ if ack:
+ ack = ord(ack)
+ else:
+ _logger.warning("did not get ACK, retrying...")
+ ack = 0
+
+ if ack == 0x06:
+ return True
+ else:
+ _logger.error("retried " + str(MAX_RETRIES) + " times without receiving ACK, is blackbox properly connected?")
+ return False
+
+ def _send_to_blackbox(self, packet, response_size, device_path, just_wait_for_ack=False):
+ if not device_path:
+ return ""
+
+ ser = serial.Serial(port=device_path,
+ baudrate=19200,
+ timeout=3)
+ MAX_NACKS = 1
+ got_response = False
+ sent_nacks = 0
+
+ if self._send_and_wait_for_ack(packet, ser):
+ if just_wait_for_ack:
+ return True
+
+ while not got_response and sent_nacks < MAX_NACKS:
+ stx = ser.read(1)
+ response = ser.read(response_size)
+ etx = ser.read(1)
+ bcc = ser.read(1)
+
+ if stx == chr(0x02) and etx == chr(0x03) and bcc and self._lrc(response) == ord(bcc):
+ got_response = True
+ ser.write(chr(0x06))
+ else:
+ _logger.warning("received ACK but not a valid response, sending NACK...")
+ sent_nacks += 1
+ ser.write(chr(0x15))
+
+ if not got_response:
+ _logger.error("sent " + str(MAX_NACKS) + " NACKS without receiving response, giving up.")
+ return ""
+
+ ser.close()
+ return response
+ else:
+ ser.close()
+ return ""
+
+blackbox_thread = Blackbox()
+hw_proxy.drivers['fiscal_data_module'] = blackbox_thread
+
+class BlackboxDriver(hw_proxy.Proxy):
+ @http.route('/hw_proxy/request_blackbox/', type='json', auth='none', cors='*')
+ def request_blackbox(self, high_level_message, response_size):
+ to_send = blackbox_thread._wrap_low_level_message_around(high_level_message)
+
+ with blackbox_thread.blackbox_lock:
+ response = blackbox_thread._send_to_blackbox(to_send, response_size, blackbox_thread.device_path)
+
+ return response
diff --git a/addons/im_livechat/controllers/main.py b/addons/im_livechat/controllers/main.py
index 43e547e2fb1..30ae212c8b2 100644
--- a/addons/im_livechat/controllers/main.py
+++ b/addons/im_livechat/controllers/main.py
@@ -30,7 +30,8 @@ class LivechatController(http.Controller):
username = kwargs.get("username", _("Visitor"))
channel = request.env['im_livechat.channel'].sudo().browse(channel_id)
info = request.env['im_livechat.channel'].get_livechat_info(channel.id, username=username)
- return request.render('im_livechat.loader', {'info': info, 'web_session_required': True}) if info['available'] else False
+ js = request.render('im_livechat.loader', {'info': info, 'web_session_required': True})
+ return request.make_response(js, headers=[('Content-Type', 'application/javascript')])
@http.route('/im_livechat/init', type='json', auth="public")
def livechat_init(self, channel_id):
diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py
index e15d4212a43..ac894a2c7ab 100644
--- a/addons/im_livechat/models/im_livechat_channel.py
+++ b/addons/im_livechat/models/im_livechat_channel.py
@@ -195,7 +195,7 @@ class ImLivechatChannel(models.Model):
@api.model
def get_livechat_info(self, channel_id, username='Visitor'):
info = {}
- info['available'] = len(self.browse(channel_id).get_available_users())
+ info['available'] = len(self.browse(channel_id).get_available_users()) > 0
if info['available']:
info['server_url'] = self.env['ir.config_parameter'].get_param('web.base.url')
info['options'] = self.sudo().get_channel_infos(channel_id)
diff --git a/addons/im_livechat/views/im_livechat_channel_templates.xml b/addons/im_livechat/views/im_livechat_channel_templates.xml
index d97df04989e..0efd2310a14 100644
--- a/addons/im_livechat/views/im_livechat_channel_templates.xml
+++ b/addons/im_livechat/views/im_livechat_channel_templates.xml
@@ -143,7 +143,6 @@
<t t-set="info" t-value="request.env['im_livechat.channel'].get_livechat_info(channel)"/>
</t>
- <t t-if="info['available']" t-translation="off">
document.addEventListener("DOMContentLoaded", function(event) {
<t t-if="web_session_required">
odoo.define('web.session', function (require) {
@@ -154,6 +153,7 @@
</t>
odoo.define('im_livechat.livesupport', function (require) {
+ <t t-if="info['available']" t-translation="off">
var im_livechat = require('im_livechat.im_livechat');
var button = new im_livechat.LivechatButton(
$('body'),
@@ -162,9 +162,9 @@
);
button.appendTo($('body'));
window.livechat_button = button;
+ </t>
});
});
- </t>
</template>
diff --git a/addons/l10n_be_invoice_bba/invoice.py b/addons/l10n_be_invoice_bba/invoice.py
index d20dab8cfd1..8e6bd53d4d3 100644
--- a/addons/l10n_be_invoice_bba/invoice.py
+++ b/addons/l10n_be_invoice_bba/invoice.py
@@ -198,7 +198,7 @@ class account_invoice(osv.osv):
required=True, readonly=True),
}
_constraints = [
- (_check_communication, 'Invalid BBA Structured Communication !', ['Communication']),
+ (_check_communication, 'Invalid BBA Structured Communication !', ['reference', 'reference_type']),
]
account_invoice()
diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py
index 2846ac0598c..0aae3dc611c 100644
--- a/addons/mail/models/mail_mail.py
+++ b/addons/mail/models/mail_mail.py
@@ -167,7 +167,7 @@ class MailMail(models.Model):
if partner:
email_to = [formataddr((partner.name, partner.email))]
else:
- email_to = tools.email_split(self.email_to)
+ email_to = tools.email_split_and_format(self.email_to)
return email_to
@api.multi
diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py
index 0680461bd37..eff036c3229 100644
--- a/addons/mail/models/mail_template.py
+++ b/addons/mail/models/mail_template.py
@@ -38,7 +38,7 @@ def format_tz(pool, cr, uid, dt, tz=False, format=False, context=None):
format_date = lang_params.get("date_format", '%B-%d-%Y')
format_time = lang_params.get("time_format", '%I-%M %p')
- fdate = ts.strftime(format_date)
+ fdate = ts.strftime(format_date).decode('utf-8')
ftime = ts.strftime(format_time)
return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py
index 98e9c8ad3c1..f70a929464c 100644
--- a/addons/mail/tests/common.py
+++ b/addons/mail/tests/common.py
@@ -39,6 +39,7 @@ class TestMail(common.SavepointCase):
cls.env['ir.mail_server']._patch_method('build_email', build_email)
cls.env['ir.mail_server']._patch_method('send_email', send_email)
+
# User groups
user_group_employee = cls.env.ref('base.group_user')
user_group_portal = cls.env.ref('base.group_portal')
diff --git a/addons/mail/tests/test_mail_gateway.py b/addons/mail/tests/test_mail_gateway.py
index 41ae67ba369..11b9bb3b9a7 100644
--- a/addons/mail/tests/test_mail_gateway.py
+++ b/addons/mail/tests/test_mail_gateway.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
+from email.utils import formataddr
+
from .common import TestMail
from openerp.tools import mute_logger
import socket
@@ -311,7 +313,7 @@ class TestMailgateway(TestMail):
'message_process: incoming email on Partners alias should send a bounce email')
self.assertIn('New Frogs', self._mails[0].get('subject'),
'message_process: bounce email on Partners alias should contain the original subject')
- self.assertIn('test.sylvie.lelitre@agrolait.com', self._mails[0].get('email_to'),
+ self.assertIn(formataddr(('Sylvie Lelitre', 'test.sylvie.lelitre@agrolait.com')), self._mails[0].get('email_to'),
'message_process: bounce email on Partners alias should have original email sender as recipient')
@mute_logger('openerp.addons.mail.models.mail_thread', 'openerp.models', 'openerp.addons.mail.models.mail_mail')
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index 54d8e599240..73429e48962 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -96,6 +96,7 @@
context="{
'default_composition_mode': 'mass_mail',
'default_partner_to': '${object.id or \'\'}',
+ 'default_active_domain': [['id', 'in', context.get('active_ids', [])]],
}"/>
</data>
</openerp>
diff --git a/addons/mass_mailing/i18n/mass_mailing.pot b/addons/mass_mailing/i18n/mass_mailing.pot
index 41113b5ea12..40e25c0bfa8 100644
--- a/addons/mass_mailing/i18n/mass_mailing.pot
+++ b/addons/mass_mailing/i18n/mass_mailing.pot
@@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server 9.0\n"
+"Project-Id-Version: Odoo Server 9.0c\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-10-09 09:20+0000\n"
-"PO-Revision-Date: 2015-10-09 09:20+0000\n"
+"POT-Creation-Date: 2016-01-06 07:17+0000\n"
+"PO-Revision-Date: 2016-01-06 07:17+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -640,11 +640,15 @@ msgstr ""
#. module: mass_mailing
#: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets
-#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
msgid "Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/>"
msgstr ""
#. module: mass_mailing
+#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
+msgid "Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br>"
+msgstr ""
+
+#. module: mass_mailing
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mail_statistics_search
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_campaign_search
#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_contact_search
@@ -660,7 +664,7 @@ msgstr ""
#. module: mass_mailing
#: model:ir.ui.view,arch_db:mass_mailing.FieldTextHtmlInline
-msgid "Help"
+msgid "Here you can design your own template from scratch by selecting your favorite theme."
msgstr ""
#. module: mass_mailing
@@ -1024,11 +1028,6 @@ msgid "Messages"
msgstr ""
#. module: mass_mailing
-#: model:ir.model.fields,help:mass_mailing.field_mail_mass_mailing_contact_message_ids
-msgid "Messages and communication history"
-msgstr ""
-
-#. module: mass_mailing
#: model:ir.ui.view,arch_db:mass_mailing.email_designer_snippets
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
msgid "Michael Fletcher"
@@ -1552,6 +1551,11 @@ msgid "This mass mailing is scheduled to"
msgstr ""
#. module: mass_mailing
+#: model:ir.ui.view,arch_db:mass_mailing.view_mail_mass_mailing_form
+msgid "This will send the email to all recipients. Do you still want to proceed ?"
+msgstr ""
+
+#. module: mass_mailing
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_campaign_total
#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_total
msgid "Total"
@@ -1615,11 +1619,6 @@ msgid "We would like you to know that your order has shipped! To track your orde
msgstr ""
#. module: mass_mailing
-#: model:ir.model.fields,field_description:mass_mailing.field_mail_mass_mailing_contact_website_message_ids
-msgid "Website Messages"
-msgstr ""
-
-#. module: mass_mailing
#: model:ir.model.fields,field_description:mass_mailing.field_mass_mailing_config_settings_group_website_popup_on_exit
msgid "Website Pop-up"
msgstr ""
@@ -1635,11 +1634,6 @@ msgid "Website Popup Redirect URL"
msgstr ""
#. module: mass_mailing
-#: model:ir.model.fields,help:mass_mailing.field_mail_mass_mailing_contact_website_message_ids
-msgid "Website communication history"
-msgstr ""
-
-#. module: mass_mailing
#: model:mail.mass_mailing,body_html:mass_mailing.mass_mail_1
msgid "You get a 20% discount for signing up!"
msgstr ""
diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py
index cc9928ca8ac..e9edd3a9824 100644
--- a/addons/mass_mailing/models/mail_mail.py
+++ b/addons/mass_mailing/models/mail_mail.py
@@ -3,7 +3,6 @@
import re
import urlparse
-import re
import werkzeug.urls
from openerp import tools
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index f19b98da6cf..193aede7d52 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -683,6 +683,8 @@ class MassMailing(osv.Model):
value['mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(mailing_list_ids)
else:
value['mailing_domain'] = "[('list_id', '=', False)]"
+ elif 'opt_out' in self.pool[mailing_model]._fields:
+ value['mailing_domain'] = "[('opt_out', '=', False)]"
else:
value['mailing_domain'] = []
value['body_html'] = "on_change_model_and_list"
diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml
index 885a923396a..2748175bb91 100644
--- a/addons/mass_mailing/views/mass_mailing.xml
+++ b/addons/mass_mailing/views/mass_mailing.xml
@@ -379,7 +379,8 @@
<form string="Mass Mailing">
<header style="min-height:31px;">
<button name="action_test_mailing" type="object" class="oe_highlight" string="Test Mailing"/>
- <button name="put_in_queue" type="object" attrs="{'invisible': ['|', ('state', 'in', ('in_queue', 'done'))]}" class="oe_highlight" string="Send to All"/>
+ <button name="put_in_queue" type="object" attrs="{'invisible': ['|', ('state', 'in', ('in_queue', 'done'))]}" class="oe_highlight" string="Send to All"
+ confirm="This will send the email to all recipients. Do you still want to proceed ?"/>
<button name="cancel_mass_mailing" type="object" attrs="{'invisible': [('state', '!=', 'in_queue')]}" class="oe_highlight" string="Cancel"/>
<button name="retry_failed_mail" type="object" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}" class="oe_highlight" string="Retry"/>
diff --git a/addons/payment_buckaroo/models/buckaroo.py b/addons/payment_buckaroo/models/buckaroo.py
index 59d9e05f902..337149e1cbd 100644
--- a/addons/payment_buckaroo/models/buckaroo.py
+++ b/addons/payment_buckaroo/models/buckaroo.py
@@ -1,6 +1,7 @@
# -*- coding: utf-'8' "-*-"
from hashlib import sha1
import logging
+import urllib
import urlparse
from openerp.addons.payment.models.payment_acquirer import ValidationError
@@ -12,6 +13,16 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
+def normalize_keys_upper(data):
+ """Set all keys of a dictionnary to uppercase
+
+ Buckaroo parameters names are case insensitive
+ convert everything to upper case to be able to easily detected the presence
+ of a parameter by checking the uppercase key only
+ """
+ return dict((key.upper(), val) for key, val in data.items())
+
+
class AcquirerBuckaroo(osv.Model):
_inherit = 'payment.acquirer'
@@ -61,17 +72,22 @@ class AcquirerBuckaroo(osv.Model):
values = dict(values or {})
if inout == 'out':
- if 'BRQ_SIGNATURE' in values:
- del values['BRQ_SIGNATURE']
- items = sorted((k.upper(), v) for k, v in values.items())
- sign = ''.join('%s=%s' % (k, v) for k, v in items)
+ for key in values.keys():
+ # case insensitive keys
+ if key.upper() == 'BRQ_SIGNATURE':
+ del values[key]
+ break
+
+ items = sorted(values.items(), key=lambda (x, y): x.lower())
+ sign = ''.join('%s=%s' % (k, urllib.unquote_plus(v)) for k, v in items)
else:
sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
#Add the pre-shared secret key at the end of the signature
sign = sign + acquirer.brq_secretkey
if isinstance(sign, str):
+ # TODO: remove me? should not be used
sign = urlparse.parse_qsl(sign)
- shasign = sha1(sign).hexdigest()
+ shasign = sha1(sign.encode('utf-8')).hexdigest()
return shasign
@@ -117,6 +133,8 @@ class TxBuckaroo(osv.Model):
def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
""" Given a data dict coming from buckaroo, verify it and find the related
transaction record. """
+ origin_data = dict(data)
+ data = normalize_keys_upper(data)
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
if not reference or not pay_id or not shasign:
error_msg = _('Buckaroo: received data with missing reference (%s) or pay_id (%s) or shasign (%s)') % (reference, pay_id, shasign)
@@ -135,7 +153,7 @@ class TxBuckaroo(osv.Model):
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
#verify shasign
- shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out' ,data)
+ shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out', origin_data)
if shasign_check.upper() != shasign.upper():
error_msg = _('Buckaroo: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)
_logger.info(error_msg)
@@ -145,7 +163,7 @@ class TxBuckaroo(osv.Model):
def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
invalid_parameters = []
-
+ data = normalize_keys_upper(data)
if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
# check what is buyed
@@ -157,6 +175,7 @@ class TxBuckaroo(osv.Model):
return invalid_parameters
def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
+ data = normalize_keys_upper(data)
status_code = int(data.get('BRQ_STATUSCODE','0'))
if status_code in self._buckaroo_valid_tx_status:
tx.write({
diff --git a/addons/payment_buckaroo/tests/test_buckaroo.py b/addons/payment_buckaroo/tests/test_buckaroo.py
index 3a9773d34f0..b23de66c49c 100644
--- a/addons/payment_buckaroo/tests/test_buckaroo.py
+++ b/addons/payment_buckaroo/tests/test_buckaroo.py
@@ -122,7 +122,7 @@ class BuckarooForm(BuckarooCommon):
'BRQ_CURRENCY': u'EUR',
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
'BRQ_INVOICENUMBER': u'SO004',
- 'BRQ_PAYMENT': u'573311D081B04069BD6336001611DBD4',
+ 'brq_payment': u'573311D081B04069BD6336001611DBD4',
'BRQ_PAYMENT_METHOD': u'paypal',
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
@@ -130,7 +130,7 @@ class BuckarooForm(BuckarooCommon):
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
- 'BRQ_SIGNATURE': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
+ 'Brq_signature': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
'BRQ_STATUSCODE': u'190',
'BRQ_STATUSCODE_DETAIL': u'S001',
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index cf1d7ecb2c9..8ba3e79b05c 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -645,7 +645,7 @@ class pos_order(osv.osv):
_order = "id desc"
def _amount_line_tax(self, cr, uid, line, fiscal_position_id, context=None):
- taxes = line.product_id.taxes_id.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
+ taxes = line.tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
if fiscal_position_id:
taxes = fiscal_position_id.map_tax(taxes)
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
@@ -1399,7 +1399,7 @@ class pos_order_line(osv.osv):
account_tax_obj = self.pool.get('account.tax')
for line in self.browse(cr, uid, ids, context=context):
cur = line.order_id.pricelist_id.currency_id
- taxes = [ tax for tax in line.product_id.taxes_id if tax.company_id.id == line.order_id.company_id.id ]
+ taxes = [ tax for tax in line.tax_ids if tax.company_id.id == line.order_id.company_id.id ]
fiscal_position_id = line.order_id.fiscal_position_id
if fiscal_position_id:
taxes = fiscal_position_id.map_tax(taxes)
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
index ec64b5991a8..5e8c8bf71d1 100644
--- a/addons/point_of_sale/static/src/js/gui.js
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -8,6 +8,7 @@ odoo.define('point_of_sale.gui', function (require) {
var core = require('web.core');
var Model = require('web.DataModel');
+var formats = require('web.formats');
var _t = core._t;
@@ -411,14 +412,16 @@ var Gui = core.Class.extend({
numpad_input: function(buffer, input, options) {
var newbuf = buffer.slice(0);
options = options || {};
+ var newbuf_float = formats.parse_value(newbuf, {type: "float"}, 0);
+ var decimal_point = _t.database.parameters.decimal_point;
- if (input === '.') {
+ if (input === decimal_point) {
if (options.firstinput) {
newbuf = "0.";
}else if (!newbuf.length || newbuf === '-') {
newbuf += "0.";
- } else if (newbuf.indexOf('.') < 0){
- newbuf = newbuf + '.';
+ } else if (newbuf.indexOf(decimal_point) < 0){
+ newbuf = newbuf + decimal_point;
}
} else if (input === 'CLEAR') {
newbuf = "";
@@ -435,7 +438,7 @@ var Gui = core.Class.extend({
newbuf = '-' + newbuf;
}
} else if (input[0] === '+' && !isNaN(parseFloat(input))) {
- newbuf = '' + ((parseFloat(newbuf) || 0) + parseFloat(input));
+ newbuf = this.chrome.format_currency_no_symbol(newbuf_float + parseFloat(input));
} else if (!isNaN(parseInt(input))) {
if (options.firstinput) {
newbuf = '' + input;
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 70ca8f6d130..33910244d5c 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1092,10 +1092,11 @@ exports.Orderline = Backbone.Model.extend({
clone: function(){
var orderline = new exports.Orderline({},{
pos: this.pos,
- order: null,
+ order: this.order,
product: this.product,
price: this.price,
});
+ orderline.order = null;
orderline.quantity = this.quantity;
orderline.quantityStr = this.quantityStr;
orderline.discount = this.discount;
diff --git a/addons/point_of_sale/static/src/js/popups.js b/addons/point_of_sale/static/src/js/popups.js
index 4d0614bc682..17a3fd6ac7c 100644
--- a/addons/point_of_sale/static/src/js/popups.js
+++ b/addons/point_of_sale/static/src/js/popups.js
@@ -205,6 +205,7 @@ var NumberPopupWidget = PopupWidget.extend({
this._super(options);
this.inputbuffer = '' + (options.value || '');
+ this.decimal_separator = _t.database.parameters.decimal_point;
this.renderElement();
this.firstinput = true;
},
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index ded5088de26..0dae03ddfea 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1300,7 +1300,7 @@ var ClientListScreenWidget = ScreenWidget.extend({
contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
this.toggle_save_button();
- contents.find('.image-uploader').on('change',function(){
+ contents.find('.image-uploader').on('change',function(event){
self.load_image_file(event.target.files[0],function(res){
if (res) {
contents.find('.client-picture img, .client-picture .fa').remove();
@@ -1519,7 +1519,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
event.keyCode === 110 || // Decimal point (numpad)
event.keyCode === 188 || // Comma
event.keyCode === 46 ) { // Numpad dot
- key = '.';
+ key = self.decimal_point;
} else if (event.keyCode >= 48 && event.keyCode <= 57) { // Numbers
key = '' + (event.keyCode - 48);
} else if (event.keyCode === 45) { // Minus
@@ -1578,7 +1578,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
order.selected_paymentline.set_amount(amount);
this.order_changes();
this.render_paymentlines();
- this.$('.paymentline.selected .edit').text(this.inputbuffer);
+ this.$('.paymentline.selected .edit').text(this.format_currency_no_symbol(amount));
}
}
},
@@ -1704,9 +1704,9 @@ var PaymentScreenWidget = ScreenWidget.extend({
this.gui.show_popup('number',{
'title': tip ? _t('Change Tip') : _t('Add Tip'),
- 'value': value,
+ 'value': self.format_currency_no_symbol(value),
'confirm': function(value) {
- order.set_tip(Number(value));
+ order.set_tip(formats.parse_value(value, {type: "float"}, 0));
self.order_changes();
self.render_paymentlines();
}
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 5744f0ea8fb..f15174bbf6c 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -559,7 +559,7 @@
<br />
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
<button class="input-button number-char" data-action='0'>0</button>
- <button class="input-button number-char" data-action='.'>.</button>
+ <button class="input-button number-char" t-att-data-action='widget.decimal_point'><t t-esc='widget.decimal_point'/></button>
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
<img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
</button>
@@ -702,9 +702,6 @@
<div>Served by <t t-esc='receipt.cashier' /></div>
</div>
</t>
- <t t-if='!receipt.customer_count'>
- <div>Guests: <t t-esc='receipt.customer_count' /></div>
- </t>
</div>
<br /><br />
@@ -1006,7 +1003,7 @@
<br />
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
<button class="input-button number-char" data-action='0'>0</button>
- <button class="input-button number-char dot" data-action='.'>.</button>
+ <button class="input-button number-char dot" t-att-data-action='widget.decimal_separator'><t t-esc='widget.decimal_separator'/></button>
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
<img style="pointer-events: none;" src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
</button>
@@ -1298,7 +1295,7 @@
<tr>
<td>Subtotal:</td>
<td class="pos-right-align">
- <t t-esc="widget.format_currency(order.get_subtotal())"/>
+ <t t-esc="widget.format_currency(order.get_total_without_tax())"/>
</td>
</tr>
<t t-foreach="order.get_tax_details()" t-as="taxdetail">
diff --git a/addons/point_of_sale/tools/posbox/configuration/odoo.conf b/addons/point_of_sale/tools/posbox/configuration/odoo.conf
index 1bc0a0d3ba5..bbe21f44c3e 100644
--- a/addons/point_of_sale/tools/posbox/configuration/odoo.conf
+++ b/addons/point_of_sale/tools/posbox/configuration/odoo.conf
@@ -3,4 +3,3 @@ data_dir = /var/run/odoo
log_level = error
logfile = /var/log/odoo/odoo-server.log
pidfile = /var/run/odoo/odoo.pid
-server_wide_modules = web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos \ No newline at end of file
diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
index f9bc0400abc..30be4bd26d3 100755
--- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
+++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init.d/odoo
@@ -26,7 +26,7 @@ test -x $DAEMON || exit 0
set -e
function _start() {
- start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be
}
function _stop() {
diff --git a/addons/pos_restaurant/static/src/xml/floors.xml b/addons/pos_restaurant/static/src/xml/floors.xml
index f84dd3c68c8..8bbddbde6ed 100644
--- a/addons/pos_restaurant/static/src/xml/floors.xml
+++ b/addons/pos_restaurant/static/src/xml/floors.xml
@@ -6,6 +6,9 @@
<t t-if='receipt.table'>
at table <t t-esc='receipt.table' />
</t>
+ <t t-if='receipt.customer_count'>
+ <div>Guests: <t t-esc='receipt.customer_count' /></div>
+ </t>
</t>
</t>
@@ -14,6 +17,9 @@
<t t-if='receipt.table'>
at table <t t-esc='receipt.table' />
</t>
+ <t t-if='receipt.customer_count'>
+ <div>Guests: <t t-esc='receipt.customer_count' /></div>
+ </t>
</t>
</t>
diff --git a/addons/product/product.py b/addons/product/product.py
index 9e19d66cfe1..4d4e355d173 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -571,7 +571,7 @@ class product_template(osv.osv):
if ptype != 'standard_price':
res[product.id] = product[ptype] or 0.0
else:
- company_id = product.env.user.company_id.id
+ company_id = context.get('force_company') or product.env.user.company_id.id
product = product.with_context(force_company=company_id)
res[product.id] = res[product.id] = product.sudo()[ptype]
if ptype == 'list_price':
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index af64e42cbef..b14df01f72d 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -3,7 +3,7 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
-from openerp import api, fields, models, _
+from openerp import api, fields, models, _, SUPERUSER_ID
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
from openerp.tools.float_utils import float_is_zero, float_compare
@@ -150,7 +150,7 @@ class PurchaseOrder(models.Model):
picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
help="This will determine picking type of incoming shipment")
default_location_dest_id_usage = fields.Selection(related='picking_type_id.default_location_dest_id.usage', string='Destination Location Type',\
- help="Technical field used to display the Drop Ship Address")
+ help="Technical field used to display the Drop Ship Address", readonly=True)
group_id = fields.Many2one('procurement.group', string="Procurement Group")
@api.model
@@ -434,6 +434,7 @@ class PurchaseOrderLine(models.Model):
@api.depends('order_id.state', 'move_ids.state')
def _compute_qty_received(self):
+ ProductUom = self.env['product.uom']
for line in self:
if line.order_id.state not in ['purchase', 'done']:
line.qty_received = 0.0
@@ -444,7 +445,10 @@ class PurchaseOrderLine(models.Model):
total = 0.0
for move in line.move_ids:
if move.state == 'done':
- total += move.product_uom_qty
+ if move.product_uom != line.product_uom:
+ total += ProductUom._compute_qty_obj(move.product_uom, move.product_uom_qty, line.product_uom)
+ else:
+ total += move.product_uom_qty
line.qty_received = total
name = fields.Text(string='Description', required=True)
@@ -581,7 +585,11 @@ class PurchaseOrderLine(models.Model):
self.name += '\n' + product_lang.description_purchase
fpos = self.order_id.fiscal_position_id
- self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
+ if self.env.uid == SUPERUSER_ID:
+ company_id = self.env.user.company_id.id
+ self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id))
+ else:
+ self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
self._suggest_quantity()
self._onchange_quantity()
diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py
index 791672b1dfc..b0486950f26 100644
--- a/addons/purchase/stock.py
+++ b/addons/purchase/stock.py
@@ -12,6 +12,14 @@ class stock_picking(osv.osv):
readonly=True, relation="many2one"),
}
+ def _prepare_values_extra_move(self, cr, uid, op, product, remaining_qty, context=None):
+ res = super(stock_picking, self)._prepare_values_extra_move(cr, uid, op, product, remaining_qty, context=context)
+ for m in op.linked_move_operation_ids:
+ if m.move_id.purchase_line_id and m.move_id.product_id == product:
+ res['purchase_line_id'] = m.move_id.purchase_line_id.id
+ break
+ return res
+
class stock_move(osv.osv):
_inherit = 'stock.move'
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 88b331c99d8..84a318a1609 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -740,7 +740,8 @@ class SaleOrderLine(models.Model):
quantity=self.product_uom_qty,
date_order=self.order_id.date_order,
pricelist=self.order_id.pricelist_id.id,
- uom=self.product_uom.id
+ uom=self.product_uom.id,
+ fiscal_position=self.env.context.get('fiscal_position')
)
self.price_unit = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id)
@@ -750,6 +751,15 @@ class SaleOrderLine(models.Model):
raise UserError(_('You can not remove a sale order line.\nDiscard changes and try setting the quantity to 0.'))
return super(SaleOrderLine, self).unlink()
+ def onchange_product_uom(self, cursor, user, ids, pricelist, product, qty=0,
+ uom=False, qty_uos=0, uos=False, name='', partner_id=False,
+ lang=False, update_tax=True, date_order=False, fiscal_position=False, context=None):
+ ctx = dict(context or {}, fiscal_position=fiscal_position)
+ return self.product_uom_change(cursor, user, ids, pricelist, product,
+ qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
+ partner_id=partner_id, lang=lang, update_tax=update_tax,
+ date_order=date_order, context=ctx)
+
@api.multi
def _get_delivered_qty(self):
'''
diff --git a/addons/sale/sale_analytic.py b/addons/sale/sale_analytic.py
index b28de1ad0a9..cc7de3a1a98 100644
--- a/addons/sale/sale_analytic.py
+++ b/addons/sale/sale_analytic.py
@@ -12,7 +12,8 @@ class SaleOrderLine(models.Model):
def _compute_analytic(self, domain=None):
lines = {}
if not domain:
- domain = [('so_line', 'in', self.ids), ('unit_amount', '<=', 0.0)]
+ # To filter on analyic lines linked to an expense
+ domain = [('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
data = self.env['account.analytic.line'].read_group(
domain,
['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index d3b4f9ffdb9..0387dbd6783 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -4,6 +4,7 @@
from datetime import datetime, timedelta
from openerp import api, fields, models, _
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
+from openerp.exceptions import UserError
class SaleOrder(models.Model):
@@ -203,7 +204,7 @@ class SaleOrderLine(models.Model):
mto_route_id = False
try:
mto_route_id = self.env['stock.warehouse']._get_mto_route()
- except models.except_orm:
+ except UserError:
# if route MTO not found in ir_model_data, we treat the product as in MTS
pass
if mto_route_id and mto_route_id in product_routes.ids:
diff --git a/addons/sale_timesheet/models/sale_timesheet.py b/addons/sale_timesheet/models/sale_timesheet.py
index fbcd3295ed6..964a79ea421 100644
--- a/addons/sale_timesheet/models/sale_timesheet.py
+++ b/addons/sale_timesheet/models/sale_timesheet.py
@@ -158,7 +158,8 @@ class SaleOrderLine(models.Model):
@api.multi
def _compute_analytic(self, domain=None):
if not domain:
- domain = [('so_line', 'in', self.ids), '|', ('unit_amount', '<=', 0.0), ('is_timesheet', '=', True)]
+ # To filter on analyic lines linked to an expense
+ domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('is_timesheet', '=', True)]
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
@api.model
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index be040fb2d54..999bbcb3a82 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -1556,7 +1556,6 @@ class stock_picking(models.Model):
for pack in pick.pack_operation_ids:
if pack.product_id and pack.product_id.tracking != 'none':
raise UserError(_('Some products require lots, so you need to specify those first!'))
-
view = data_obj.xmlid_to_res_id(cr, uid, 'stock.view_immediate_transfer')
wiz_id = self.pool['stock.immediate.transfer'].create(cr, uid, {'pick_id': pick.id}, context=context)
return {
@@ -4206,7 +4205,7 @@ class stock_package(osv.osv):
quant_obj = self.pool.get('stock.quant')
for package in self.browse(cr, uid, ids, context=context):
quant_ids = [quant.id for quant in package.quant_ids]
- quant_obj.write(cr, uid, quant_ids, {'package_id': package.parent_id.id or False}, context=context)
+ quant_obj.write(cr, SUPERUSER_ID, quant_ids, {'package_id': package.parent_id.id or False}, context=context)
children_package_ids = [child_package.id for child_package in package.children_ids]
self.write(cr, uid, children_package_ids, {'parent_id': package.parent_id.id or False}, context=context)
#delete current package since it contains nothing anymore
diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py
index bfc8fd1a6b9..750866ccbde 100644
--- a/addons/stock_account/stock_account.py
+++ b/addons/stock_account/stock_account.py
@@ -380,7 +380,7 @@ class stock_move(osv.osv):
def _store_average_cost_price(self, cr, uid, move, context=None):
''' move is a browe record '''
product_obj = self.pool.get('product.product')
- if any([q.qty <= 0 for q in move.quant_ids]):
+ if any([q.qty <= 0 for q in move.quant_ids]) or move.product_qty == 0:
#if there is a negative quant, the standard price shouldn't be updated
return
#Note: here we can't store a quant.cost directly as we may have moved out 2 units (1 unit to 5€ and 1 unit to 7€) and in case of a product return of 1 unit, we can't know which of the 2 costs has to be used (5€ or 7€?). So at that time, thanks to the average valuation price we are storing we will valuate it at 6€
diff --git a/addons/stock_calendar/procurement.py b/addons/stock_calendar/procurement.py
index 515eccdd598..328b105c053 100644
--- a/addons/stock_calendar/procurement.py
+++ b/addons/stock_calendar/procurement.py
@@ -30,8 +30,8 @@ class procurement_order(osv.osv):
date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
purchase_date, delivery_date = self._get_previous_dates(cr, uid, orderpoint, date_planned, context=context)
if purchase_date and delivery_date:
- self.write(cr, uid, {'next_delivery_date': self._convert_to_UTC(cr, uid, delivery_date, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'next_purchase_date': self._convert_to_UTC(cr, uid, purchase_date, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT),}, context=context)
+ self.write(cr, uid, {'next_delivery_date': delivery_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+ 'next_purchase_date': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),}, context=context)
@api.v8
def _get_purchase_order_date(self, schedule_date):
@@ -105,8 +105,6 @@ class procurement_order(osv.osv):
att_obj = self.pool.get('resource.calendar.attendance')
context = context or {}
context['no_round_hours'] = True
- # Date should be converted to the correct timezone
- start_date = self._convert_to_tz(cr, uid, start_date, context=context)
# First check if the orderpoint has a Calendar as it should be delivered at this calendar date
purchase_date = False
delivery_date = start_date
@@ -135,8 +133,8 @@ class procurement_order(osv.osv):
context = context or {}
context['no_round_hours'] = True
if not new_date:
- new_date = self._convert_to_tz(cr, uid, datetime.utcnow(), context=context)
- now_date = self._convert_to_tz(cr, uid, datetime.utcnow(), context=context)
+ new_date = datetime.utcnow()
+ now_date = datetime.utcnow()
# Search first calendar day (without group)
res = calendar_obj._schedule_days(cr, uid, orderpoint.calendar_id.id, 1, new_date, compute_leaves=True, context=context)
@@ -178,21 +176,6 @@ class procurement_order(osv.osv):
return (date1, res[0][1])
return (False, False)
- def _convert_to_tz(self, cr, uid, date, context=None):
- if not context or not context.get('tz'):
- return date
- utc_date = pytz.UTC.localize(date)
- timezone = pytz.timezone(context['tz'])
- return utc_date.astimezone(timezone)
-
- def _convert_to_UTC(self, cr, uid, date, context=None):
- """
- The date should be timezone aware
- """
- if not context or not context.get('tz'):
- return date
- return date.astimezone(pytz.UTC)
-
def _get_group(self, cr, uid, orderpoint, context=None):
"""
Will return the groups and the end dates of the intervals of the purchase calendar
@@ -207,15 +190,13 @@ class procurement_order(osv.osv):
context = context or {}
context['no_round_hours'] = True
date = False
- now_date = self._convert_to_tz(cr, uid, datetime.utcnow(), context=context)
+ now_date = datetime.utcnow()
res_intervals = []
if orderpoint.purchase_calendar_id and orderpoint.purchase_calendar_id.attendance_ids:
if orderpoint.last_execution_date:
new_date = datetime.strptime(orderpoint.last_execution_date, DEFAULT_SERVER_DATETIME_FORMAT)
else:
new_date = datetime.utcnow()
- # Convert to timezone of user
- new_date = self._convert_to_tz(cr, uid, new_date, context=context)
intervals = calendar_obj._schedule_days(cr, uid, orderpoint.purchase_calendar_id.id, 1, new_date, compute_leaves=True, context=context)
for interval in intervals:
# If last execution date, interval should start after it in order not to execute the same orderpoint twice
@@ -285,8 +266,8 @@ class procurement_order(osv.osv):
date = res_group[1]
subtract_qty = orderpoint_obj.subtract_procurements_from_orderpoints(cr, uid, [x.id for x in ops_dict[key]], context=context)
first_op = True
- ndelivery = date and self._convert_to_UTC(cr, uid, date, context=context) or False
- npurchase = res_group[3] and self._convert_to_UTC(cr, uid, res_group[3], context=context) or False
+ ndelivery = date
+ npurchase = res_group[3]
for op in ops_dict[key]:
try:
prods = prod_qty[op.product_id.id]['virtual_available']
diff --git a/addons/stock_calendar/resource.py b/addons/stock_calendar/resource.py
index 7fee8720396..a52c8e6fcc6 100644
--- a/addons/stock_calendar/resource.py
+++ b/addons/stock_calendar/resource.py
@@ -48,21 +48,16 @@ class resource_calendar(osv.osv):
leave intervals
"""
resource_calendar = self.browse(cr, uid, id, context=context)
- proc_obj = self.pool.get("procurement.order")
leaves = []
for leave in resource_calendar.leave_ids:
if leave.resource_id and not resource_id == leave.resource_id.id:
continue
- date_from_db = datetime.datetime.strptime(leave.date_from, DEFAULT_SERVER_DATETIME_FORMAT)
- date_from = proc_obj._convert_to_tz(cr, uid, date_from_db, context=context)
+ date_from = datetime.datetime.strptime(leave.date_from, DEFAULT_SERVER_DATETIME_FORMAT)
if end_datetime and date_from > end_datetime:
continue
-
- date_to_db = datetime.datetime.strptime(leave.date_to, DEFAULT_SERVER_DATETIME_FORMAT)
- date_to = proc_obj._convert_to_tz(cr, uid, date_to_db, context=context)
+ date_to = datetime.datetime.strptime(leave.date_to, DEFAULT_SERVER_DATETIME_FORMAT)
if start_datetime and date_to < start_datetime:
continue
-
leaves.append((date_from, date_to, leave.group_id.id))
return leaves
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 12caba8ba79..9b2a3243866 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1481,7 +1481,7 @@ class Reports(http.Controller):
if 'name' not in action:
reports = request.session.model('ir.actions.report.xml')
res_id = reports.search([('report_name', '=', action['report_name']),],
- 0, False, False, context)
+ context=context)
if len(res_id) > 0:
file_name = reports.read(res_id[0], ['name'], context)['name']
else:
diff --git a/addons/web_calendar/static/src/js/web_calendar.js b/addons/web_calendar/static/src/js/web_calendar.js
index f522e9e3164..08f685e9677 100644
--- a/addons/web_calendar/static/src/js/web_calendar.js
+++ b/addons/web_calendar/static/src/js/web_calendar.js
@@ -843,7 +843,7 @@ var CalendarView = View.extend({
var index = this.dataset.get_id_index(id);
if (index !== null) {
event_id = this.dataset.ids[index];
- this.dataset.write(event_id, data, {}).done(function() {
+ this.dataset.write(event_id, data, {}).always(function() {
if (is_virtual_id(event_id)) {
// this is a virtual ID and so this will create a new event
// with an unknown id for us.
diff --git a/addons/web_editor/static/src/js/backend.js b/addons/web_editor/static/src/js/backend.js
index 3d3b6cb27ee..eca913e1309 100644
--- a/addons/web_editor/static/src/js/backend.js
+++ b/addons/web_editor/static/src/js/backend.js
@@ -194,7 +194,7 @@ var FieldTextHtml = widget.extend({
return def;
},
get_url: function (_attr) {
- var src = this.options.editor_url ? this.options.editor_url+"?" : "/web_editor/field/html?";
+ var src = this.options.editor_url || "/web_editor/field/html";
var datarecord = this.view.get_fields_values();
var attr = {
@@ -211,6 +211,9 @@ var FieldTextHtml = widget.extend({
if (this.options.snippets) {
attr.snippets = this.options.snippets;
}
+ if (this.options.template) {
+ attr.template = this.options.template;
+ }
if (!this.get("effective_readonly")) {
attr.enable_editor = 1;
}
@@ -227,6 +230,10 @@ var FieldTextHtml = widget.extend({
attr[k] = _attr[k];
}
+ if (src.indexOf('?') === -1) {
+ src += "?";
+ }
+
for (var k in attr) {
if (attr[k] !== null) {
src += "&"+k+"="+(_.isBoolean(attr[k]) ? +attr[k] : attr[k]);
diff --git a/addons/web_kanban/static/src/less/kanban_view.less b/addons/web_kanban/static/src/less/kanban_view.less
index b9a8c418d48..77a350b60c6 100644
--- a/addons/web_kanban/static/src/less/kanban_view.less
+++ b/addons/web_kanban/static/src/less/kanban_view.less
@@ -191,9 +191,6 @@
}
&.o_kanban_mobile {
- .o_kanban_record {
- width: 400px;
- }
span, div.label {
.o_text_overflow;
}
diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css
index 2e4764c469e..c6f9b2d9add 100644
--- a/addons/website/static/src/css/website.css
+++ b/addons/website/static/src/css/website.css
@@ -675,6 +675,7 @@ table.well tr td {
}
.oe_website_spinner input {
text-align: center;
+ min-width: 50px;
}
div.carousel .container > .carousel-caption {
diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass
index 206eedf4480..8b03d1d86cd 100644
--- a/addons/website/static/src/css/website.sass
+++ b/addons/website/static/src/css/website.sass
@@ -545,6 +545,7 @@ table.well tr
width: 121px
input
text-align: center
+ min-width: 50px
div.carousel
.container
diff --git a/addons/website_portal/views/templates.xml b/addons/website_portal/views/templates.xml
index 55003fab4ad..6f52b9a8aac 100644
--- a/addons/website_portal/views/templates.xml
+++ b/addons/website_portal/views/templates.xml
@@ -19,7 +19,7 @@
<t t-call="website_portal.contact" />
<div class="oe_structure"/>
</div>
- <div class="col-md-3 col-md-offset-1 o_my_sidebar">
+ <div id="o_my_sidebar" class="col-md-3 col-md-offset-1 o_my_sidebar">
<div class="o_my_details">
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
<div t-field="user.partner_id" t-field-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
@@ -32,7 +32,7 @@
</template>
<template id="website_portal.custom_panel" inherit_id='website_portal.account' customize_show="True" name="Editable Panel" active="False">
- <xpath expr="//div[@class='o_my_sidebar']" position="inside">
+ <xpath expr="//div[@id='o_my_sidebar']" position="inside">
<div class="oe_structure">
<h3 class="page-header">Custom Panel</h3>
<p>Place your content here.</p>
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index b847cd5a9dd..e3fb18813f1 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -133,6 +133,32 @@ class sale_order_line(osv.Model):
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
}
+class sale_order_line(osv.Model):
+ _inherit = "sale.order.line"
+
+ def _fnct_get_discounted_price(self, cr, uid, ids, field_name, args, context=None):
+ res = dict.fromkeys(ids, False)
+ for line in self.browse(cr, uid, ids, context=context):
+ res[line.id] = (line.price_unit * (1.0 - (line.discount or 0.0) / 100.0))
+ return res
+
+ _columns = {
+ 'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
+ }
+
+class sale_order_line(osv.Model):
+ _inherit = "sale.order.line"
+
+ def _fnct_get_discounted_price(self, cr, uid, ids, field_name, args, context=None):
+ res = dict.fromkeys(ids, False)
+ for line in self.browse(cr, uid, ids, context=context):
+ res[line.id] = (line.price_unit * (1.0 - (line.discount or 0.0) / 100.0))
+ return res
+
+ _columns = {
+ 'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
+ }
+
class website(orm.Model):
_inherit = 'website'
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 18b47dfe285..0149232558d 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -101,7 +101,7 @@ $('.oe_website_sale').each(function () {
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
var product_ids = [];
- var product_dom = $(".js_add_cart_variants[data-attribute_value_ids]").last();
+ var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]").last();
if (!product_dom.length) {
return;
}
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index c9fd134ec03..e042cfd7a0b 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1478,10 +1478,13 @@
<!-- Add a link to the product page in the portal orders followup page -->
<template id="orders_followup_products_links" name="Orders Followup Products Links" inherit_id="website_portal_sale.orders_followup">
- <xpath expr="//span[@t-esc='ol.product_id.name']" position="replace">
- <a t-att-href="ol.product_id.website_url">
- <span t-esc="ol.product_id.name"/>
+ <xpath expr="//div[@id='product_name']/*" position="replace">
+ <a t-if="ol.product_id.website_published" t-att-href="ol.product_id.website_url">
+ <span t-esc="ol.name"/>
</a>
+ <t t-if="not ol.product_id.website_published">
+ <span t-esc="ol.name"/>
+ </t>
</xpath>
</template>
diff --git a/addons/website_sale_delivery/__openerp__.py b/addons/website_sale_delivery/__openerp__.py
index c05689e506f..b03a495ccaf 100644
--- a/addons/website_sale_delivery/__openerp__.py
+++ b/addons/website_sale_delivery/__openerp__.py
@@ -12,7 +12,6 @@ Delivery Costs
'data': [
'views/website_sale_delivery.xml',
'views/website_sale_delivery_view.xml',
- 'security/ir.model.access.csv',
'demo/website_sale_delivery_data.xml'
],
'demo': [
diff --git a/addons/website_sale_delivery/models/sale_order.py b/addons/website_sale_delivery/models/sale_order.py
index b5d25bd8a1e..f5d49849783 100644
--- a/addons/website_sale_delivery/models/sale_order.py
+++ b/addons/website_sale_delivery/models/sale_order.py
@@ -109,21 +109,24 @@ class SaleOrder(orm.Model):
def _get_delivery_methods(self, cr, uid, order, context=None):
carrier_obj = self.pool.get('delivery.carrier')
- delivery_ids = carrier_obj.search(cr, uid, [('website_published', '=', True)], context=context)
+ carrier_ids = carrier_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True)], context=context)
available_carrier_ids = []
# Following loop is done to avoid displaying delivery methods who are not available for this order
# This can surely be done in a more efficient way, but at the moment, it mimics the way it's
# done in delivery_set method of sale.py, from delivery module
- for carrier in carrier_obj.browse(cr, SUPERUSER_ID, delivery_ids, context=dict(context, order_id=order.id)):
+
+ new_context = dict(context, order_id=order.id)
+ for carrier in carrier_ids:
try:
- _logger.debug("Checking availability of %s" % carrier.name)
- if carrier.available:
- available_carrier_ids.append(carrier.id)
+ _logger.debug("Checking availability of carrier #%s" % carrier)
+ available = carrier_obj.read(cr, SUPERUSER_ID, [carrier], fields=['available'], context=new_context)[0]['available']
+ if available:
+ available_carrier_ids = available_carrier_ids + [carrier]
except ValidationError as e:
# RIM: hack to remove in master, because available field should not depend on a SOAP call to external shipping provider
# The validation error is used in backend to display errors in fedex config, but should fail silently in frontend
- _logger.debug("%s removed from e-commerce carrier list. %s" % (carrier.name, e))
+ _logger.debug("Carrier #%s removed from e-commerce carrier list. %s" % (carrier, e))
return available_carrier_ids
diff --git a/addons/website_sale_delivery/security/ir.model.access.csv b/addons/website_sale_delivery/security/ir.model.access.csv
deleted file mode 100644
index 69e804cfd28..00000000000
--- a/addons/website_sale_delivery/security/ir.model.access.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_delivery_carrier_public,delivery.carrier.public,delivery.model_delivery_carrier,,1,0,0,0
diff --git a/addons/website_slides/static/src/less/website_slides.less b/addons/website_slides/static/src/less/website_slides.less
index fc4cb43e9f7..12469b90f46 100644
--- a/addons/website_slides/static/src/less/website_slides.less
+++ b/addons/website_slides/static/src/less/website_slides.less
@@ -12,6 +12,11 @@
@MAX-Z-INDEX : 2147483647;
} .define-color-if-not-defined();
+.top_covered_image {
+ object-fit: cover;
+ object-position: top;
+}
+
.oe_slides_panel_footer {
background-color: @navbar-default-bg;
}
@@ -45,6 +50,7 @@ section.oe_slides_promote_box {
}
img.oe_slides_channel_thumbnail {
+ .top_covered_image();
height: 250px;
width: 100%
}
@@ -54,6 +60,7 @@ img.oe_slides_opacity {
}
img.oe_slides_promote_image {
+ .top_covered_image();
max-height: 300px;
width: 100%;
overflow: hidden;
@@ -65,6 +72,7 @@ img.oe_slides_promote_image {
}
.oe_slides_apart_small {
+ .top_covered_image();
height: 60px;
width: 90px;
}
@@ -97,6 +105,7 @@ textarea.o_slides_comment {
}
.oe_slides_grid_thumbnail {
+ .top_covered_image();
height: 200px;
width: 100%;
overflow: hidden;