diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 8ef48cf6d68..64f9c6ee0ae 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -352,7 +352,7 @@ class AccountBankStatement(models.Model): class AccountBankStatementLine(models.Model): _name = "account.bank.statement.line" _description = "Bank Statement Line" - _order = "statement_id desc, sequence" + _order = "statement_id desc, sequence, id desc" _inherit = ['ir.needaction_mixin'] name = fields.Char(string='Memo', required=True) @@ -956,8 +956,23 @@ class AccountBankStatementLine(models.Model): aml_dict['currency_id'] = statement_currency.id # Create write-offs + # When we register a payment on an invoice, the write-off line contains the amount + # currency if all related invoices have the same currency. We apply the same logic in + # the manual reconciliation. + counterpart_aml = self.env['account.move.line'] + for aml_dict in counterpart_aml_dicts: + counterpart_aml |= aml_dict.get('move_line', self.env['account.move.line']) + new_aml_currency = False + if counterpart_aml\ + and len(counterpart_aml.mapped('currency_id')) == 1\ + and counterpart_aml[0].currency_id\ + and counterpart_aml[0].currency_id != company_currency: + new_aml_currency = counterpart_aml[0].currency_id for aml_dict in new_aml_dicts: aml_dict['payment_id'] = payment_id + if new_aml_currency and not aml_dict.get('currency_id'): + aml_dict['currency_id'] = new_aml_currency.id + aml_dict['amount_currency'] = company_currency.with_context(ctx).compute(aml_dict['debit'] - aml_dict['credit'], new_aml_currency) aml_obj.with_context(check_move_validity=False).create(aml_dict) # Create counterpart move lines and reconcile them diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 215d40f3b8d..d2fe2cc4689 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -918,6 +918,8 @@ class AccountMoveLine(models.Model): first_line_dict['account_id'] = self[0].account_id.id if 'analytic_account_id' in first_line_dict: del first_line_dict['analytic_account_id'] + if 'tax_ids' in first_line_dict: + del first_line_dict['tax_ids'] # Writeoff line in specified writeoff account second_line_dict = vals.copy() @@ -1244,7 +1246,7 @@ class AccountMoveLine(models.Model): """ Prepare the values used to create() an account.analytic.line upon validation of an account.move.line having an analytic account. This method is intended to be extended in other modules. """ - amount = (self.credit or 0.0) - (self.debit or 0.0) + amount = (self.debit or 0.0) - (self.credit or 0.0) return { 'name': self.name, 'date': self.date, diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 50c12c035b9..084f203680b 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -242,7 +242,7 @@ class TestReconciliation(AccountingTestCase): self.check_results(bank_stmt.move_line_ids, { self.account_euro.id: {'debit': 40.0, 'credit': 0.0, 'amount_currency': 0, 'currency_id': False}, self.account_rcv.id: {'debit': 0.0, 'credit': 32.7, 'amount_currency': -41.97, 'currency_id': self.currency_usd_id, 'currency_diff': 0, 'amount_currency_diff': -8.03}, - self.diff_income_account.id: {'debit': 0.0, 'credit': 7.3, 'amount_currency': 0, 'currency_id': False}, + self.diff_income_account.id: {'debit': 0.0, 'credit': 7.3, 'amount_currency': -9.37, 'currency_id': self.currency_usd_id}, }) # The invoice should be paid, as the payments totally cover its total diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index e560e2419b7..f45dd8397ab 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- +import calendar from datetime import date, datetime from dateutil.relativedelta import relativedelta from openerp import api, fields, models, _ from openerp.exceptions import UserError, ValidationError from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF -from openerp.tools import float_compare +from openerp.tools import float_compare, float_is_zero class AccountAssetCategory(models.Model): @@ -150,14 +151,26 @@ class AccountAssetAsset(models.Model): if self.prorata: amount = amount_to_depr / self.method_number if sequence == 1: - days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 - amount = (amount_to_depr / self.method_number) / total_days * days + if self.method_period % 12 != 0: + date = datetime.strptime(self.date, '%Y-%m-%d') + month_days = calendar.monthrange(date.year, date.month)[1] + days = month_days - date.day + 1 + amount = (amount_to_depr / self.method_number) / month_days * days + else: + days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 + amount = (amount_to_depr / self.method_number) / total_days * days elif self.method == 'degressive': amount = residual_amount * self.method_progress_factor if self.prorata: if sequence == 1: - days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 - amount = (residual_amount * self.method_progress_factor) / total_days * days + if self.method_period % 12 != 0: + date = datetime.strptime(self.date, '%Y-%m-%d') + month_days = calendar.monthrange(date.year, date.month)[1] + days = month_days - date.day + 1 + amount = (residual_amount * self.method_progress_factor) / month_days * days + else: + days = (self.company_id.compute_fiscalyear_dates(depreciation_date)['date_to'] - depreciation_date).days + 1 + amount = (residual_amount * self.method_progress_factor) / total_days * days return amount def _compute_board_undone_dotation_nb(self, depreciation_date, total_days): @@ -210,6 +223,8 @@ class AccountAssetAsset(models.Model): sequence = x + 1 amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date) amount = self.currency_id.round(amount) + if float_is_zero(amount, precision_rounding=self.currency_id.rounding): + continue residual_amount -= amount vals = { 'amount': amount, @@ -407,7 +422,7 @@ class AccountAssetAsset(models.Model): 'res_model': 'account.move', 'view_id': False, 'type': 'ir.actions.act_window', - 'domain': [('id', 'in', move_ids)] + 'domain': [('id', 'in', move_ids)], } diff --git a/addons/account_asset/tests/test_account_asset.py b/addons/account_asset/tests/test_account_asset.py index 0027c6db815..0b48aa32c71 100644 --- a/addons/account_asset/tests/test_account_asset.py +++ b/addons/account_asset/tests/test_account_asset.py @@ -54,7 +54,7 @@ class TestAccountAsset(common.TransactionCase): asset_modify_number_0.with_context({'active_id': account_asset_asset_office0.id}).modify() # I check the proper depreciation lines created. - self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids) - 1) + self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids)) # I compute a asset on period. context = { "active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")], diff --git a/addons/bus/static/src/js/bus.js b/addons/bus/static/src/js/bus.js index a8de43dd9fa..9f7003f418b 100644 --- a/addons/bus/static/src/js/bus.js +++ b/addons/bus/static/src/js/bus.js @@ -18,6 +18,7 @@ bus.Bus = Widget.extend({ this._super(); this.options = {}; this.activated = false; + this.bus_id = _.uniqueId('bus'); this.channels = []; this.last = 0; this.stop = false; @@ -32,15 +33,24 @@ bus.Bus = Widget.extend({ this.trigger('window_focus', this.is_master); } }); - $(window).on("focus", _.bind(this.focus_change, this, true)); - $(window).on("blur", _.bind(this.focus_change, this, false)); - $(window).on("unload", _.bind(this.focus_change, this, false)); + $(window).on("focus." + this.bus_id, _.bind(this.focus_change, this, true)); + $(window).on("blur." + this.bus_id, _.bind(this.focus_change, this, false)); + $(window).on("unload." + this.bus_id, _.bind(this.focus_change, this, false)); _.each('click,keydown,keyup'.split(','), function(evtype) { - $(window).on(evtype, function() { + $(window).on(evtype + "." + self.bus_id, function() { self.last_presence = new Date().getTime(); }); }); }, + destroy: function () { + var self = this; + $(window).off("focus." + this.bus_id); + $(window).off("blur." + this.bus_id); + $(window).off("unload." + this.bus_id); + _.each('click,keydown,keyup'.split(','), function(evtype) { + $(window).off(evtype + "." + self.bus_id); + }); + }, start_polling: function(){ if(!this.activated){ this.poll(); diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index 84b819faa25..2d2c9aa2f70 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -79,7 +79,7 @@ class StockPicking(models.Model): def _compute_shipping_weight(self): self.shipping_weight = self.weight_bulk + sum([pack.shipping_weight for pack in self.package_ids]) - carrier_price = fields.Float(string="Shipping Cost", readonly=True) + carrier_price = fields.Float(string="Shipping Cost") delivery_type = fields.Selection(related='carrier_id.delivery_type', readonly=True) carrier_id = fields.Many2one("delivery.carrier", string="Carrier") volume = fields.Float(copy=False) @@ -91,6 +91,11 @@ class StockPicking(models.Model): weight_bulk = fields.Float('Bulk Weight', compute='_compute_bulk_weight') shipping_weight = fields.Float("Weight for Shipping", compute='_compute_shipping_weight') + @api.onchange('carrier_id') + def onchange_carrier(self): + if self.carrier_id.delivery_type in ['fixed', 'base_on_rule']: + self.carrier_price = self.carrier_id.price + @api.depends('product_id', 'move_lines') def _cal_weight(self): for picking in self: diff --git a/addons/im_odoo_support/static/src/js/im_odoo_support.js b/addons/im_odoo_support/static/src/js/im_odoo_support.js index ae76d9f93f5..10f161861f3 100644 --- a/addons/im_odoo_support/static/src/js/im_odoo_support.js +++ b/addons/im_odoo_support/static/src/js/im_odoo_support.js @@ -27,6 +27,10 @@ var OdooSupport = Widget.extend({ // bind event $(window).on("odoo_support_ready_to_bind", this, _.bind(this.bind_actions, this)); }, + destroy: function () { + $(window).off("odoo_support_ready_to_bind"); + return this._super.apply(this, arguments); + }, bind_actions: function(event, button){ if (button === 'usermenu'){ $('.oe_user_menu_placeholder .odoo_support_contact').on('click', this, _.bind(this.click_action, this)); diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index f94997c946c..9566bcc7014 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -661,6 +661,10 @@ var ChatAction = Widget.extend(ControlPanelMixin, { target: 'current' }); }, + destroy: function() { + this.$buttons.off().destroy(); + this._super.apply(this, arguments); + }, }); diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 10c99ae6eb0..dd30dddf8f3 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -461,6 +461,11 @@ var BasicComposer = Widget.extend({ return this._super(); }, + destroy: function () { + $(window).off(this.fileupload_id); + return this._super.apply(this, arguments); + }, + toggle: function(state) { this.$el.toggle(state); }, diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index f0aa92f44cf..d9891283fc9 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -214,7 +214,7 @@ class PosOrder(models.Model): if move is None: # Create an entry for the sale journal_id = self.env['ir.config_parameter'].sudo().get_param( - 'pos.closing.journal_id', default=order.sale_journal.id) + 'pos.closing.journal_id_%s' % current_company.id, default=order.sale_journal.id) move = self._create_account_move( order.session_id.start_at, order.name, int(journal_id), order.company_id.id) @@ -598,7 +598,7 @@ class PosOrder(models.Model): if moves and not return_picking and not order_picking: moves.action_confirm() moves.force_assign() - moves.action_done() + moves.filtered(lambda m: m.product_id.tracking == 'none').action_done() return True @@ -608,9 +608,10 @@ class PosOrder(models.Model): picking.action_confirm() picking.force_assign() # Mark pack operations as done - for pack in picking.pack_operation_ids: + for pack in picking.pack_operation_ids.filtered(lambda x: x.product_id.tracking == 'none'): pack.write({'qty_done': pack.product_qty}) - picking.action_done() + if not any([(x.product_id.tracking != 'none') for x in picking.pack_operation_ids]): + picking.action_done() def add_payment(self, data): """Create a new payment for the order""" diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index 086b7980a1d..01576578fed 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -19,7 +19,7 @@ class PosSession(models.Model): company_id = session.config_id.journal_id.company_id.id orders = session.order_ids.filtered(lambda order: order.state == 'paid') journal_id = self.env['ir.config_parameter'].sudo().get_param( - 'pos.closing.journal_id', default=session.config_id.journal_id.id) + 'pos.closing.journal_id_%s' % company_id, default=session.config_id.journal_id.id) move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, int(journal_id), company_id) orders.with_context(force_company=company_id)._create_account_move_line(session, move) for order in session.order_ids.filtered(lambda o: o.state != 'done'): diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index db68be7db41..2697ce97475 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1821,11 +1821,9 @@ var PaymentScreenWidget = ScreenWidget.extend({ self.$('.next').removeClass('highlight'); } }, - // Check if the order is paid, then sends it to the backend, - // and complete the sale process - validate_order: function(force_validation) { - var self = this; + order_is_valid: function(force_validation) { + var self = this; var order = this.pos.get_order(); // FIXME: this check is there because the backend is unable to @@ -1835,7 +1833,7 @@ var PaymentScreenWidget = ScreenWidget.extend({ 'title': _t('Empty Order'), 'body': _t('There must be at least one product in your order before it can be validated'), }); - return; + return false; } var plines = order.get_paymentlines(); @@ -1845,12 +1843,12 @@ var PaymentScreenWidget = ScreenWidget.extend({ 'message': _t('Negative Bank Payment'), 'comment': _t('You cannot have a negative amount in a Bank payment. Use a cash payment method to return money to the customer.'), }); - return; + return false; } } if (!order.is_paid() || this.invoicing) { - return; + return false; } // The exact amount must be paid if there is no cash payment method defined. @@ -1864,7 +1862,7 @@ var PaymentScreenWidget = ScreenWidget.extend({ title: _t('Cannot return change without a cash payment method'), body: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'), }); - return; + return false; } } @@ -1885,9 +1883,16 @@ var PaymentScreenWidget = ScreenWidget.extend({ self.validate_order('confirm'); }, }); - return; + return false; } + return true; + }, + + finalize_validation: function() { + var self = this; + var order = this.pos.get_order(); + if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) { this.pos.proxy.open_cashbox(); @@ -1937,6 +1942,14 @@ var PaymentScreenWidget = ScreenWidget.extend({ } }, + + // Check if the order is paid, then sends it to the backend, + // and complete the sale process + validate_order: function(force_validation) { + if (this.order_is_valid(force_validation)) { + this.finalize_validation(); + } + }, }); gui.define_screen({name:'payment', widget: PaymentScreenWidget}); diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index 9a6713e4cd0..71541f64192 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -108,7 +108,7 @@ - -