diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 189e7a3f832..b4972946e7c 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -414,12 +414,11 @@ class AccountBankStatementLine(models.Model):
@api.multi
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']
payment_to_cancel = self.env['account.payment']
for st_line in self:
- moves_to_unbind |= st_line.journal_entry_ids
+ moves_to_unbind = st_line.journal_entry_ids
for move in st_line.journal_entry_ids:
for line in move.line_ids:
payment_to_unreconcile |= line.payment_id
@@ -427,13 +426,15 @@ class AccountBankStatementLine(models.Model):
#there can be several moves linked to a statement line but maximum one created by the line itself
moves_to_cancel |= st_line.journal_entry_ids
payment_to_cancel |= line.payment_id
- moves_to_unbind = moves_to_unbind - moves_to_cancel
- payment_to_unreconcile = payment_to_unreconcile - payment_to_cancel
- if moves_to_unbind:
- moves_to_unbind.write({'statement_line_id': False})
- for move in moves_to_unbind:
- move.line_ids.filtered(lambda x:x.statement_id == st_line.statement_id).write({'statement_id': False})
+ moves_to_unbind = moves_to_unbind - moves_to_cancel
+
+ if moves_to_unbind:
+ moves_to_unbind.write({'statement_line_id': False})
+ for move in moves_to_unbind:
+ move.line_ids.filtered(lambda x: x.statement_id == st_line.statement_id).write({'statement_id': False})
+
+ payment_to_unreconcile = payment_to_unreconcile - payment_to_cancel
if payment_to_unreconcile:
payment_to_unreconcile.unreconcile()
@@ -755,7 +756,7 @@ class AccountBankStatementLine(models.Model):
return data
def _prepare_reconciliation_move_line(self, move, amount):
- """ Prepare the dict of values to create the move line from a statement line.
+ """ Prepare the dict of values to balance the move.
:param recordset move: the account.move to link the move line
:param float amount: the amount of transaction that wasn't already reconciled
@@ -763,25 +764,29 @@ class AccountBankStatementLine(models.Model):
company_currency = self.journal_id.company_id.currency_id
statement_currency = self.journal_id.currency_id or company_currency
st_line_currency = self.currency_id or statement_currency
-
amount_currency = False
- if statement_currency != company_currency or st_line_currency != company_currency:
- # First get the ratio total mount / amount not already reconciled
- if statement_currency == company_currency:
- total_amount = self.amount
- elif st_line_currency == company_currency:
- total_amount = self.amount_currency
- else:
- total_amount = statement_currency.with_context({'date': self.date}).compute(self.amount, company_currency, round=False)
- if float_compare(total_amount, amount, precision_digits=company_currency.rounding) == 0:
- ratio = 1.0
- else:
- ratio = total_amount / amount
- # Then use it to adjust the statement.line field that correspond to the move.line amount_currency
- if statement_currency != company_currency:
- amount_currency = self.amount * ratio
- elif st_line_currency != company_currency:
- amount_currency = self.amount_currency * ratio
+ st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
+ # We have several use case here to compure the currency and amount currency of counterpart line to balance the move:
+ if st_line_currency != company_currency and st_line_currency == statement_currency:
+ # company in currency A, statement in currency B and transaction in currency B
+ # counterpart line must have currency B and correct amount is inverse of already existing lines
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])
+ elif st_line_currency != company_currency and statement_currency == company_currency:
+ # company in currency A, statement in currency A and transaction in currency B
+ # counterpart line must have currency B and correct amount is inverse of already existing lines
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])
+ elif st_line_currency != company_currency and st_line_currency != statement_currency:
+ # company in currency A, statement in currency B and transaction in currency C
+ # counterpart line must have currency B and use rate between B and C to compute correct amount
+ amount_currency = -sum([x.amount_currency for x in move.line_ids])/st_line_currency_rate
+ elif st_line_currency == company_currency and statement_currency != company_currency:
+ # company in currency A, statement in currency B and transaction in currency A
+ # counterpart line must have currency B and amount is computed using the rate between A and B
+ amount_currency = amount/st_line_currency_rate
+
+ # last case is company in currency A, statement in currency A and transaction in currency A
+ # and in this case counterpart line does not need any second currency nor amount_currency
+
return {
'name': self.name,
'move_id': move.id,
@@ -985,8 +990,7 @@ class AccountBankStatementLine(models.Model):
(new_aml | counterpart_move_line).reconcile()
- # Create the move line for the statement line using the bank statement line as the remaining amount
- # This leaves out the amount already reconciled and avoids rounding errors from currency conversion
+ # Balance the move
st_line_amount = -sum([x.balance for x in move.line_ids])
aml_dict = self._prepare_reconciliation_move_line(move, st_line_amount)
aml_dict['payment_id'] = payment and payment.id or False
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 531b12fac5f..72ae1ab6e6b 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -42,7 +42,7 @@ class AccountInvoice(models.Model):
_order = "date_invoice desc, number desc, id desc"
@api.one
- @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', 'date_invoice')
+ @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', 'date_invoice', 'type')
def _compute_amount(self):
self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids)
self.amount_tax = sum(line.amount for line in self.tax_line_ids)
@@ -74,7 +74,7 @@ class AccountInvoice(models.Model):
@api.model
def _default_currency(self):
journal = self._default_journal()
- return journal.currency_id or journal.company_id.currency_id
+ return journal.currency_id or journal.company_id.currency_id or self.env.user.company_id.currency_id
@api.model
def _get_reference_type(self):
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 3ac4cbdec0a..081651a2019 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -81,7 +81,7 @@ class account_abstract_payment(models.AbstractModel):
def _compute_total_invoices_amount(self):
""" Compute the sum of the residual of invoices, expressed in the payment currency """
- payment_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id
+ payment_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id or self.env.user.company_id.currency_id
invoices = self._get_invoices()
if all(inv.currency_id == payment_currency for inv in invoices):
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index bd40fa8816a..ad316e2b0a2 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -402,3 +402,75 @@ class TestReconciliation(AccountingTestCase):
self.assertTrue(aml.reconciled, 'The journal item should be totally reconciled')
self.assertEquals(aml.amount_residual, 0, 'The journal item should be totally reconciled')
self.assertEquals(aml.amount_residual_currency, 0, 'The journal item should be totally reconciled')
+
+ def test_reconcile_bank_statement_with_payment_and_writeoff(self):
+ # Use case:
+ # Company is in EUR, create a bill for 80 USD and register payment of 80 USD.
+ # create a bank statement in USD bank journal with a bank statement line of 85 USD
+ # Reconcile bank statement with payment and put the remaining 5 USD in bank fees or another account.
+
+ invoice = self.create_invoice(type='out_invoice', invoice_amount=80, currency_id=self.currency_usd_id)
+ # register payment on invoice
+ payment = self.env['account.payment'].create({'payment_type': 'inbound',
+ 'payment_method_id': self.env.ref('account.account_payment_method_manual_in').id,
+ 'partner_type': 'customer',
+ 'partner_id': self.partner_agrolait_id,
+ 'amount': 80,
+ 'currency_id': self.currency_usd_id,
+ 'payment_date': time.strftime('%Y') + '-07-15',
+ 'journal_id': self.bank_journal_usd.id,
+ })
+ payment.post()
+ payment_move_line = False
+ bank_move_line = False
+ for l in payment.move_line_ids:
+ if l.account_id.id == self.account_rcv.id:
+ payment_move_line = l
+ else:
+ bank_move_line = l
+ invoice.register_payment(payment_move_line)
+
+ # create bank statement
+ bank_stmt = self.acc_bank_stmt_model.create({
+ 'journal_id': self.bank_journal_usd.id,
+ 'date': time.strftime('%Y') + '-07-15',
+ })
+
+ bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment',
+ 'statement_id': bank_stmt.id,
+ 'partner_id': self.partner_agrolait_id,
+ 'amount': 85,
+ 'date': time.strftime('%Y') + '-07-15',})
+
+ #reconcile the statement with invoice and put remaining in another account
+ bank_stmt_line.process_reconciliation(payment_aml_rec= bank_move_line, new_aml_dicts=[{
+ 'account_id': self.diff_income_account.id,
+ 'debit': 0,
+ 'credit': 5,
+ 'name': 'bank fees',
+ }])
+
+ # Check that move lines associated to bank_statement are correct
+ bank_stmt_aml = self.env['account.move.line'].search([('statement_id', '=', bank_stmt.id)])
+ bank_stmt_aml |= bank_stmt_aml.mapped('move_id').mapped('line_ids')
+ self.assertEquals(len(bank_stmt_aml), 4, "The bank statement should have 4 moves lines")
+ lines = {
+ self.account_usd.id: [
+ {'debit': 3.27, 'credit': 0.0, 'amount_currency': 5, 'currency_id': self.currency_usd_id},
+ {'debit': 52.33, 'credit': 0, 'amount_currency': 80, 'currency_id': self.currency_usd_id}
+ ],
+ self.diff_income_account.id: {'debit': 0.0, 'credit': 3.27, 'amount_currency': -5, 'currency_id': self.currency_usd_id},
+ self.account_rcv.id: {'debit': 0.0, 'credit': 52.33, 'amount_currency': -80, 'currency_id': self.currency_usd_id},
+ }
+ for aml in bank_stmt_aml:
+ line = lines[aml.account_id.id]
+ if type(line) == list:
+ # find correct line inside the list
+ if line[0]['debit'] == round(aml.debit, 2):
+ line = line[0]
+ else:
+ line = line[1]
+ self.assertEquals(round(aml.debit, 2), line['debit'])
+ self.assertEquals(round(aml.credit, 2), line['credit'])
+ self.assertEquals(round(aml.amount_currency, 2), line['amount_currency'])
+ self.assertEquals(aml.currency_id.id, line['currency_id'])
\ No newline at end of file
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py
index 47da5794fe4..0f120e000d8 100644
--- a/addons/account_voucher/models/account_voucher.py
+++ b/addons/account_voucher/models/account_voucher.py
@@ -257,6 +257,7 @@ class AccountVoucher(models.Model):
'date': self.account_date,
'tax_ids': [(4,t.id) for t in line.tax_ids],
'amount_currency': line.price_subtotal if current_currency != company_currency else 0.0,
+ 'currency_id': company_currency != current_currency and current_currency or False,
}
self.env['account.move.line'].with_context(apply_taxes=True).create(move_line)
@@ -282,7 +283,7 @@ class AccountVoucher(models.Model):
move = self.env['account.move'].create(voucher.account_move_get())
# Get the name of the account_move just created
# Create the first line of the voucher
- move_line = self.env['account.move.line'].with_context(ctx).create(voucher.first_move_line_get(move.id, company_currency, current_currency))
+ move_line = self.env['account.move.line'].with_context(ctx).create(voucher.with_context(ctx).first_move_line_get(move.id, company_currency, current_currency))
line_total = move_line.debit - move_line.credit
if voucher.voucher_type == 'sale':
line_total = line_total - voucher._convert_amount(voucher.tax_amount)
diff --git a/addons/hr_payroll/models/hr_payroll.py b/addons/hr_payroll/models/hr_payroll.py
index 4e85266fa01..6cdf51562fd 100644
--- a/addons/hr_payroll/models/hr_payroll.py
+++ b/addons/hr_payroll/models/hr_payroll.py
@@ -404,7 +404,9 @@ class HrPayslip(models.Model):
def _sum_salary_rule_category(localdict, category, amount):
if category.parent_id:
localdict = _sum_salary_rule_category(localdict, category.parent_id, amount)
- localdict['categories'].dict[category.code] = category.code in localdict['categories'].dict and localdict['categories'].dict[category.code] + amount or amount
+ if category.code in localdict['categories'].dict:
+ amount += localdict['categories'].dict[category.code]
+ localdict['categories'].dict[category.code] = amount
return localdict
class BrowsableObject(object):
diff --git a/addons/l10n_sa/data/account_chart_template_data.xml b/addons/l10n_sa/data/account_chart_template_data.xml
index 6038be6a328..bd6c3a3597d 100644
--- a/addons/l10n_sa/data/account_chart_template_data.xml
+++ b/addons/l10n_sa/data/account_chart_template_data.xml
@@ -12,7 +12,7 @@
1
1
6
-
+
diff --git a/addons/link_tracker/models/link_tracker.py b/addons/link_tracker/models/link_tracker.py
index 7b30151ca66..89ab91ef48b 100644
--- a/addons/link_tracker/models/link_tracker.py
+++ b/addons/link_tracker/models/link_tracker.py
@@ -10,7 +10,7 @@ from lxml import html
from urllib2 import urlopen
from urlparse import urljoin
from urlparse import urlparse
-from werkzeug import url_encode
+from werkzeug import url_encode, unescape
from odoo import models, fields, api, _
from odoo.tools import ustr
@@ -56,7 +56,7 @@ class link_tracker(models.Model):
href = match[0]
long_url = match[1]
- vals['url'] = long_url
+ vals['url'] = unescape(long_url)
if not blacklist or not [s for s in blacklist if s in long_url] and not long_url.startswith(short_schema):
link = self.create(vals)
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index aeea21cfbd9..125248aee0f 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -746,7 +746,7 @@ class PosOrderLine(models.Model):
return line
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.user.company_id)
- name = fields.Char(string='Line No', required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('pos.order.line'))
+ name = fields.Char(string='Line No', required=True, copy=False)
notice = fields.Char(string='Discount Notice')
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], required=True, change_default=True)
price_unit = fields.Float(string='Unit Price', digits=0)
@@ -760,6 +760,31 @@ class PosOrderLine(models.Model):
tax_ids_after_fiscal_position = fields.Many2many('account.tax', compute='_get_tax_ids_after_fiscal_position', string='Taxes')
pack_lot_ids = fields.One2many('pos.pack.operation.lot', 'pos_order_line_id', string='Lot/serial Number')
+ @api.model
+ def create(self, values):
+ if values.get('order_id') and not values.get('name'):
+ # set name based on the sequence specified on the config
+ config_id = self.env['pos.order'].browse(values['order_id']).session_id.config_id.id
+ # HACK: sequence created in the same transaction as the config
+ # cf TODO master is pos.config create
+ # remove me saas-15
+ self.env.cr.execute("""
+ SELECT s.id
+ FROM ir_sequence s
+ JOIN pos_config c
+ ON s.create_date=c.create_date
+ WHERE c.id = %s
+ AND s.code = 'pos.order.line'
+ LIMIT 1
+ """, (config_id,))
+ sequence = self.env.cr.fetchone()
+ if sequence:
+ values['name'] = self.env['ir.sequence'].browse(sequence[0])._next()
+ if not values.get('name'):
+ # fallback on any pos.order sequence
+ values['name'] = self.env['ir.sequence'].next_by_code('pos.order.line')
+ return super(PosOrderLine, self).create(values)
+
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
def _compute_amount_line_all(self):
for line in self:
diff --git a/addons/point_of_sale/static/src/img/scroll-down.png b/addons/point_of_sale/static/src/img/scroll-down.png
new file mode 100644
index 00000000000..5fd07589157
Binary files /dev/null and b/addons/point_of_sale/static/src/img/scroll-down.png differ
diff --git a/addons/point_of_sale/static/src/img/scroll-up.png b/addons/point_of_sale/static/src/img/scroll-up.png
new file mode 100644
index 00000000000..b34a90014a2
Binary files /dev/null and b/addons/point_of_sale/static/src/img/scroll-up.png differ
diff --git a/addons/sale/models/product_template.py b/addons/sale/models/product_template.py
index 6e04794c2f7..822a0d1ac42 100644
--- a/addons/sale/models/product_template.py
+++ b/addons/sale/models/product_template.py
@@ -30,7 +30,7 @@ class ProductTemplate(models.Model):
def action_view_sales(self):
self.ensure_one()
action = self.env.ref('sale.action_product_sale_list')
- product_ids = self.product_variant_ids.ids
+ product_ids = self.with_context(active_test=False).product_variant_ids.ids
return {
'name': action.name,
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 637ee18b5f1..7214c6260c5 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -905,13 +905,18 @@ class SaleOrderLine(models.Model):
PricelistItem = self.env['product.pricelist.item']
field_name = 'lst_price'
currency_id = None
+ product_currency = None
if rule_id:
pricelist_item = PricelistItem.browse(rule_id)
if pricelist_item.base == 'standard_price':
field_name = 'standard_price'
+ if pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id:
+ field_name = 'price'
+ product = product.with_context(pricelist=pricelist_item.base_pricelist_id.id)
+ product_currency = pricelist_item.base_pricelist_id.currency_id
currency_id = pricelist_item.pricelist_id.currency_id
- product_currency = (product.company_id and product.company_id.currency_id) or self.env.user.company_id.currency_id
+ product_currency = product_currency or(product.company_id and product.company_id.currency_id) or self.env.user.company_id.currency_id
if not currency_id:
currency_id = product_currency
cur_factor = 1.0
diff --git a/addons/sale_margin/__init__.py b/addons/sale_margin/__init__.py
index a5f67aebd79..d1b51ace91d 100644
--- a/addons/sale_margin/__init__.py
+++ b/addons/sale_margin/__init__.py
@@ -1,5 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import models
-import report
+from functools import partial
+import openerp
+from openerp import api, SUPERUSER_ID
+
+import models # noqa
+import report # noqa
+
+
+def uninstall_hook(cr, registry):
+ def recreate_view(dbname):
+ db_registry = openerp.modules.registry.RegistryManager.new(dbname)
+ with api.Environment.manage(), db_registry.cursor() as cr:
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ if 'sale.report' in env:
+ env['sale.report'].init()
+
+ cr.after("commit", partial(recreate_view, cr.dbname))
diff --git a/addons/sale_margin/__manifest__.py b/addons/sale_margin/__manifest__.py
index 5d86da377ac..2e901ec0f74 100644
--- a/addons/sale_margin/__manifest__.py
+++ b/addons/sale_margin/__manifest__.py
@@ -15,4 +15,5 @@ Price and Cost Price.
'depends':['sale'],
'demo':['data/sale_margin_demo.xml'],
'data':['security/ir.model.access.csv','views/sale_margin_view.xml'],
+ 'uninstall_hook': "uninstall_hook",
}
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index e98251c5410..c846e969827 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -822,7 +822,8 @@
- ![]()