diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000000..98eb36db26e
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,40 @@
+# Security Policy
+
+## Supported Versions
+
+| Version | Supported |
+| ------- | ------------------ |
+| 12.0 | :white_check_mark: |
+| 11.0 | :white_check_mark: |
+| 10.0 | :white_check_mark: |
+| <=9.0 | :x: |
+
+## Reporting a Vulnerability
+
+Please share privately the details of your security vulnerability by contacting our Security Team:
+[Contact Info](https://www.odoo.com/security-report)
+
+Make sure to include as much information as possible, with the detailed steps to reproduce the problem,
+the versions that are affected, the expected results and actual results, and any other information that
+might help us react faster and more efficiently.
+
+We tend to prefer _text-based descriptions_ accompanied with a proof-of-concept script/exploit, rather
+than screenshots and videos.
+
+Our [Responsible Disclosure](https://www.odoo.com/security-report) page gives an overview of the
+process, including:
+
+ - Our Incident Response Procedure (what will happen after you report an issue)
+ - Our Rules (what you can and cannot do while researching security issues)
+ - Guidelines with **DO REPORT** and **DO NOT REPORT** issues
+ (what kind of issues will be accepted/rejected)
+
+
+## Important note
+
+We receive a majority of security reports that have little to no impact on the security of Odoo or
+the Odoo Cloud, and we ultimately have to reject them. To avoid a disappointing experience when
+contacting us, please try to put together a proof-of-concept attack and take a critical look at
+what's really at risk.
+If the proposed attack scenario turns out unrealistic, your report will probably be rejected.
+Also be sure to review our list of [non-qualifying issues](https://www.odoo.com/security-report#what).
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 0931f8ebd3d..6be56cd4795 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -1067,18 +1067,20 @@ class AccountTax(models.Model):
@api.model
def default_get(self, vals):
- rslt = super(AccountTax, self).default_get(vals)
+ # company_id is added so that we are sure to fetch a default value from it to use in repartition lines, below
+ rslt = super(AccountTax, self).default_get(vals + ['company_id'])
+ company_id = rslt.get('company_id')
if 'refund_repartition_line_ids' in vals:
rslt['refund_repartition_line_ids'] = [
- (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
- (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
+ (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
+ (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
]
if 'invoice_repartition_line_ids' in vals:
rslt['invoice_repartition_line_ids'] = [
- (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
- (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': rslt.get('company_id')}),
+ (0, 0, { 'repartition_type': 'base', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
+ (0, 0, { 'repartition_type': 'tax', 'factor_percent': 100.0, 'tag_ids': [], 'company_id': company_id}),
]
return rslt
@@ -1174,7 +1176,8 @@ class AccountTax(models.Model):
str(invoice_tax_val['account_id']) + '-' + \
str(invoice_tax_val['account_analytic_id']) + '-' + \
str(invoice_tax_val.get('analytic_tag_ids', [])) + '-' + \
- str(invoice_tax_val.get('tax_ids') or [])
+ str(invoice_tax_val.get('tax_ids') or []) + '-' + \
+ str(invoice_tax_val.get('tag_ids') or [])
def _compute_amount(self, base_amount, price_unit, quantity=1.0, product=None, partner=None):
""" Returns the amount of a single tax. base_amount is the actual amount on which the tax is applied, which is
@@ -1235,6 +1238,10 @@ class AccountTax(models.Model):
all_taxes += tax
return all_taxes
+ def get_tax_tags(self, is_refund, repartition_type):
+ rep_lines = self.mapped(is_refund and 'refund_repartition_line_ids' or 'invoice_repartition_line_ids')
+ return rep_lines.filtered(lambda x: x.repartition_type == repartition_type).mapped('tag_ids')
+
@api.multi
def compute_all(self, price_unit, currency=None, quantity=1.0, product=None, partner=None, is_refund=False):
""" Returns all information required to apply taxes (in self + their children in case of a tax group).
@@ -1392,12 +1399,23 @@ class AccountTax(models.Model):
if tax.price_include and not total_included_checkpoints.get(i):
cumulated_tax_included_amount += tax_amount
+ # If the tax affects the base of subsequent taxes, its tax move lines must
+ # receive the base tags and tag_ids of these taxes, so that the tax report computes
+ # the right total
+ subsequent_taxes = self.env['account.tax']
+ subsequent_tags = self.env['account.account.tag']
+ if tax.include_base_amount:
+ subsequent_taxes = taxes[i+1:]
+ subsequent_tags = subsequent_taxes.get_tax_tags(is_refund, 'base')
+
+ # Compute the tax lines
tax_repartition_lines = is_refund and tax.refund_repartition_line_ids or tax.invoice_repartition_line_ids
repartition_lines_to_treat = len(tax_repartition_lines)
total_amount = 0
for repartition_line in tax_repartition_lines.filtered(lambda x: x.repartition_type == 'tax'):
# In case some rounding error occurs, we compensate for it on the last line
line_amount = round(sign * tax_amount * repartition_line.factor if repartition_lines_to_treat != 1 else tax_amount - total_amount, prec)
+
taxes_vals.append({
'id': tax.id,
'name': partner and tax.with_context(lang=partner.lang).name or tax.name,
@@ -1409,7 +1427,8 @@ class AccountTax(models.Model):
'price_include': tax.price_include,
'tax_exigibility': tax.tax_exigibility,
'tax_repartition_line_id': repartition_line.id,
- 'tag_ids': [(6, False, repartition_line.tag_ids.ids)],
+ 'tag_ids': [(6, False, (repartition_line.tag_ids + subsequent_tags).ids)],
+ 'tax_ids': [(6, False, subsequent_taxes.ids)]
})
total_amount += line_amount
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 8cea4c9bd92..7537227fdae 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -446,7 +446,6 @@ class AccountBankStatementLine(models.Model):
""" Prepare the dict of values to balance the move.
:param recordset move: the account.move to link the move line
- :param dict move: a dict of vals of a account.move which will be created later
:param float amount: the amount of transaction that wasn't already reconciled
"""
company_currency = self.journal_id.company_id.currency_id
@@ -454,23 +453,19 @@ class AccountBankStatementLine(models.Model):
st_line_currency = self.currency_id or statement_currency
amount_currency = False
st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
- if isinstance(move, dict):
- amount_sum = sum(x[2].get('amount_currency', 0) for x in move['line_ids'])
- else:
- amount_sum = sum(x.amount_currency for x in move.line_ids)
# We have several use case here to compare 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 = -amount_sum
+ 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 = -amount_sum
+ 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 = -amount_sum/st_line_currency_rate
+ 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
@@ -479,8 +474,9 @@ class AccountBankStatementLine(models.Model):
# 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
- aml_dict = {
+ return {
'name': self.name,
+ 'move_id': move.id,
'partner_id': self.partner_id and self.partner_id.id or False,
'account_id': amount >= 0 \
and self.statement_id.journal_id.default_credit_account_id.id \
@@ -491,71 +487,22 @@ class AccountBankStatementLine(models.Model):
'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False),
'amount_currency': amount_currency,
}
- if isinstance(move, self.env['account.move'].__class__):
- aml_dict['move_id'] = move.id
- return aml_dict
- @api.multi
def fast_counterpart_creation(self):
"""This function is called when confirming a bank statement and will allow to automatically process lines without
going in the bank reconciliation widget. By setting an account_id on bank statement lines, it will create a journal
entry using that account to counterpart the bank account
"""
- payment_list = []
- move_list = []
- account_type_receivable = self.env.ref('account.data_account_type_receivable')
- already_done_stmt_line_ids = [a['statement_line_id'][0] for a in self.env['account.move.line'].read_group([('statement_line_id', 'in', self.ids)], ['statement_line_id'], ['statement_line_id'])]
- managed_st_line = []
for st_line in self:
# Technical functionality to automatically reconcile by creating a new move line
- if st_line.account_id and not st_line.id in already_done_stmt_line_ids:
- managed_st_line.append(st_line.id)
- # Create payment vals
- total = st_line.amount
- payment_methods = (total > 0) and st_line.journal_id.inbound_payment_method_ids or st_line.journal_id.outbound_payment_method_ids
- currency = st_line.journal_id.currency_id or st_line.company_id.currency_id
- partner_type = 'customer' if st_line.account_id.user_type_id == account_type_receivable else 'supplier'
- payment_list.append({
- 'payment_method_id': payment_methods and payment_methods[0].id or False,
- 'payment_type': total > 0 and 'inbound' or 'outbound',
- 'partner_id': st_line.partner_id.id,
- 'partner_type': partner_type,
- 'journal_id': st_line.statement_id.journal_id.id,
- 'payment_date': st_line.date,
- 'state': 'reconciled',
- 'currency_id': currency.id,
- 'amount': abs(total),
- 'communication': st_line._get_communication(payment_methods[0] if payment_methods else False),
- 'name': st_line.statement_id.name or _("Bank Statement %s") % st_line.date,
- })
-
- # Create move and move line vals
- move_vals = st_line._prepare_reconciliation_move(st_line.statement_id.name)
- aml_dict = {
+ if st_line.account_id and not st_line.journal_entry_ids.ids:
+ vals = {
'name': st_line.name,
'debit': st_line.amount < 0 and -st_line.amount or 0.0,
'credit': st_line.amount > 0 and st_line.amount or 0.0,
'account_id': st_line.account_id.id,
- 'partner_id': st_line.partner_id.id,
- 'statement_line_id': st_line.id,
}
- st_line._prepare_move_line_for_currency(aml_dict, st_line.date or fields.Date.context_today())
- move_vals['line_ids'] = [(0, 0, aml_dict)]
- balance_line = self._prepare_reconciliation_move_line(move_vals, st_line.amount)
- move_vals['line_ids'].append((0, 0, balance_line))
- move_list.append(move_vals)
-
- # Creates
- payment_ids = self.env['account.payment'].create(payment_list)
- for payment_id, move_vals in zip(payment_ids, move_list):
- for line in move_vals['line_ids']:
- line[2]['payment_id'] = payment_id.id
- move_ids = self.env['account.move'].create(move_list)
- move_ids.post()
-
- for move, st_line, payment in zip(move_ids, self.browse(managed_st_line), payment_ids):
- st_line.write({'move_name': move.name})
- payment.write({'payment_reference': move.name})
+ st_line.process_reconciliation(new_aml_dicts=[vals])
def _get_communication(self, payment_method_id):
return self.name or ''
@@ -683,6 +630,8 @@ class AccountBankStatementLine(models.Model):
# Create move line(s). Either matching an existing journal entry (eg. invoice), in which
# case we reconcile the existing and the new move lines together, or being a write-off.
if counterpart_aml_dicts or new_aml_dicts:
+ st_line_currency = self.currency_id or statement_currency
+ st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
# Create the move
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
@@ -705,12 +654,32 @@ class AccountBankStatementLine(models.Model):
# Complete dicts to create both counterpart move lines and write-offs
to_create = (counterpart_aml_dicts + new_aml_dicts)
+ company = self.company_id
date = self.date or fields.Date.today()
for aml_dict in to_create:
aml_dict['move_id'] = move.id
aml_dict['partner_id'] = self.partner_id.id
aml_dict['statement_line_id'] = self.id
- self._prepare_move_line_for_currency(aml_dict, date)
+ if st_line_currency.id != company_currency.id:
+ aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
+ aml_dict['currency_id'] = st_line_currency.id
+ if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
+ # Statement is in company currency but the transaction is in foreign currency
+ aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
+ aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
+ elif self.currency_id and st_line_currency_rate:
+ # Statement is in foreign currency and the transaction is in another one
+ aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
+ aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
+ else:
+ # Statement is in foreign currency and no extra currency is given for the transaction
+ aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
+ aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
+ elif statement_currency.id != company_currency.id:
+ # Statement is in foreign currency but the transaction is in company currency
+ prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
+ aml_dict['amount_currency'] = prorata_factor * self.amount
+ aml_dict['currency_id'] = statement_currency.id
# Create write-offs
for aml_dict in new_aml_dicts:
@@ -760,36 +729,6 @@ class AccountBankStatementLine(models.Model):
counterpart_moves.assert_balanced()
return counterpart_moves
- @api.multi
- def _prepare_move_line_for_currency(self, aml_dict, date):
- self.ensure_one()
- 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
- st_line_currency_rate = self.currency_id and (self.amount_currency / self.amount) or False
- company = self.company_id
-
- if st_line_currency.id != company_currency.id:
- aml_dict['amount_currency'] = aml_dict['debit'] - aml_dict['credit']
- aml_dict['currency_id'] = st_line_currency.id
- if self.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
- # Statement is in company currency but the transaction is in foreign currency
- aml_dict['debit'] = company_currency.round(aml_dict['debit'] / st_line_currency_rate)
- aml_dict['credit'] = company_currency.round(aml_dict['credit'] / st_line_currency_rate)
- elif self.currency_id and st_line_currency_rate:
- # Statement is in foreign currency and the transaction is in another one
- aml_dict['debit'] = statement_currency._convert(aml_dict['debit'] / st_line_currency_rate, company_currency, company, date)
- aml_dict['credit'] = statement_currency._convert(aml_dict['credit'] / st_line_currency_rate, company_currency, company, date)
- else:
- # Statement is in foreign currency and no extra currency is given for the transaction
- aml_dict['debit'] = st_line_currency._convert(aml_dict['debit'], company_currency, company, date)
- aml_dict['credit'] = st_line_currency._convert(aml_dict['credit'], company_currency, company, date)
- elif statement_currency.id != company_currency.id:
- # Statement is in foreign currency but the transaction is in company currency
- prorata_factor = (aml_dict['debit'] - aml_dict['credit']) / self.amount_currency
- aml_dict['amount_currency'] = prorata_factor * self.amount
- aml_dict['currency_id'] = statement_currency.id
-
def _check_invoice_state(self, invoice):
if invoice.state == 'in_payment' and all([payment.state == 'reconciled' for payment in invoice.mapped('payment_move_line_ids.payment_id')]):
invoice.write({'state': 'paid'})
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 47c0cb42b27..8770b232808 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -879,7 +879,7 @@ class AccountInvoice(models.Model):
domain = {}
company_id = self.company_id.id
p = self.partner_id if not company_id else self.partner_id.with_context(force_company=company_id)
- type = self.type
+ type = self.type or self.env.context.get('type', 'out_invoice')
if p:
rec_account = p.property_account_receivable_id
pay_account = p.property_account_payable_id
@@ -1081,7 +1081,7 @@ class AccountInvoice(models.Model):
else:
return self.env.ref('account.invoice_form').id
- def _prepare_tax_line_vals(self, line, tax, tax_ids):
+ def _prepare_tax_line_vals(self, line, tax):
''' Prepare values to create an account.invoice.tax line.
:param line: An account.invoice.line record.
:param tax: Tax values outputted by compute_all() in account.tax.
@@ -1099,8 +1099,9 @@ class AccountInvoice(models.Model):
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
'account_id': tax['account_id'] or line.account_id.id,
'analytic_tag_ids': tax['analytic'] and line.analytic_tag_ids.ids or False,
- 'tax_ids': tax_ids and [(6, None, tax_ids)] or False,
'tax_repartition_line_id': tax.get('tax_repartition_line_id'), # For base amount, we let this field empty
+ 'tag_ids': tax['tag_ids'],
+ 'tax_ids': tax['tax_ids'],
}
# If the taxes generate moves on the same financial account as the invoice line,
@@ -1130,7 +1131,6 @@ class AccountInvoice(models.Model):
price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = line.invoice_line_tax_ids._origin.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id, is_refund=self.type in ('in_refund', 'out_refund'))['taxes']
- affecting_base_tax_ids = []
for tax_vals in taxes:
# Retrieve the tax record (not in tax_vals when dealing with group of taxes).
if tax_map.get(tax_vals['id']):
@@ -1138,7 +1138,7 @@ class AccountInvoice(models.Model):
else:
tax = tax_map[tax_vals['id']] = self.env['account.tax'].browse(tax_vals['id'])
- val = self._prepare_tax_line_vals(line, tax_vals, affecting_base_tax_ids)
+ val = self._prepare_tax_line_vals(line, tax_vals)
key = tax.get_grouping_key(val)
if key not in tax_grouped:
@@ -1148,11 +1148,6 @@ class AccountInvoice(models.Model):
for field in default_tax_group_fields:
tax_grouped[key][field] += round_curr(val.get(field)) or 0
- if tax.amount_type == "group":
- affecting_base_tax_ids += tax.children_tax_ids.filtered(lambda t: is_tax_affecting_base_amount(t)).ids
- elif is_tax_affecting_base_amount(tax):
- affecting_base_tax_ids.append(tax.id)
-
return tax_grouped
@api.multi
@@ -1268,7 +1263,6 @@ class AccountInvoice(models.Model):
def tax_line_move_line_get(self):
self.ensure_one()
-
res = []
# loop the invoice.tax.line in reversal sequence
for tax_line in sorted(self.tax_line_ids, key=lambda x: -x.sequence):
@@ -1288,7 +1282,8 @@ class AccountInvoice(models.Model):
'invoice_id': self.id,
'tax_ids': tax_line.tax_ids and [(6, 0, tax_line.tax_ids.ids)] or False, # We don't pass an empty recordset here, as it would reset the tax_exibility of the line, due to the condition in account.move.line's create
'tax_repartition_line_id': tax_line.tax_repartition_line_id.id,
- 'tag_ids': [(6, 0, tax_line.tax_repartition_line_id.tag_ids.ids)],
+ 'tag_ids': [(6, 0, tax_line.tag_ids.ids)],
+ 'tax_base_amount': tax_line.base,
})
return res
@@ -2146,8 +2141,8 @@ class AccountInvoiceTax(models.Model):
company_id = fields.Many2one('res.company', string='Company', related='account_id.company_id', store=True, readonly=True)
currency_id = fields.Many2one('res.currency', related='invoice_id.currency_id', store=True, readonly=True)
base = fields.Monetary(string='Base')
- tax_ids = fields.Many2many('account.tax', string='Affecting Base Taxes',
- help='Taxes affecting the tax base amount applied before this one.')
+ tax_ids = fields.Many2many('account.tax', string='Affecting Base Taxes', help='Taxes whose base amount needs has to be affected by this tax line.')
+ tag_ids = fields.Many2many(string="Tags", comodel_name='account.account.tag', help="The taxes that will be applied on the move line generated for this tax entry")
@api.depends('amount', 'amount_rounding')
def _compute_amount_total(self):
diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py
index cf1f40f7a94..08b119405b4 100644
--- a/addons/account/models/account_journal_dashboard.py
+++ b/addons/account/models/account_journal_dashboard.py
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
from babel.dates import format_datetime, format_date
from odoo import models, api, _, fields
+from odoo.osv import expression
from odoo.release import version
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF, safe_eval
from odoo.tools.misc import formatLang, format_date as odoo_format_date
@@ -427,10 +428,16 @@ class account_journal(models.Model):
action_name = 'action_view_bank_statement_tree'
elif self.type == 'sale':
action_name = 'action_invoice_tree1'
- self = self.with_context(use_domain=[('journal_id', '=', self.id)])
+ use_domain = expression.AND(
+ [self.env.context.get('use_domain', []), [('journal_id', '=', self.id)]]
+ )
+ self = self.with_context(use_domain=use_domain)
elif self.type == 'purchase':
action_name = 'action_vendor_bill_template'
- self = self.with_context(use_domain=[('journal_id', '=', self.id)])
+ use_domain = expression.AND(
+ [self.env.context.get('use_domain', []), [('journal_id', '=', self.id)]]
+ )
+ self = self.with_context(use_domain=use_domain)
else:
action_name = 'action_move_journal_line'
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 8fb43c81d3d..19e3ea5dec4 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -87,14 +87,12 @@ class AccountMove(models.Model):
if line.account_id.user_type_id.type in ('receivable', 'payable'):
amount = abs(line.debit - line.credit)
total_amount += amount
+ for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
+ total_reconciled += partial_line.amount
precision_currency = move.currency_id or move.company_id.currency_id
if float_is_zero(total_amount, precision_rounding=precision_currency.rounding):
move.matched_percentage = 1.0
else:
- for line in move.line_ids:
- if line.account_id.user_type_id.type in ('receivable', 'payable'):
- for partial_line in (line.matched_debit_ids + line.matched_credit_ids):
- total_reconciled += partial_line.amount
move.matched_percentage = total_reconciled / total_amount
@api.multi
@@ -290,6 +288,7 @@ class AccountMove(models.Model):
'company_currency_id': self.company_id.currency_id.id,
'tax_repartition_line_id': line_vals['tax_repartition_line_id'],
'tag_ids': line_vals['tag_ids'],
+ 'tax_base_amount': line_vals['base'],
}
# N.B. currency_id/amount_currency are not set because if we have two lines with the same tax
# and different currencies, we have no idea which currency set on this line.
@@ -580,7 +579,7 @@ class AccountMoveLine(models.Model):
else:
date = partial_line.credit_move_id.date if partial_line.debit_move_id == line else partial_line.debit_move_id.date
rate = line.currency_id.with_context(date=date).rate
- amount_residual_currency += sign_partial_line * line.currency_id.round(partial_line.amount * rate)
+ amount_residual_currency += sign_partial_line * partial_line.amount * rate
#computing the `reconciled` field.
reconciled = False
@@ -609,15 +608,6 @@ class AccountMoveLine(models.Model):
currency = self.env['account.journal'].browse(context['default_journal_id']).currency_id
return currency
- @api.depends('move_id.line_ids', 'move_id.line_ids.tax_line_id', 'move_id.line_ids.debit', 'move_id.line_ids.credit')
- def _compute_tax_base_amount(self):
- for move_line in self:
- if move_line.tax_line_id:
- base_lines = move_line.move_id.line_ids.filtered(lambda line: move_line.tax_line_id in line.tax_ids)
- move_line.tax_base_amount = abs(sum(base_lines.mapped('balance')))
- else:
- move_line.tax_base_amount = 0
-
@api.depends('move_id')
def _compute_parent_state(self):
for record in self.filtered('move_id'):
@@ -641,7 +631,7 @@ class AccountMoveLine(models.Model):
help="The residual amount on a journal item expressed in the company currency.")
amount_residual_currency = fields.Monetary(compute='_amount_residual', string='Residual Amount in Currency', store=True,
help="The residual amount on a journal item expressed in its currency (possibly not the company currency).")
- tax_base_amount = fields.Monetary(string="Base Amount", compute='_compute_tax_base_amount', currency_field='company_currency_id', store=True)
+ tax_base_amount = fields.Monetary(string="Base Amount", currency_field='company_currency_id')
account_id = fields.Many2one('account.account', string='Account', required=True, index=True,
ondelete="cascade", domain=[('deprecated', '=', False)], default=lambda self: self._context.get('account_id', False))
move_id = fields.Many2one('account.move', string='Journal Entry', ondelete="cascade",
@@ -699,21 +689,23 @@ class AccountMoveLine(models.Model):
if {'debit', 'credit', 'partner_id', 'account_id'}.isdisjoint(fields):
return rec
#compute the default credit/debit of the next line in case of a manual entry
- AccountMove = self.env['account.move']
+ move = self.env['account.move'].new({'line_ids': self._context['line_ids']})
balance = 0
- for line in AccountMove.resolve_2many_commands(
- 'line_ids', self._context['line_ids'], fields=['credit', 'debit']):
- balance += line.get('debit', 0) - line.get('credit', 0)
- if len(self._context['line_ids']) > 1:
- lines = AccountMove.resolve_2many_commands('line_ids', self._context['line_ids'][-2:], fields=['partner_id', 'account_id'])
- if lines[0].get('partner_id', False) == lines[1].get('partner_id', False):
- rec.update({'partner_id': lines[0].get('partner_id', False)})
- if lines[0].get('account_id', False) == lines[1].get('account_id', False):
- rec.update({'account_id': lines[0].get('account_id', False)})
+ for line in move.line_ids:
+ balance += line.debit - line.credit
if balance < 0:
rec.update({'debit': -balance})
if balance > 0:
rec.update({'credit': balance})
+
+ # Set the partner/account to the same value as the two last lines if they are equal
+ if len(move.line_ids) > 1:
+ last_lines = move.line_ids[-2:]
+ if last_lines[0].account_id == last_lines[1].account_id:
+ rec['account_id'] = last_lines[0].account_id.id
+ if last_lines[0].partner_id == last_lines[1].partner_id:
+ rec['partner_id'] = last_lines[0].partner_id.id
+
return rec
@api.multi
@@ -821,21 +813,22 @@ class AccountMoveLine(models.Model):
In case of full reconciliation, all moves belonging to the reconciliation will belong to the same account_full_reconcile object.
"""
# Get first all aml involved
- todo = self.env['account.partial.reconcile'].search_read(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)], ['debit_move_id', 'credit_move_id'])
- amls = set(self.ids)
+ part_recs = self.env['account.partial.reconcile'].search(['|', ('debit_move_id', 'in', self.ids), ('credit_move_id', 'in', self.ids)])
+ amls = self
+ todo = set(part_recs)
seen = set()
while todo:
- aml_ids = [rec['debit_move_id'][0] for rec in todo if rec['debit_move_id']] + [rec['credit_move_id'][0] for rec in todo if rec['credit_move_id']]
- amls |= set(aml_ids)
- seen |= set([rec['id'] for rec in todo])
- todo = self.env['account.partial.reconcile'].search_read(['&', '|', ('credit_move_id', 'in', aml_ids), ('debit_move_id', 'in', aml_ids), '!', ('id', 'in', list(seen))], ['debit_move_id', 'credit_move_id'])
-
- partial_rec_ids = list(seen)
+ partial_rec = todo.pop()
+ seen.add(partial_rec)
+ for aml in [partial_rec.debit_move_id, partial_rec.credit_move_id]:
+ if aml not in amls:
+ amls += aml
+ for x in aml.matched_debit_ids | aml.matched_credit_ids:
+ if x not in seen:
+ todo.add(x)
+ partial_rec_ids = [x.id for x in seen]
if not amls:
return
- else:
- amls = self.browse(list(amls))
-
# If we have multiple currency, we can only base ourselve on debit-credit to see if it is fully reconciled
currency = set([a.currency_id for a in amls if a.currency_id.id != False])
multiple_currency = False
@@ -998,7 +991,10 @@ class AccountMoveLine(models.Model):
debit_moves = debit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id))
credit_moves = credit_moves.sorted(key=lambda a: (a.date_maturity or a.date, a.currency_id))
# Compute on which field reconciliation should be based upon:
- field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual'
+ if self[0].account_id.currency_id and self[0].account_id.currency_id != self[0].account_id.company_id.currency_id:
+ field = 'amount_residual_currency'
+ else:
+ field = 'amount_residual'
#if all lines share the same currency, use amount_residual_currency to avoid currency rounding error
if self[0].currency_id and all([x.amount_currency and x.currency_id == self[0].currency_id for x in self]):
field = 'amount_residual_currency'
@@ -1712,6 +1708,7 @@ class AccountPartialReconcile(models.Model):
'move_id': newly_created_move.id,
'partner_id': line.partner_id.id,
'tax_repartition_line_id': line.tax_repartition_line_id.id,
+ 'tax_base_amount': line.tax_base_amount,
'tag_ids': [(6, 0, line.tag_ids.ids)],
})
if line.account_id.reconcile:
@@ -1737,6 +1734,7 @@ class AccountPartialReconcile(models.Model):
'amount_currency': self.amount_currency and line.currency_id.round(line.amount_currency * amount / line.balance) or 0.0,
'partner_id': line.partner_id.id,
'tax_repartition_line_id': line.tax_repartition_line_id.id,
+ 'tax_base_amount': line.tax_base_amount,
'tag_ids': [(6, 0, line.tag_ids.ids)],
})
self.env['account.move.line'].with_context(check_move_validity=False).create({
diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py
index 53b36274de1..6409ae13445 100644
--- a/addons/account/models/chart_template.py
+++ b/addons/account/models/chart_template.py
@@ -894,7 +894,9 @@ class AccountTaxTemplate(models.Model):
'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
}
"""
- ChartTemplate = self.env['account.chart.template']
+ # default_company_id is needed in context to allow creation of default
+ # repartition lines on taxes
+ ChartTemplate = self.env['account.chart.template'].with_context(default_company_id=company.id)
todo_dict = {'account.tax': {}, 'account.tax.repartition.line': {}}
tax_template_to_tax = {}
@@ -926,7 +928,7 @@ class AccountTaxTemplate(models.Model):
# We also have to delay the assignation of accounts to repartition lines
all_tax_rep_lines = tax.invoice_repartition_line_ids + tax.refund_repartition_line_ids
all_template_rep_lines = template.invoice_repartition_line_ids + template.refund_repartition_line_ids
- for i in range(0, len(all_tax_rep_lines)):
+ for i in range(0, len(all_template_rep_lines)):
# We assume template and tax repartition lines are in the same order
template_account = all_template_rep_lines[i].account_id
if template_account:
diff --git a/addons/account/models/product.py b/addons/account/models/product.py
index 655b457bbf0..f9b046c652a 100644
--- a/addons/account/models/product.py
+++ b/addons/account/models/product.py
@@ -81,4 +81,5 @@ class ProductProduct(models.Model):
'analytic_tag_ids': line.get('analytic_tag_ids', False),
'tax_repartition_line_id': line.get('tax_repartition_line_id'),
'tag_ids': line.get('tag_ids'),
+ 'tax_base_amount': line.get('tax_base_amount'),
}
diff --git a/addons/account/models/reconciliation_widget.py b/addons/account/models/reconciliation_widget.py
index 0d6577fe34c..f10bacfa3f3 100644
--- a/addons/account/models/reconciliation_widget.py
+++ b/addons/account/models/reconciliation_widget.py
@@ -83,28 +83,43 @@ class AccountReconciliation(models.AbstractModel):
@api.model
def _get_bank_statement_line_partners(self, st_lines):
+ params = []
+
+ # Add the res.partner.ban's IR rules. In case partners are not shared between companies,
+ # identical bank accounts may exist in a company we don't have access to.
+ ir_rules_query = self.env['res.partner.bank']._where_calc([])
+ self.env['res.partner.bank']._apply_ir_rules(ir_rules_query, 'read')
+ from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
+ if where_clause:
+ where_bank = ('AND %s' % where_clause).replace('res_partner_bank', 'bank')
+ params += where_clause_params
+ else:
+ where_bank = ''
+
+ # Add the res.partner's IR rules. In case partners are not shared between companies,
+ # identical partners may exist in a company we don't have access to.
+ ir_rules_query = self.env['res.partner']._where_calc([])
+ self.env['res.partner']._apply_ir_rules(ir_rules_query, 'read')
+ from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
+ if where_clause:
+ where_partner = ('AND %s' % where_clause).replace('res_partner', 'p3')
+ params += where_clause_params
+ else:
+ where_partner = ''
+
query = '''
SELECT
st_line.id AS id,
COALESCE(p1.id,p2.id,p3.id) AS partner_id
FROM account_bank_statement_line st_line
- LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number
- LEFT JOIN res_partner p1 ON st_line.partner_id=p1.id
- LEFT JOIN res_partner p2 ON bank.partner_id=p2.id
- LEFT JOIN res_partner p3 ON p3.name ILIKE st_line.partner_name
- WHERE st_line.id IN %s
'''
- params = [tuple(st_lines.ids)]
+ query += 'LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number %s\n' % (where_bank)
+ query += 'LEFT JOIN res_partner p1 ON st_line.partner_id=p1.id \n'
+ query += 'LEFT JOIN res_partner p2 ON bank.partner_id=p2.id \n'
+ query += 'LEFT JOIN res_partner p3 ON p3.name ILIKE st_line.partner_name %s\n' % (where_partner)
+ query += 'WHERE st_line.id IN %s'
- # Add the res.partner's IR rules to the WHERE clause. In case partners are not shared
- # between companies, identical partners may exist in a company we don't have access to.
- ir_rules_query = self.env['res.partner']._where_calc([])
- self.env['res.partner']._apply_ir_rules(ir_rules_query, 'read')
- from_clause, where_clause, where_clause_params = ir_rules_query.get_sql()
- where_p3 = (" AND %s" % where_clause).replace('res_partner', 'p3') if where_clause else ''
- if where_p3:
- query += where_p3
- params += where_clause_params
+ params += [tuple(st_lines.ids)]
self._cr.execute(query, params)
diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py
index 594a49d831e..b94bd711c63 100644
--- a/addons/account/report/account_invoice_report.py
+++ b/addons/account/report/account_invoice_report.py
@@ -112,13 +112,13 @@ class AccountInvoiceReport(models.Model):
1 AS nbr,
ai.id AS invoice_id, ai.type, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id AS account_line_id,
ai.partner_bank_id,
- SUM ((invoice_type.sign_qty * ail.quantity) / u.factor * u2.factor) AS product_qty,
+ SUM ((invoice_type.sign_qty * ail.quantity) / COALESCE(u.factor,1) * COALESCE(u2.factor,1)) AS product_qty,
SUM(ail.price_subtotal_signed * invoice_type.sign) AS price_total,
- (ai.amount_total * invoice_type.sign) / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
+ ai.amount_total_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
count(*) * invoice_type.sign AS amount_total,
SUM(ABS(ail.price_subtotal_signed)) / CASE
- WHEN SUM(ail.quantity / u.factor * u2.factor) <> 0::numeric
- THEN SUM(ail.quantity / u.factor * u2.factor)
+ WHEN SUM(ail.quantity / COALESCE(u.factor,1) * COALESCE(u2.factor,1)) <> 0::numeric
+ THEN SUM(ail.quantity / COALESCE(u.factor,1) * COALESCE(u2.factor,1))
ELSE 1::numeric
END AS price_average,
ai.residual_company_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js
index 67d5de9de08..5f27b0970f6 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_action.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js
@@ -346,9 +346,12 @@ var StatementAction = AbstractAction.extend({
'context': self.model.getContext(),
});
_.each(result.handles, function (handle) {
- self._getWidget(handle).destroy();
- var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;});
- self.widgets.splice(index, 1);
+ var widget = self._getWidget(handle);
+ if (widget) {
+ widget.destroy();
+ var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;});
+ self.widgets.splice(index, 1);
+ }
});
// Get number of widget and if less than constant and if there are more to laod, load until constant
if (self.widgets.length < self.model.defaultDisplayQty
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index c28790796d2..d4dce5581ca 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -623,13 +623,13 @@ var StatementModel = BasicModel.extend({
prop = this._formatQuickCreate(line);
line.reconciliation_proposition.push(prop);
}
- if (!line.reconciliation_proposition.slice(0,-1).every(function(prop) {return prop.to_check == values.to_check;})) {
+ if ('to_check' in values && !line.reconciliation_proposition.slice(0,-1).every(function(prop) {return prop.to_check == values.to_check;})) {
new CrashManager().show_warning({data: {
exception_type: _t("Incorrect Operation"),
message: _t("You cannot mix items with and without the 'To Check' checkbox ticked.")
}});
// FIXME: model should not be tied to the DOM !
- $('.create_to_check input').click();
+ $('.create_to_check input:visible').prop('checked', !values.to_check).change();
return Promise.resolve();
}
_.each(values, function (value, fieldName) {
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index b59b99bc881..53cd0b76598 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -938,10 +938,12 @@ var ManualLineRenderer = LineRenderer.extend({
* @override
*/
_renderCreate: function (state) {
- var parentPromise = this._super(state);
- this.$('.create .create_journal_id').show();
- this.$('.create .create_date').removeClass('d-none');
- this.$('.create .create_journal_id .o_input').addClass('o_required_modifier');
+ var self = this;
+ var parentPromise = this._super(state).then(function() {
+ self.$('.create .create_journal_id').show();
+ self.$('.create .create_date').removeClass('d-none');
+ self.$('.create .create_journal_id .o_input').addClass('o_required_modifier');
+ });
return parentPromise;
},
diff --git a/addons/account/tests/test_account_customer_invoice.py b/addons/account/tests/test_account_customer_invoice.py
index 5334151eebb..1a84c82cda2 100644
--- a/addons/account/tests/test_account_customer_invoice.py
+++ b/addons/account/tests/test_account_customer_invoice.py
@@ -306,30 +306,30 @@ class TestAccountCustomerInvoice(AccountTestUsers):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 2)
- self.assertEquals(dashboard_data['sum_draft'], '$ 68.42')
+ self.assertIn('68.42', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 0)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 0.00')
+ self.assertIn('0.00', dashboard_data['sum_waiting'])
# Check Both
invoice.action_invoice_open()
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 1)
- self.assertEquals(dashboard_data['sum_draft'], '$ -13.30')
+ self.assertIn('-13.30', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 1)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 81.72')
+ self.assertIn('81.72', dashboard_data['sum_waiting'])
# Check waiting payment
refund.action_invoice_open()
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 0)
- self.assertEquals(dashboard_data['sum_draft'], '$ 0.00')
+ self.assertIn('0.00', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 2)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 68.42')
+ self.assertIn('68.42', dashboard_data['sum_waiting'])
# Check partial
receivable_account = refund.move_id.line_ids.mapped('account_id').filtered(lambda a: a.internal_type == 'receivable')
@@ -353,12 +353,12 @@ class TestAccountCustomerInvoice(AccountTestUsers):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_draft'], 0)
- self.assertEquals(dashboard_data['sum_draft'], '$ 0.00')
+ self.assertIn('0.00', dashboard_data['sum_draft'])
self.assertEquals(dashboard_data['number_waiting'], 2)
- self.assertEquals(dashboard_data['sum_waiting'], '$ 78.42')
+ self.assertIn('78.42', dashboard_data['sum_waiting'])
with patch('odoo.fields.Date.today', patched_today):
dashboard_data = journal.get_journal_dashboard_datas()
self.assertEquals(dashboard_data['number_late'], 2)
- self.assertEquals(dashboard_data['sum_late'], '$ 78.42')
+ self.assertIn('78.42', dashboard_data['sum_late'])
diff --git a/addons/account/tests/test_invoice_taxes.py b/addons/account/tests/test_invoice_taxes.py
index 4b0d8380f72..b6759271579 100644
--- a/addons/account/tests/test_invoice_taxes.py
+++ b/addons/account/tests/test_invoice_taxes.py
@@ -100,8 +100,8 @@ class TestInvoiceTaxes(AccountingTestCase):
Expected:
Tax | Taxes | Base | Amount
--------------------------------------------
- 21% incl | / | 100 | 21
- 12% | 21% incl | 121 | 14.52
+ 21% incl | 12% | 100 | 21
+ 12% | / | 121 | 14.52
12% | / | 100 | 12
'''
invoice = self._create_invoice([
@@ -110,9 +110,8 @@ class TestInvoiceTaxes(AccountingTestCase):
])
invoice.action_invoice_open()
self.assertRecordValues(invoice.tax_line_ids.sorted(lambda x: x.amount), [
- {'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
- {'name': self.percent_tax_2.name, 'base': 121, 'amount': 14.52, 'tax_ids': [self.percent_tax_1_incl.id]},
- {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
+ {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': [self.percent_tax_2.id]},
+ {'name': self.percent_tax_2.name, 'base': 221, 'amount': 26.52, 'tax_ids': []},
])
def test_group_of_taxes(self):
@@ -135,9 +134,8 @@ class TestInvoiceTaxes(AccountingTestCase):
])
invoice.action_invoice_open()
self.assertRecordValues(invoice.tax_line_ids.sorted(lambda x: x.amount), [
- {'name': self.percent_tax_2.name, 'base': 100, 'amount': 12, 'tax_ids': []},
- {'name': self.percent_tax_2.name, 'base': 121, 'amount': 14.52, 'tax_ids': [self.percent_tax_1_incl.id]},
- {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': []},
+ {'name': self.percent_tax_1_incl.name, 'base': 100, 'amount': 21, 'tax_ids': [self.percent_tax_2.id]},
+ {'name': self.percent_tax_2.name, 'base': 221, 'amount': 26.52, 'tax_ids': []},
])
def _create_tax_tag(self, tag_name):
diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py
index 62d414fdf54..0a35310f8a7 100644
--- a/addons/account/tests/test_reconciliation.py
+++ b/addons/account/tests/test_reconciliation.py
@@ -91,6 +91,11 @@ class TestReconciliation(AccountingTestCase):
'code': 'CABA',
'type': 'general',
})
+ self.general_journal = self.env['account.journal'].create({
+ 'name': 'general',
+ 'code': 'GENE',
+ 'type': 'general',
+ })
# Tax Cash Basis
self.tax_cash_basis = self.env['account.tax'].create({
@@ -1304,6 +1309,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 16.67,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 83.33,
})
purchase_move.post()
@@ -1422,6 +1428,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 5,
'move_id': purchase_move.id,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 50,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
@@ -1436,6 +1443,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 16.67,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 83.33,
})
purchase_move.post()
@@ -1646,6 +1654,7 @@ class TestReconciliationExec(TestReconciliation):
'currency_id': self.currency_usd_id,
'amount_currency': 848.16,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 106841.65,
})
purchase_payable_line0 = aml_obj.create({
'name': 'Payable',
@@ -1816,6 +1825,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 17094.66,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 106841.65,
'currency_id': self.currency_usd_id,
'amount_currency': 848.16,
})
@@ -1975,6 +1985,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 5,
'move_id': purchase_move.id,
'tax_repartition_line_id': tax_cash_basis10percent.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 50,
})
AccountMoveLine.create({
'name': 'expenseTaxed 20%',
@@ -1989,6 +2000,7 @@ class TestReconciliationExec(TestReconciliation):
'debit': 20,
'move_id': purchase_move.id,
'tax_repartition_line_id': self.tax_cash_basis.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax').id,
+ 'tax_base_amount': 100,
})
purchase_move.post()
@@ -2056,6 +2068,71 @@ class TestReconciliationExec(TestReconciliation):
self.assertTrue(pay_receivable_line1.reconciled)
self.assertEqual(pay_receivable_line1.matched_debit_ids, move_caba1.tax_cash_basis_rec_id)
+ def test_reconciliation_with_currency(self):
+ #reconciliation on an account having a foreign currency being
+ #the same as the company one
+ account_rcv = self.account_rcv
+ account_rcv.currency_id = self.currency_euro_id
+ aml_obj = self.env['account.move.line'].with_context(
+ check_move_validity=False)
+ general_move1 = self.env['account.move'].create({
+ 'name': 'general1',
+ 'journal_id': self.general_journal.id,
+ })
+ aml_obj.create({
+ 'name': 'debit1',
+ 'account_id': account_rcv.id,
+ 'debit': 11,
+ 'move_id': general_move1.id,
+ })
+ aml_obj.create({
+ 'name': 'credit1',
+ 'account_id': self.account_rsa.id,
+ 'credit': 11,
+ 'move_id': general_move1.id,
+ })
+ general_move1.post()
+ general_move2 = self.env['account.move'].create({
+ 'name': 'general2',
+ 'journal_id': self.general_journal.id,
+ })
+ aml_obj.create({
+ 'name': 'credit2',
+ 'account_id': account_rcv.id,
+ 'credit': 10,
+ 'move_id': general_move2.id,
+ })
+ aml_obj.create({
+ 'name': 'debit2',
+ 'account_id': self.account_rsa.id,
+ 'debit': 10,
+ 'move_id': general_move2.id,
+ })
+ general_move2.post()
+ general_move3 = self.env['account.move'].create({
+ 'name': 'general3',
+ 'journal_id': self.general_journal.id,
+ })
+ aml_obj.create({
+ 'name': 'credit3',
+ 'account_id': account_rcv.id,
+ 'credit': 1,
+ 'move_id': general_move3.id,
+ })
+ aml_obj.create({
+ 'name': 'debit3',
+ 'account_id': self.account_rsa.id,
+ 'debit': 1,
+ 'move_id': general_move3.id,
+ })
+ general_move3.post()
+ to_reconcile = ((general_move1 + general_move2 + general_move3)
+ .mapped('line_ids')
+ .filtered(lambda l: l.account_id.id == account_rcv.id))
+ to_reconcile.reconcile()
+ for aml in to_reconcile:
+ self.assertEqual(aml.amount_residual, 0.0)
+
def test_reconciliation_process_move_lines_with_mixed_currencies(self):
# Delete any old rate - to make sure that we use the ones we need.
old_rates = self.env['res.currency.rate'].search(
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index b965b67f488..a55aafc795e 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -384,6 +384,7 @@
+
@@ -655,6 +656,7 @@
+
diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml
index 0eadb5cdcf8..aa2e1a35123 100644
--- a/addons/account/views/account_move_views.xml
+++ b/addons/account/views/account_move_views.xml
@@ -357,6 +357,7 @@
+
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 772a29affff..a3e175bdf20 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -99,7 +99,8 @@
-
+
+
-
+
diff --git a/addons/hr_attendance_presence/views/hr_employee.xml b/addons/hr_attendance_presence/views/hr_employee.xml
index c49871522a2..a645e594b20 100644
--- a/addons/hr_attendance_presence/views/hr_employee.xml
+++ b/addons/hr_attendance_presence/views/hr_employee.xml
@@ -4,7 +4,7 @@
hr.employeehr.employee
-
+ 20
diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py
index 366624ab8f4..f8662c1f55f 100644
--- a/addons/hr_contract/models/hr_contract.py
+++ b/addons/hr_contract/models/hr_contract.py
@@ -60,12 +60,12 @@ class Contract(models.Model):
@api.constrains('employee_id', 'state', 'date_start', 'date_end')
def _check_current_contract(self):
- """ Two contracts in state [incoming | pending | open | close] cannot overlap """
- for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel']):
+ """ Two contracts in state [incoming | pending | open] cannot overlap """
+ for contract in self.filtered(lambda c: c.state not in ['draft', 'cancel', 'close']):
domain = [
('id', '!=', contract.id),
('employee_id', '=', contract.employee_id.id),
- ('state', 'in', ['incoming', 'pending', 'open', 'close']),
+ ('state', 'in', ['incoming', 'pending', 'open']),
]
if not contract.date_end:
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index 5ece94762a4..5c865fc053e 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -151,14 +151,14 @@ class HrExpense(models.Model):
@api.model
def get_empty_list_help(self, help_message):
- if help_message and "oe_view_nocontent_smiling_face" not in help_message:
+ if help_message and "o_view_nocontent_smiling_face" not in help_message:
use_mailgateway = self.env['ir.config_parameter'].sudo().get_param('hr_expense.use_mailgateway')
alias_record = use_mailgateway and self.env.ref('hr_expense.mail_alias_expense') or False
if alias_record and alias_record.alias_domain and alias_record.alias_name:
link = "%(email)s" % {
'email': '%s@%s' % (alias_record.alias_name, alias_record.alias_domain)
}
- return '