diff options
| author | Christophe Simonis <chs@odoo.com> | 2016-02-17 17:48:03 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2016-02-17 17:48:03 +0100 |
| commit | 5be43b8d9ae4485ac28e9afb7dd7cdecefb58abc (patch) | |
| tree | 483b65cfc65fb4f57a32f841424812575ebdbc84 /addons | |
| parent | 6999a862fdff34ea7d05ba90c2977f2c386cb506 (diff) | |
| parent | 27058c4763b40c5ef9b2b81c9f8a2de8d68469af (diff) | |
[MERGE] forward port of branch 9.0 up to 27058c4
Diffstat (limited to 'addons')
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 |
