summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-02-17 17:48:03 +0100
committerChristophe Simonis <chs@odoo.com>2016-02-17 17:48:03 +0100
commit5be43b8d9ae4485ac28e9afb7dd7cdecefb58abc (patch)
tree483b65cfc65fb4f57a32f841424812575ebdbc84 /addons
parent6999a862fdff34ea7d05ba90c2977f2c386cb506 (diff)
parent27058c4763b40c5ef9b2b81c9f8a2de8d68469af (diff)
[MERGE] forward port of branch 9.0 up to 27058c4
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account.py37
-rw-r--r--addons/account/models/account_invoice.py21
-rw-r--r--addons/account/models/account_move.py62
-rw-r--r--addons/analytic/models/analytic.py2
-rw-r--r--addons/base_import/models.py28
-rw-r--r--addons/base_import/tests/test_cases.py4
-rw-r--r--addons/crm/base_partner_merge_view.xml18
-rw-r--r--addons/delivery/views/delivery_view.xml23
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml18
-rw-r--r--addons/l10n_fr_hr_payroll/l10n_fr_hr_payroll_data.xml2
-rw-r--r--addons/mail/models/mail_message.py53
-rw-r--r--addons/mail/static/src/js/chat_manager.js14
-rw-r--r--addons/mail/static/src/js/client_action.js2
-rw-r--r--addons/mass_mailing/controllers/main.py18
-rw-r--r--addons/mrp/mrp.py1
-rw-r--r--addons/payment_ogone/models/ogone.py12
-rw-r--r--addons/point_of_sale/static/src/js/screens.js1
-rw-r--r--addons/product/pricelist.py2
-rw-r--r--addons/product_visible_discount/product_visible_discount.py2
-rw-r--r--addons/purchase/purchase.py2
-rw-r--r--addons/purchase/purchase_view.xml2
-rw-r--r--addons/resource/resource.py11
-rw-r--r--addons/resource/tests/test_resource.py29
-rw-r--r--addons/sale/sale.py4
-rw-r--r--addons/stock_account/wizard/stock_valuation_history.py4
-rw-r--r--addons/web/static/src/js/framework/utils.js2
-rw-r--r--addons/website_mass_mailing/controllers/main.py7
-rw-r--r--addons/website_quote/models/order.py4
28 files changed, 258 insertions, 127 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index ae06e71f0cf..5c6fea97a5c 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -653,25 +653,24 @@ class AccountTax(models.Model):
else:
tax_amount = currency.round(tax_amount)
- if tax_amount:
- if tax.price_include:
- total_excluded -= tax_amount
- base -= tax_amount
- else:
- total_included += tax_amount
-
- if tax.include_base_amount:
- base += tax_amount
-
- taxes.append({
- 'id': tax.id,
- 'name': tax.name,
- 'amount': tax_amount,
- 'sequence': tax.sequence,
- 'account_id': tax.account_id.id,
- 'refund_account_id': tax.refund_account_id.id,
- 'analytic': tax.analytic,
- })
+ if tax.price_include:
+ total_excluded -= tax_amount
+ base -= tax_amount
+ else:
+ total_included += tax_amount
+
+ if tax.include_base_amount:
+ base += tax_amount
+
+ taxes.append({
+ 'id': tax.id,
+ 'name': tax.name,
+ 'amount': tax_amount,
+ 'sequence': tax.sequence,
+ 'account_id': tax.account_id.id,
+ 'refund_account_id': tax.refund_account_id.id,
+ 'analytic': tax.analytic,
+ })
return {
'taxes': sorted(taxes, key=lambda k: k['sequence']),
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 19b305babd4..7a82962f6ec 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -623,16 +623,17 @@ class AccountInvoice(models.Model):
def tax_line_move_line_get(self):
res = []
for tax_line in self.tax_line_ids:
- res.append({
- 'tax_line_id': tax_line.tax_id.id,
- 'type': 'tax',
- 'name': tax_line.name,
- 'price_unit': tax_line.amount,
- 'quantity': 1,
- 'price': tax_line.amount,
- 'account_id': tax_line.account_id.id,
- 'account_analytic_id': tax_line.account_analytic_id.id,
- })
+ if tax_line.amount:
+ res.append({
+ 'tax_line_id': tax_line.tax_id.id,
+ 'type': 'tax',
+ 'name': tax_line.name,
+ 'price_unit': tax_line.amount,
+ 'quantity': 1,
+ 'price': tax_line.amount,
+ 'account_id': tax_line.account_id.id,
+ 'account_analytic_id': tax_line.account_analytic_id.id,
+ })
return res
def inv_line_characteristic_hashcode(self, invoice_line):
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index c6d43c5ce96..9ecc153a498 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -535,7 +535,7 @@ class AccountMoveLine(models.Model):
# Return lines formatted
if len(pairs) > 0:
- target_currency = self.currency_id or self.company_id.currency_id
+ target_currency = (self.currency_id and self.amount_currency) and self.currency_id or self.company_id.currency_id
lines = self.browse(list(pairs[0]))
return lines.prepare_move_lines_for_reconciliation_widget(target_currency=target_currency)
return []
@@ -630,7 +630,7 @@ class AccountMoveLine(models.Model):
'journal_name': line.journal_id.name,
'partner_id': line.partner_id.id,
'partner_name': line.partner_id.name,
- 'currency_id': line.currency_id.id or False,
+ 'currency_id': (line.currency_id and line.amount_currency) and line.currency_id.id or False,
}
debit = line.debit
@@ -645,7 +645,7 @@ class AccountMoveLine(models.Model):
# Get right debit / credit:
target_currency = target_currency or company_currency
- line_currency = line.currency_id or company_currency
+ line_currency = (line.currency_id and line.amount_currency) and line.currency_id or company_currency
amount_currency_str = ""
total_amount_currency_str = ""
if line_currency != company_currency:
@@ -976,20 +976,21 @@ class AccountMoveLine(models.Model):
vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (amount / res['total_excluded']))
# Create tax lines
for tax_vals in res['taxes']:
- account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id'])
- if not account_id: account_id = vals['account_id']
- tax_lines_vals.append({
- 'account_id': account_id,
- 'name': vals['name'] + ' ' + tax_vals['name'],
- 'tax_line_id': tax_vals['id'],
- 'move_id': vals['move_id'],
- 'date': vals['date'],
- 'partner_id': vals.get('partner_id'),
- 'ref': vals.get('ref'),
- 'statement_id': vals.get('statement_id'),
- 'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0,
- 'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0,
- })
+ if tax_vals['amount']:
+ account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id'])
+ if not account_id: account_id = vals['account_id']
+ tax_lines_vals.append({
+ 'account_id': account_id,
+ 'name': vals['name'] + ' ' + tax_vals['name'],
+ 'tax_line_id': tax_vals['id'],
+ 'move_id': vals['move_id'],
+ 'date': vals['date'],
+ 'partner_id': vals.get('partner_id'),
+ 'ref': vals.get('ref'),
+ 'statement_id': vals.get('statement_id'),
+ 'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0,
+ 'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0,
+ })
new_line = super(AccountMoveLine, self).create(vals)
for tax_line_vals in tax_lines_vals:
@@ -1220,6 +1221,7 @@ class AccountPartialReconcile(models.Model):
'move_id': move.id,
'currency_id': rec.currency_id.id,
'amount_currency': 0.0,
+ 'partner_id': rec.debit_move_id.partner_id.id,
})
rec.env['account.move.line'].create({
'name': _('Currency exchange rate difference'),
@@ -1228,6 +1230,7 @@ class AccountPartialReconcile(models.Model):
'account_id': amount_diff > 0 and rec.company_id.currency_exchange_journal_id.default_debit_account_id.id or rec.company_id.currency_exchange_journal_id.default_credit_account_id.id,
'move_id': move.id,
'currency_id': rec.currency_id.id,
+ 'partner_id': rec.debit_move_id.partner_id.id,
})
rec.env['account.partial.reconcile'].create({
'debit_move_id': amount_diff < 0 and line_to_reconcile.id or rec.debit_move_id.id,
@@ -1249,7 +1252,28 @@ class AccountPartialReconcile(models.Model):
def unlink(self):
""" When removing a link between entries, we need to revert the eventual journal entries we created to book the
fluctuation of the foreign currency's exchange rate.
+ We need also to reconcile together the origin currency difference line and its reversal in order to completly
+ cancel the currency difference entry on the partner account (otherwise it will still appear on the aged balance
+ for example).
"""
exchange_rate_entries = self.env['account.move'].search([('rate_diff_partial_rec_id', 'in', self.ids)])
- exchange_rate_entries.reverse_moves()
- return super(AccountPartialReconcile, self).unlink()
+ # revert the currency difference entry
+ reversed_moves = exchange_rate_entries.reverse_moves()
+ # find the origin currency difference line on the partner account and its newly created reversal, and store them in a list
+ pairs_to_rec = []
+ for rev_move in self.env['account.move'].browse(reversed_moves):
+ if not rev_move.rate_diff_partial_rec_id:
+ continue
+ origin_move = exchange_rate_entries.filtered(lambda x: x.rate_diff_partial_rec_id == rev_move.rate_diff_partial_rec_id)
+ for acm_line in rev_move.line_ids:
+ if acm_line.account_id.reconcile:
+ for origin_line in origin_move.line_ids:
+ if origin_line.account_id == acm_line.account_id and origin_line.debit == acm_line.credit and origin_line.credit == acm_line.debit:
+ to_rec = origin_line + acm_line
+ pairs_to_rec.append(to_rec)
+ # the call to super() had to be delayed in order to mark the move lines to reconcile together (to use 'rate_diff_partial_rec_id')
+ res = super(AccountPartialReconcile, self).unlink()
+ # now that the origin currency difference line is not reconciled anymore, we can reconcile it with its reversal entry to cancel it completly
+ for to_rec in pairs_to_rec:
+ to_rec.reconcile()
+ return res
diff --git a/addons/analytic/models/analytic.py b/addons/analytic/models/analytic.py
index aa4cc3a330b..2946740387b 100644
--- a/addons/analytic/models/analytic.py
+++ b/addons/analytic/models/analytic.py
@@ -77,6 +77,8 @@ class account_analytic_account(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
+ if operator not in ('ilike', 'like', '=', '=like', '=ilike'):
+ return super(account_analytic_account, self).name_search(name, args, operator, limit)
args = args or []
domain = ['|', ('code', operator, name), ('name', operator, name)]
partners = self.env['res.partner'].search([('name', operator, name)], limit=limit)
diff --git a/addons/base_import/models.py b/addons/base_import/models.py
index 7cbd0d79e09..22b48967e65 100644
--- a/addons/base_import/models.py
+++ b/addons/base_import/models.py
@@ -6,6 +6,8 @@ import logging
import operator
import os
+from openerp.tools.mimetypes import guess_mimetype
+
try:
from cStringIO import StringIO
except ImportError:
@@ -158,16 +160,36 @@ class ir_import(orm.TransientModel):
return fields
def _read_file(self, file_type, record, options):
+ # guess mimetype from file content
+ mimetype = guess_mimetype(record.file)
+ (file_extension, handler, req) = FILE_TYPE_DICT.get(mimetype, (None, None, None))
+ if handler:
+ try:
+ return getattr(self, '_read_' + file_extension)(record, options)
+ except Exception:
+ _logger.warn("Failed to read file '%s' (transient id %d) using guessed mimetype %s",
+ record.file_name or '<unknown>', record.id, mimetype)
+
+ # try reading with user-provided mimetype
(file_extension, handler, req) = FILE_TYPE_DICT.get(file_type, (None, None, None))
if handler:
- return getattr(self, '_read_' + file_extension)(record, options)
+ try:
+ return getattr(self, '_read_' + file_extension)(record, options)
+ except Exception:
+ _logger.warn("Failed to read file '%s' (transient id %d) using user-provided mimetype %s",
+ record.file_name or '<unknown>', record.id, file_type)
+
# fallback on file extensions as mime types can be unreliable (e.g.
# software setting incorrect mime types, or non-installed software
# leading to browser not sending mime types)
if record.file_name:
p, ext = os.path.splitext(record.file_name)
- if ext and EXTENSIONS.get(ext):
- return getattr(self, '_read_' + ext[1:])(record, options)
+ if ext in EXTENSIONS:
+ try:
+ return getattr(self, '_read_' + ext[1:])(record, options)
+ except Exception:
+ _logger.warn("Failed to read file '%s' (transient id %s) using file extension",
+ record.file_name, record.id)
if req:
raise ImportError(_("Unable to load \"{extension}\" file: requires Python module \"{modname}\"").format(extension=file_extension, modname=req))
diff --git a/addons/base_import/tests/test_cases.py b/addons/base_import/tests/test_cases.py
index bd9feafb9ab..5a8ad70402b 100644
--- a/addons/base_import/tests/test_cases.py
+++ b/addons/base_import/tests/test_cases.py
@@ -3,7 +3,7 @@ import unittest
from openerp.tests.common import TransactionCase, can_import
from openerp.modules.module import get_module_resource
-
+from openerp.tools import mute_logger
from .. import models
@@ -197,6 +197,7 @@ class test_preview(TransactionCase):
})
return Import, id
+ @mute_logger('openerp.addons.base_import.models')
def test_encoding(self):
Import, id = self.make_import()
result = Import.parse_preview(self.cr, self.uid, id, {
@@ -205,6 +206,7 @@ class test_preview(TransactionCase):
})
self.assertTrue('error' in result)
+ @mute_logger('openerp.addons.base_import.models')
def test_csv_errors(self):
Import, id = self.make_import()
diff --git a/addons/crm/base_partner_merge_view.xml b/addons/crm/base_partner_merge_view.xml
index 54632487de7..4585ec0be97 100644
--- a/addons/crm/base_partner_merge_view.xml
+++ b/addons/crm/base_partner_merge_view.xml
@@ -2,16 +2,14 @@
<openerp>
<data>
- <act_window id="action_partner_deduplicate"
- res_model="base.partner.merge.automatic.wizard"
- src_model="res.partner"
- target="new"
- multi="True"
- key2="client_action_multi"
- view_mode="form"
- name="Deduplicate Contact"
- context="{'default_state': 'option'}"
- />
+ <record model="ir.actions.act_window" id="action_partner_deduplicate">
+ <field name="name">Deduplicate Contacts</field>
+ <field name="res_model">base.partner.merge.automatic.wizard</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ <field name="context">{'active_test': False}</field>
+ </record>
<record model='ir.ui.view' id='base_partner_merge_automatic_wizard_form'>
<field name='name'>base.partner.merge.automatic.wizard.form</field>
diff --git a/addons/delivery/views/delivery_view.xml b/addons/delivery/views/delivery_view.xml
index 915f31fd3f3..de71a72b22c 100644
--- a/addons/delivery/views/delivery_view.xml
+++ b/addons/delivery/views/delivery_view.xml
@@ -4,6 +4,22 @@
<menuitem id="menu_delivery" name="Delivery" parent="stock.menu_stock_config_settings" groups="stock.group_stock_manager" sequence="50"/>
<menuitem id="sale_menu_delivery" name="Delivery" parent="base.menu_sale_config" groups="base.group_sale_manager" sequence="12"/>
+ <record id="view_delivery_carrier_search" model="ir.ui.view">
+ <field name="name">delivery.carrier.search</field>
+ <field name="model">delivery.carrier</field>
+ <field name="arch" type="xml">
+ <search string="Delivery Carrier Search">
+ <field name="name" string="Carrier" />
+ <field name="partner_id" operator="child_of"/>
+ <separator/>
+ <filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
+ <group expand="0" string="Group By" >
+ <filter string="Price Computation" name="delivery_type" context="{'group_by':'delivery_type'}" />
+ </group>
+ </search>
+ </field>
+ </record>
+
<record id="view_delivery_carrier_tree" model="ir.ui.view">
<field name="name">delivery.carrier.tree</field>
<field name="model">delivery.carrier</field>
@@ -23,6 +39,13 @@
<field name="arch" type="xml">
<form string="Carrier">
<sheet>
+ <div class="oe_button_box" name="button_box">
+ <button name="toggle_active" type="object"
+ class="oe_stat_button" icon="fa-archive">
+ <field name="active" widget="boolean_button"
+ options='{"terminology": "archive"}'/>
+ </button>
+ </div>
<div class="oe_title" name="title">
<label for="name" string="Name" class="oe_edit_only"/>
<h1>
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
index e52725a7270..2e4ae1a2550 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml
@@ -134,14 +134,16 @@
<field name="is_timesheet" invisible="1"/>
</tree>
<form string="Timesheet Activities">
- <field name="date"/>
- <field name="account_id"/>
- <field name="name"/>
- <field name="unit_amount" widget="float_time"/>
- <field name="amount"/>
- <field name="general_account_id"/>
- <field name="user_id" required="1"/>
- <field name="is_timesheet" invisible="1"/>
+ <group>
+ <field name="date"/>
+ <field name="account_id"/>
+ <field name="name"/>
+ <field name="unit_amount" widget="float_time"/>
+ <field name="amount"/>
+ <field name="general_account_id"/>
+ <field name="user_id" required="1"/>
+ <field name="is_timesheet" invisible="1"/>
+ </group>
</form>
</field>
</page>
diff --git a/addons/l10n_fr_hr_payroll/l10n_fr_hr_payroll_data.xml b/addons/l10n_fr_hr_payroll/l10n_fr_hr_payroll_data.xml
index 1893d1f1824..d144de3b0e7 100644
--- a/addons/l10n_fr_hr_payroll/l10n_fr_hr_payroll_data.xml
+++ b/addons/l10n_fr_hr_payroll/l10n_fr_hr_payroll_data.xml
@@ -21,7 +21,7 @@
<!-- Rule Category -->
<record id="base.main_company" model="res.company">
- <field name="plafond_secu">2946</field>
+ <field name="plafond_secu">3218</field>
</record>
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 8ed23669b4c..a210b1f4f40 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -7,6 +7,7 @@ import logging
from openerp import _, api, fields, models, SUPERUSER_ID
from openerp import tools
from openerp.exceptions import UserError, AccessError
+from openerp.osv import expression
_logger = logging.getLogger(__name__)
@@ -147,34 +148,36 @@ class Message(models.Model):
#------------------------------------------------------
@api.model
- def mark_all_as_read(self, channel_ids=None):
+ def mark_all_as_read(self, channel_ids=None, domain=None):
""" Remove all needactions of the current partner. If channel_ids is
given, restrict to messages written in one of those channels. """
partner_id = self.env.user.partner_id.id
- # possibly horribly inefficient method:
- # it does one db request for the search, and one for each message in
- # the result set to remove the current user from the relation.
- # domain = [('needaction_partner_ids', 'in', partner_id)]
- # if channel_ids:
- # domain += [('channel_ids', 'in', channel_ids)]
- # unread_messages = self.search(domain)
- # unread_messages.write({'needaction_partner_ids': [(3, partner_id)]})
-
- # a much faster way to do this is in pure sql:
- query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s"
- args = [(partner_id,)]
- if channel_ids:
- query += """
- AND mail_message_id in
- (SELECT mail_message_id
- FROM mail_message_mail_channel_rel
- WHERE mail_channel_id in %s)"""
- args += [tuple(channel_ids)]
- query += " RETURNING mail_message_id as id"
- self._cr.execute(query, args)
- self.invalidate_cache()
-
- ids = [m['id'] for m in self._cr.dictfetchall()]
+ if domain is None:
+ query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s"
+ args = [(partner_id,)]
+ if channel_ids:
+ query += """
+ AND mail_message_id in
+ (SELECT mail_message_id
+ FROM mail_message_mail_channel_rel
+ WHERE mail_channel_id in %s)"""
+ args += [tuple(channel_ids)]
+ query += " RETURNING mail_message_id as id"
+ self._cr.execute(query, args)
+ self.invalidate_cache()
+
+ ids = [m['id'] for m in self._cr.dictfetchall()]
+ else:
+ # not really efficient method: it does one db request for the
+ # search, and one for each message in the result set to remove the
+ # current user from the relation.
+ msg_domain = [('needaction_partner_ids', 'in', partner_id)]
+ if channel_ids:
+ msg_domain += [('channel_ids', 'in', channel_ids)]
+ unread_messages = self.search(expression.AND([msg_domain, domain]))
+ unread_messages.write({'needaction_partner_ids': [(3, partner_id)]})
+ ids = unread_messages.mapped('id')
+
notification = {'type': 'mark_as_read', 'message_ids': ids, 'channel_ids': channel_ids}
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), notification)
diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js
index 0dc41db8f0d..d1c17e1b965 100644
--- a/addons/mail/static/src/js/chat_manager.js
+++ b/addons/mail/static/src/js/chat_manager.js
@@ -148,7 +148,7 @@ function add_message (data, options) {
var msg = _.findWhere(messages, { id: data.id });
if (!msg) {
- msg = make_message(data);
+ msg = chat_manager.make_message(data);
// Keep the array ordered by id when inserting the new message
messages.splice(_.sortedIndex(messages, msg, 'id'), 0, msg);
_.each(msg.channel_ids, function (channel_id) {
@@ -298,7 +298,7 @@ function add_channel (data, options) {
chat_manager.bus.trigger("channel_toggle_fold", channel);
}
} else {
- channel = make_channel(data, options);
+ channel = chat_manager.make_channel(data, options);
channels.push(channel);
channels = _.sortBy(channels, function (channel) { return channel.name.toLowerCase(); });
if (!options.silent) {
@@ -677,6 +677,10 @@ function on_presence_notification (data) {
// Public interface
//----------------------------------------------------------------------------------
var chat_manager = {
+ // these two functions are exposed for extensibility purposes and shouldn't be called by other modules
+ make_message: make_message,
+ make_channel: make_channel,
+
post_message: function (data, options) {
options = options || {};
var msg = {
@@ -773,9 +777,9 @@ var chat_manager = {
return $.when();
}
},
- mark_all_as_read: function (channel) {
- if ((!channel && needaction_counter) || (channel && channel.needaction_counter)) {
- return MessageModel.call('mark_all_as_read', channel ? [[channel.id]] : []);
+ mark_all_as_read: function (channel, domain) {
+ if ((channel.id === "channel_inbox" && needaction_counter) || (channel && channel.needaction_counter)) {
+ return MessageModel.call('mark_all_as_read', [], {channel_ids: channel.id !== "channel_inbox" ? [channel.id] : [], domain: domain});
}
return $.when();
},
diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js
index 442e00e6b94..090b2025239 100644
--- a/addons/mail/static/src/js/client_action.js
+++ b/addons/mail/static/src/js/client_action.js
@@ -200,7 +200,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
self.$('.o_mail_chat_sidebar').slideToggle(200);
});
this.$buttons.on('click', '.o_mail_chat_button_mark_read', function () {
- chat_manager.mark_all_as_read();
+ chat_manager.mark_all_as_read(self.channel, self.domain);
});
this.$buttons.on('click', '.o_mail_chat_button_unstar_all', chat_manager.unstar_all);
this.$buttons.on('click', '.o_mail_chat_button_new_message', this.on_click_new_message);
diff --git a/addons/mass_mailing/controllers/main.py b/addons/mass_mailing/controllers/main.py
index 6ac211fa25f..c43e8206765 100644
--- a/addons/mass_mailing/controllers/main.py
+++ b/addons/mass_mailing/controllers/main.py
@@ -2,12 +2,28 @@
import werkzeug
-from openerp import http, SUPERUSER_ID
+from openerp import http, SUPERUSER_ID, _
from openerp.http import request
class MassMailController(http.Controller):
+ @http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
+ def mailing(self, mailing_id, email=None, res_id=None, **post):
+ mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
+ if mailing.exists():
+ res_ids = []
+ if mailing.mailing_model == 'mail.mass_mailing.contact':
+ contacts = request.env['mail.mass_mailing.contact'].sudo().search([
+ ('email', '=', email),
+ ('list_id', 'in', [mailing_list.id for mailing_list in mailing.contact_list_ids])
+ ])
+ res_ids = contacts.ids
+ else:
+ res_ids = [res_id]
+ mailing.update_opt_out(mailing_id, email, res_ids, True)
+ return _('You have been unsubscribed successfully')
+
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='none')
def track_mail_open(self, mail_id, **post):
""" Email tracking. """
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py
index 30a5f6a4220..2ac1033f67e 100644
--- a/addons/mrp/mrp.py
+++ b/addons/mrp/mrp.py
@@ -1221,6 +1221,7 @@ class mrp_production(osv.osv):
move_id = stock_move.create(cr, uid, {
'name': production.name,
'date': production.date_planned,
+ 'date_expected': production.date_planned,
'product_id': product.id,
'product_uom_qty': qty,
'product_uom': uom_id,
diff --git a/addons/payment_ogone/models/ogone.py b/addons/payment_ogone/models/ogone.py
index 0a07315467f..53db91d5e34 100644
--- a/addons/payment_ogone/models/ogone.py
+++ b/addons/payment_ogone/models/ogone.py
@@ -264,7 +264,7 @@ class PaymentTxOgone(osv.Model):
'acquirer_id': tx.acquirer_id.id,
'acquirer_ref': alias
})
- tx.partner_reference = alias
+ tx.write({'payment_method_id': ref})
return tx
@@ -294,7 +294,7 @@ class PaymentTxOgone(osv.Model):
'date_validate': datetime.datetime.strptime(data['TRXDATE'], '%m/%d/%y').strftime(DEFAULT_SERVER_DATE_FORMAT),
'acquirer_reference': data['PAYID'],
}
- if data.get('ALIAS') and tx.partner_id and tx.type == 'form_save':
+ if data.get('ALIAS') and tx.partner_id and tx.type == 'form_save' and not tx.payment_method_id:
pm_id = self.pool['payment.method'].create(cr, uid, {
'partner_id': tx.partner_id.id,
'acquirer_id': tx.acquirer_id.id,
@@ -399,6 +399,14 @@ class PaymentTxOgone(osv.Model):
'date_validate': datetime.date.today().strftime(DEFAULT_SERVER_DATE_FORMAT),
'acquirer_reference': tree.get('PAYID'),
})
+ if tree.get('ALIAS') and tx.partner_id and tx.type == 'form_save' and not tx.payment_method_id:
+ pm = tx.env['payment.method'].create({
+ 'partner_id': tx.partner_id.id,
+ 'acquirer_id': tx.acquirer_id.id,
+ 'acquirer_ref': tree.get('ALIAS'),
+ 'name': tree.get('CARDNO'),
+ })
+ tx.write({'payment_method_id': pm.id})
if tx.callback_eval:
safe_eval(tx.callback_eval, {'self': tx})
return True
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 4626f3b4ff3..beffd60af28 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1961,6 +1961,7 @@ return {
NumpadWidget: NumpadWidget,
ProductScreenWidget: ProductScreenWidget,
ProductListWidget: ProductListWidget,
+ ClientListScreenWidget: ClientListScreenWidget,
};
});
diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py
index c482a271710..b23691384e9 100644
--- a/addons/product/pricelist.py
+++ b/addons/product/pricelist.py
@@ -299,7 +299,7 @@ class product_pricelist_item(osv.osv):
'base': fields.selection([('list_price', 'Public Price'), ('standard_price', 'Cost'), ('pricelist', 'Other Pricelist')], string="Based on", required=True,
help='Base price for computation. \n Public Price: The base price will be the Sale/public Price. \n Cost Price : The base price will be the cost price. \n Other Pricelist : Computation of the base price based on another Pricelist.'),
'base_pricelist_id': fields.many2one('product.pricelist', 'Other Pricelist'),
- 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
+ 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', ondelete='cascade'),
'price_surcharge': fields.float('Price Surcharge',
digits_compute= dp.get_precision('Product Price'), help='Specify the fixed amount to add or substract(if negative) to the amount calculated with the discount.'),
'price_discount': fields.float('Price Discount', digits=(16,2)),
diff --git a/addons/product_visible_discount/product_visible_discount.py b/addons/product_visible_discount/product_visible_discount.py
index 5d77144e4cf..98363e6ff42 100644
--- a/addons/product_visible_discount/product_visible_discount.py
+++ b/addons/product_visible_discount/product_visible_discount.py
@@ -20,7 +20,7 @@ class sale_order_line(osv.osv):
"""Retrieve the price before applying the pricelist"""
item_obj = self.pool['product.pricelist.item']
product_obj = self.pool['product.product']
- field_name = 'list_price'
+ field_name = 'lst_price'
currency_id = None
if res_dict.get(pricelist):
rule_id = res_dict[pricelist][1]
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 07a1835cdad..56ccb5e2e7c 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -102,7 +102,7 @@ class PurchaseOrder(models.Model):
"It's used to do the matching when you receive the "
"products as this reference is usually written on the "
"delivery order sent by your vendor.")
- date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Datetime.now(),\
+ date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Datetime.now,\
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
date_approve = fields.Date('Approval Date', readonly=1, select=True, copy=False)
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 55dc320ac8e..8408a1d5f9a 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -146,7 +146,7 @@
<button name="button_confirm" type="object" states="sent" string="Confirm Order" class="oe_highlight" id="bid_confirm"/>
<button name="button_approve" type="object" states='to approve' string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
<button name="action_rfq_send" states="purchase" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
- <button name="action_view_picking" string="Receive Products" type="object" attrs="{'invisible': [('state','not in', ('purchase','done'))]}"/>
+ <button name="action_view_picking" string="Receive Products" type="object" attrs="{'invisible': ['|', ('state','not in', ('purchase','done')), ('picking_count', '=', 0)]}"/>
<button name="button_draft" states="cancel" string="Set to Draft" type="object" />
<button name="button_cancel" states="draft,to approve,sent,purchase" string="Cancel" type="object" />
<button name="button_done" type="object" string="Set to Done" states="purchase"
diff --git a/addons/resource/resource.py b/addons/resource/resource.py
index 5e9895f4f9b..1a0ea76c71f 100644
--- a/addons/resource/resource.py
+++ b/addons/resource/resource.py
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
+from datetime import timedelta
from dateutil import rrule
from dateutil.relativedelta import relativedelta
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
@@ -330,14 +331,8 @@ class resource_calendar(osv.osv):
working_intervals = []
tz_info = fields.datetime.context_timestamp(cr, uid, work_dt, context=context).tzinfo
for calendar_working_day in self.get_attendances_for_weekday(cr, uid, id, start_dt, context=context):
- if context and context.get('no_round_hours'):
- min_from = int((calendar_working_day.hour_from - int(calendar_working_day.hour_from)) * 60)
- min_to = int((calendar_working_day.hour_to - int(calendar_working_day.hour_to)) * 60)
- dt_f = work_dt.replace(hour=int(calendar_working_day.hour_from), minute=min_from)
- dt_t = work_dt.replace(hour=int(calendar_working_day.hour_to), minute=min_to)
- else:
- dt_f = work_dt.replace(hour=int(calendar_working_day.hour_from))
- dt_t = work_dt.replace(hour=int(calendar_working_day.hour_to))
+ dt_f = work_dt.replace(hour=0, minute=0, second=0) + timedelta(seconds=(calendar_working_day.hour_from * 3600))
+ dt_t = work_dt.replace(hour=0, minute=0, second=0) + timedelta(seconds=(calendar_working_day.hour_to * 3600))
# adapt tz
working_interval = (
diff --git a/addons/resource/tests/test_resource.py b/addons/resource/tests/test_resource.py
index 5665234f46b..029c001f81a 100644
--- a/addons/resource/tests/test_resource.py
+++ b/addons/resource/tests/test_resource.py
@@ -405,6 +405,35 @@ class TestResource(TestResourceCommon):
default_interval=(8, 16), context=context)
self.assertEqual(res, 32.0, 'resource_calendar: wrong get_working_hours computation')
+ self.att0_0_id = self.resource_attendance.create(
+ cr, uid, {
+ 'name': 'Att0',
+ 'dayofweek': '0',
+ 'hour_from': 7.5,
+ 'hour_to': 12.5,
+ 'calendar_id': self.calendar_id,
+ }, context=context
+ )
+ self.att0_1_id = self.resource_attendance.create(
+ cr, uid, {
+ 'name': 'Att0',
+ 'dayofweek': '0',
+ 'hour_from': 13,
+ 'hour_to': 14,
+ 'calendar_id': self.calendar_id,
+ }, context=context
+ )
+ date1 = datetime.strptime('2013-02-11 07:30:00', '%Y-%m-%d %H:%M:%S')
+ date2 = datetime.strptime('2013-02-11 14:00:00', '%Y-%m-%d %H:%M:%S')
+ res = self.resource_calendar.get_working_hours(
+ cr, uid, self.calendar_id,
+ date1,
+ date2,
+ compute_leaves=False, resource_id=self.resource1_id, context=context)
+ # 7h30 -> 12h30 = 5 / 13h -> 14h = 1 / -> 6h
+ self.assertEqual(res, 6, 'resource_calendar: wrong get_working_hours computation')
+
+
def test_50_calendar_schedule_days(self):
""" Testing calendar days scheduling """
cr, uid = self.cr, self.uid
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 30c44a50d20..667c3f79c39 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -136,7 +136,7 @@ class SaleOrder(models.Model):
payment_term_id = fields.Many2one('account.payment.term', string='Payment Term', oldname='payment_term')
fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position')
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('sale.order'))
- team_id = fields.Many2one('crm.team', 'Sales Team', change_default=True, default=_get_default_team)
+ team_id = fields.Many2one('crm.team', 'Sales Team', change_default=True, default=_get_default_team, oldname='section_id')
procurement_group_id = fields.Many2one('procurement.group', 'Procurement Group', copy=False)
product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
@@ -797,7 +797,7 @@ class AccountInvoice(models.Model):
def _get_default_team(self):
return self.env['crm.team']._get_default_team_id()
- team_id = fields.Many2one('crm.team', string='Sales Team', default=_get_default_team)
+ team_id = fields.Many2one('crm.team', string='Sales Team', default=_get_default_team, oldname='section_id')
@api.multi
def confirm_paid(self):
diff --git a/addons/stock_account/wizard/stock_valuation_history.py b/addons/stock_account/wizard/stock_valuation_history.py
index 922598e9456..fa82810cb3b 100644
--- a/addons/stock_account/wizard/stock_valuation_history.py
+++ b/addons/stock_account/wizard/stock_valuation_history.py
@@ -128,7 +128,7 @@ class stock_history(osv.osv):
product_template_id,
SUM(quantity) as quantity,
date,
- price_unit_on_quant,
+ SUM(price_unit_on_quant * quantity) / SUM(quantity) as price_unit_on_quant,
source,
serial_number
FROM
@@ -205,5 +205,5 @@ class stock_history(osv.osv):
dest_location.usage not in ('internal', 'transit'))
))
AS foo
- GROUP BY move_id, location_id, company_id, product_id, product_categ_id, date, price_unit_on_quant, source, product_template_id, serial_number
+ GROUP BY move_id, location_id, company_id, product_id, product_categ_id, date, source, product_template_id, serial_number
)""")
diff --git a/addons/web/static/src/js/framework/utils.js b/addons/web/static/src/js/framework/utils.js
index a0ab37a7dfb..c1b9758b625 100644
--- a/addons/web/static/src/js/framework/utils.js
+++ b/addons/web/static/src/js/framework/utils.js
@@ -468,7 +468,7 @@ function autoresize ($textarea, options) {
var $fixed_text_area;
var min_height = (options && options.min_height) || 50;
if (!$fixed_text_area) {
- $fixed_text_area = $('<textarea>').css({
+ $fixed_text_area = $('<textarea disabled>').css({
position: 'absolute',
opacity: 0,
height: 10,
diff --git a/addons/website_mass_mailing/controllers/main.py b/addons/website_mass_mailing/controllers/main.py
index 5a67a326f6d..2a7596104a8 100644
--- a/addons/website_mass_mailing/controllers/main.py
+++ b/addons/website_mass_mailing/controllers/main.py
@@ -1,23 +1,24 @@
# -*- coding: utf-8 -*-
from openerp import http, SUPERUSER_ID
+from openerp.addons.mass_mailing.controllers.main import MassMailController
from openerp.http import request
-class MassMailController(http.Controller):
+class MassMailController(MassMailController):
@http.route(['/mail/mailing/<int:mailing_id>/unsubscribe'], type='http', website=True, auth='public')
def mailing(self, mailing_id, email=None, res_id=None, **post):
mailing = request.env['mail.mass_mailing'].sudo().browse(mailing_id)
if mailing.exists():
if mailing.mailing_model == 'mail.mass_mailing.contact':
- contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email', 'ilike', email)])
+ contacts = request.env['mail.mass_mailing.contact'].sudo().search([('email', '=', email)])
return request.website.render('website_mass_mailing.page_unsubscribe', {
'contacts': contacts,
'email': email,
'mailing_id': mailing_id})
else:
- mailing.update_opt_out(mailing_id, email, [res_id], True)
+ super(MassMailController, self).mailing(mailing_id, email=email, res_id=res_id, **post)
return request.website.render('website_mass_mailing.page_unsubscribed')
@http.route(['/mail/mailing/unsubscribe'], type='json', auth='none')
diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py
index 99ef26b8b54..f0d90d23d23 100644
--- a/addons/website_quote/models/order.py
+++ b/addons/website_quote/models/order.py
@@ -277,9 +277,9 @@ class sale_order(osv.osv):
# create draft invoice if transaction is ok
if tx and tx.state == 'done':
if order.state in ['draft', 'sent']:
- self.signal_workflow(cr, SUPERUSER_ID, [order.id], 'manual_invoice', context=context)
+ self.action_confirm(cr, SUPERUSER_ID, order.id, context=context)
message = _('Order payed by %s. Transaction: %s. Amount: %s.') % (tx.partner_id.name, tx.acquirer_reference, tx.amount)
- self.message_post(cr, uid, order_id, body=message, type='comment', subtype='mt_comment', context=context)
+ self.message_post(cr, uid, order_id, body=message, context=context)
return True
return False