diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 367fbc13800..a163368d813 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -421,20 +421,28 @@ class AccountBankStatementLine(models.Model): def button_cancel_reconciliation(self): moves_to_unbind = self.env['account.move'] moves_to_cancel = self.env['account.move'] + payment_to_unreconcile = self.env['account.payment'] for st_line in self: moves_to_unbind |= st_line.journal_entry_ids for move in st_line.journal_entry_ids: if any(line.payment_id for line in move.line_ids): + for line in move.line_ids: + payment_to_unreconcile |= line.payment_id continue moves_to_cancel |= st_line.journal_entry_ids if moves_to_unbind: moves_to_unbind.write({'statement_line_id': False}) + moves_to_unbind.line_ids.filtered(lambda x:x.statement_id == st_line.statement_id).write({'statement_id': False}) + if moves_to_cancel: for move in moves_to_cancel: move.line_ids.remove_move_reconcile() moves_to_cancel.button_cancel() moves_to_cancel.unlink() + if payment_to_unreconcile: + payment_to_unreconcile.write({'state': 'posted'}) + #################################################### # Reconciliation interface methods #################################################### @@ -566,7 +574,18 @@ class AccountBankStatementLine(models.Model): else: domain = [(f, '>', 0), (f, '<', amount)] elif comparator == '=': - domain = [(f, '=', float_round(amount, precision_digits=p))] + if f == 'amount_residual': + domain = [ + '|', (f, '=', float_round(amount, precision_digits=p)), + '&', ('account_id.internal_type', '=', 'liquidity'), + '|', ('debit', '=', amount), ('credit', '=', amount), + ] + else: + domain = [ + '|', (f, '=', float_round(amount, precision_digits=p)), + '&', ('account_id.internal_type', '=', 'liquidity'), + ('amount_currency', '=', amount), + ] else: raise UserError(_("Programmation error : domain_maker_move_line_amount requires comparator '=' or '<'")) domain += [('currency_id', '=', c)] diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 56298bebbba..a4b29bedcfc 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -142,11 +142,22 @@ class account_journal(models.Model): if self.type in ['bank', 'cash']: last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1) last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0 - ac_bnk_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids),('state', '=', 'open')]) - for ac_bnk in ac_bnk_stmt: - for line in ac_bnk.line_ids: - if not line.journal_entry_ids: - number_to_reconcile += 1 + #Get the number of items to reconcile for that bank journal + self.env.cr.execute("""SELECT COUNT(DISTINCT(statement_line_id)) + FROM account_move where statement_line_id + IN (SELECT line.id + FROM account_bank_statement_line AS line + LEFT JOIN account_bank_statement AS st + ON line.statement_id = st.id + WHERE st.journal_id IN %s and st.state = 'open')""", (tuple(self.ids),)) + already_reconciled = self.env.cr.fetchone()[0] + self.env.cr.execute("""SELECT COUNT(line.id) + FROM account_bank_statement_line AS line + LEFT JOIN account_bank_statement AS st + ON line.statement_id = st.id + WHERE st.journal_id IN %s and st.state = 'open'""", (tuple(self.ids),)) + all_lines = self.env.cr.fetchone()[0] + number_to_reconcile = all_lines - already_reconciled # optimization to read sum of balance from account_move_line account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id])) if account_ids: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 2ea4403a3e7..acd1fc2bcec 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -583,7 +583,14 @@ class AccountMoveLine(models.Model): ] try: amount = float(str) - amount_domain = ['|', ('amount_residual', '=', amount), '|', ('amount_residual_currency', '=', amount), '|', ('amount_residual', '=', -amount), ('amount_residual_currency', '=', -amount)] + amount_domain = [ + '|', ('amount_residual', '=', amount), + '|', ('amount_residual_currency', '=', amount), + '|', ('amount_residual', '=', -amount), + '|', ('amount_residual_currency', '=', -amount), + '&', ('account_id.internal_type', '=', 'liquidity'), + '|', '|', ('debit', '=', amount), ('credit', '=', amount), ('amount_currency', '=', amount), + ] str_domain = expression.OR([str_domain, amount_domain]) except: pass @@ -1050,13 +1057,13 @@ class AccountMoveLine(models.Model): if vals['debit'] != 0.0: vals['debit'] = res['total_excluded'] if vals['credit'] != 0.0: vals['credit'] = -res['total_excluded'] if vals.get('amount_currency'): - vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (amount / res['total_excluded'])) + vals['amount_currency'] = self.env['res.currency'].browse(vals['currency_id']).round(vals['amount_currency'] * (res['total_excluded']/amount)) # Create tax lines for tax_vals in res['taxes']: 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({ + temp = { 'account_id': account_id, 'name': vals['name'] + ' ' + tax_vals['name'], 'tax_line_id': tax_vals['id'], @@ -1065,7 +1072,12 @@ class AccountMoveLine(models.Model): '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, - }) + } + bank = self.env["account.bank.statement"].browse(vals.get('statement_id')) + if bank.currency_id != bank.company_id.currency_id: + temp['currency_id'] = bank.currency_id.id + temp['amount_currency'] = bank.company_id.currency_id.compute(tax_vals['amount'], bank.currency_id, round=True) + tax_lines_vals.append(temp) new_line = super(AccountMoveLine, self).create(vals) for tax_line_vals in tax_lines_vals: @@ -1107,10 +1119,11 @@ class AccountMoveLine(models.Model): msg = _('New expected payment date: ') + vals['expected_pay_date'] + '.\n' + vals.get('internal_note', '') self.invoice_id.message_post(body=msg) #TODO: check it is an internal note (not a regular email)! #when making a reconciliation on an existing liquidity journal item, mark the payment as reconciled - if 'statement_id' in vals and self.payment_id: - # In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement - if all(line.statement_id for line in self.payment_id.move_line_ids.filtered(lambda r: r.id != self.id and r.account_id.internal_type=='liquidity')): - self.payment_id.state = 'reconciled' + for record in self: + if 'statement_id' in vals and record.payment_id: + # In case of an internal transfer, there are 2 liquidity move lines to match with a bank statement + if all(line.statement_id for line in record.payment_id.move_line_ids.filtered(lambda r: r.id != record.id and r.account_id.internal_type=='liquidity')): + record.payment_id.state = 'reconciled' result = super(AccountMoveLine, self).write(vals) if self._context.get('check_move_validity', True): @@ -1362,6 +1375,9 @@ class AccountPartialReconcile(models.Model): partial_rec_set[x] = None #then, if the total debit and credit are equal, or the total amount in currency is 0, the reconciliation is full if currency and aml_to_balance and (float_is_zero(total_amount_currency, precision_rounding=currency.rounding) or float_compare(total_debit, total_credit, precision_rounding=currency.rounding) == 0): + # If both condition are satisfied, no need to create exchange rate entry + if float_is_zero(total_amount_currency, precision_rounding=currency.rounding) and float_compare(total_debit, total_credit, precision_rounding=currency.rounding) == 0: + return res #eventually create a journal entry to book the difference due to foreign currency's exchange rate that fluctuates aml_id, partial_rec_id = partial_rec.create_exchange_rate_entry(aml_to_balance, total_debit - total_credit, total_amount_currency, currency, maxdate) return res diff --git a/addons/account_extra_reports/report/account_partner_ledger.py b/addons/account_extra_reports/report/account_partner_ledger.py index 2bf1c0ca8ca..53b9164f032 100644 --- a/addons/account_extra_reports/report/account_partner_ledger.py +++ b/addons/account_extra_reports/report/account_partner_ledger.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- +from datetime import datetime import time from openerp import api, models +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT class ReportPartnerLedger(models.AbstractModel): @@ -26,7 +28,12 @@ class ReportPartnerLedger(models.AbstractModel): self.env.cr.execute(query, tuple(params)) res = self.env.cr.dictfetchall() sum = 0.0 + lang_code = self.env.context.get('lang') or 'en_US' + lang = self.env['res.lang'] + lang_id = lang._lang_get(lang_code) + date_format = lang.browse(lang_id).date_format for r in res: + r['date'] = datetime.strptime(r['date'], DEFAULT_SERVER_DATE_FORMAT).strftime(date_format) r['displayed_name'] = '-'.join( r[field_name] for field_name in ('move_name', 'ref', 'name') if r[field_name] not in (None, '', '/') diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 9ca93c18f3f..546faee3655 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -36,7 +36,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { if (match) { var barcode = match[1]; - core.bus.trigger('barcode_scanned', barcode); + // Send the target in case there are several barcode widgets on the same page (e.g. + // registering the lot numbers in a stock picking) + core.bus.trigger('barcode_scanned', barcode, this.buffered_key_events[0].target); // Dispatch a barcode_scanned DOM event to elements that have barcode_events="true" set. if (this.buffered_key_events[0].target.getAttribute("barcode_events") === "true") diff --git a/addons/barcodes/static/src/js/barcode_handler_mixin.js b/addons/barcodes/static/src/js/barcode_handler_mixin.js index 5b01516788f..0a9480a979f 100644 --- a/addons/barcodes/static/src/js/barcode_handler_mixin.js +++ b/addons/barcodes/static/src/js/barcode_handler_mixin.js @@ -13,8 +13,13 @@ return { init: function() { var self = this; this._super.apply(this, arguments); - this.__on_barcode_scanned = function () { - self.on_barcode_scanned.apply(self, arguments); + this.__on_barcode_scanned = function (barcode, target) { + // Handle the case where there are several barcode widgets on the same page. Since the + // event is global on the page, all barcode widgets will be triggered. However, we only + // want to keep the event on the target widget. + if ($.contains(target, self.el)) { + self.on_barcode_scanned.call(self, barcode); + } }; this.start_listening(); // Handlers inside a View managed by a ViewManager only listen to barcode events while their view is displayed diff --git a/addons/crm/crm_stage.py b/addons/crm/crm_stage.py index c686b62f3b2..ac453ddc2ff 100644 --- a/addons/crm/crm_stage.py +++ b/addons/crm/crm_stage.py @@ -20,7 +20,7 @@ class crm_stage(osv.Model): _name = "crm.stage" _description = "Stage of case" _rec_name = 'name' - _order = "sequence" + _order = "sequence, name, id" _columns = { 'name': fields.char('Stage Name', required=True, translate=True), diff --git a/addons/sale/sale.py b/addons/sale/sale.py index e3475a100e3..44fde59aee7 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -97,7 +97,7 @@ class SaleOrder(models.Model): for order in self: order.order_line._compute_tax_id() - name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, index=True, default=lambda self: _('New')) + name = fields.Char(string='Order Reference', required=True, copy=False, readonly=True, states={'draft': [('readonly', False)]}, index=True, default=lambda self: _('New')) origin = fields.Char(string='Source Document', help="Reference of the document that generated this sales order request.") client_order_ref = fields.Char(string='Customer Reference', copy=False) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 9462015088f..44550463e55 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -37,6 +37,7 @@ from openerp.modules import get_resource_path from openerp.tools import topological_sort from openerp.tools.translate import _ from openerp.tools import ustr +from openerp.tools.misc import str2bool from openerp import http from openerp.http import request, serialize_exception as _serialize_exception from openerp.exceptions import AccessError @@ -711,7 +712,7 @@ class Database(http.Controller): def restore(self, master_pwd, backup_file, name, copy=False): try: data = base64.b64encode(backup_file.read()) - request.session.proxy("db").restore(master_pwd, name, data, copy) + request.session.proxy("db").restore(master_pwd, name, data, str2bool(copy)) return http.local_redirect('/web/database/manager') except Exception, e: error = "Database restore error: %s" % e diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html index ad7076313ae..8b6fd4f09e7 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -208,10 +208,19 @@
In order to avoid conflicts between databases, Odoo needs to know if this database was moved or copied. + If you don't know, answer "This database is a copy".
+