diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index bbe399a4647..b1544067bfe 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -422,20 +422,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
####################################################
@@ -567,7 +575,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_invoice.py b/addons/account/models/account_invoice.py
index 2b4aaf445e9..f51575e1d8d 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -718,6 +718,9 @@ class AccountInvoice(models.Model):
line2[tmp]['credit'] = (am < 0) and -am or 0.0
line2[tmp]['amount_currency'] += l['amount_currency']
line2[tmp]['analytic_line_ids'] += l['analytic_line_ids']
+ qty = l.get('quantity')
+ if qty:
+ line2[tmp]['quantity'] = line2[tmp].get('quantity', 0.0) + qty
else:
line2[tmp] = l
line = []
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index 3f7347fdabe..84b449d6b8d 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 025bd8412e0..1b50940d2ac 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -592,7 +592,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
@@ -1076,13 +1083,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'],
@@ -1091,7 +1098,15 @@ 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:
+ ctx = {}
+ if 'date' in vals:
+ ctx['date'] = vals['date']
+ temp['currency_id'] = bank.currency_id.id
+ temp['amount_currency'] = bank.company_id.currency_id.with_context(ctx).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:
@@ -1133,10 +1148,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):
diff --git a/addons/account/report/account_partner_ledger.py b/addons/account/report/account_partner_ledger.py
index 9c5eee46151..80c1fec2a20 100644
--- a/addons/account/report/account_partner_ledger.py
+++ b/addons/account/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/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 4740e4a1faf..46004b1cb26 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -312,7 +312,7 @@ class MassMailing(models.Model):
create_date = fields.Datetime(string='Creation Date')
sent_date = fields.Datetime(string='Sent Date', oldname='date', copy=False)
schedule_date = fields.Datetime(string='Schedule in the Future')
- body_html = fields.Html(string='Body', translate=True)
+ body_html = fields.Html(string='Body', translate=True, sanitize=False)
attachment_ids = fields.Many2many('ir.attachment', 'mass_mailing_ir_attachments_rel',
'mass_mailing_id', 'attachment_id', string='Attachments')
keep_archives = fields.Boolean(string='Keep Archives')
diff --git a/addons/payment_stripe/static/src/js/stripe.js b/addons/payment_stripe/static/src/js/stripe.js
index c81c6c7f8dc..ca2d6eb9ad2 100644
--- a/addons/payment_stripe/static/src/js/stripe.js
+++ b/addons/payment_stripe/static/src/js/stripe.js
@@ -40,12 +40,23 @@ odoo.define('payment_stripe.stripe', function(require) {
if(!$(this).find('i').length)
$(this).append('');
$(this).attr('disabled','disabled');
- handler.open({
- name: $("input[name='merchant']").val(),
- description: $("input[name='invoice_num']").val(),
- currency: $("input[name='currency']").val(),
- amount: $("input[name='amount']").val()*100
+
+ var $form = $(e.currentTarget).parents('form');
+ var acquirer_id = $(e.currentTarget).parents('div.oe_sale_acquirer_button').first().data('id');
+ if (! acquirer_id) {
+ return false;
+ }
+
+ ajax.jsonRpc('/shop/payment/transaction/' + acquirer_id, 'call', {}).then(function (data) {
+ $form.html(data);
+ handler.open({
+ name: $("input[name='merchant']").val(),
+ description: $("input[name='invoice_num']").val(),
+ currency: $("input[name='currency']").val(),
+ amount: $("input[name='amount']").val()*100
+ });
+ e.preventDefault();
});
- e.preventDefault();
+
});
});
diff --git a/addons/point_of_sale/views/pos_order_view.xml b/addons/point_of_sale/views/pos_order_view.xml
index 4475a0a590e..07aa8d8cc6a 100644
--- a/addons/point_of_sale/views/pos_order_view.xml
+++ b/addons/point_of_sale/views/pos_order_view.xml
@@ -50,7 +50,8 @@
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".
+