summaryrefslogtreecommitdiff
path: root/addons/account
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-06-18 10:24:25 +0200
committerChristophe Simonis <chs@odoo.com>2019-06-18 10:24:25 +0200
commit9035fb8a2a4ddfae2951ed1f540060896924bb44 (patch)
tree024ce185effd5b1cb59463d2a187a9fa4db35472 /addons/account
parent0fc33c37c3cea3cadf69c2352f49c88b295bd63f (diff)
parent618ea1ab675d6454280deac7d6f853b2556e5be1 (diff)
[MERGE] forward port branch saas-12.4 up to 618ea1ab675
Diffstat (limited to 'addons/account')
-rw-r--r--addons/account/models/account.py15
-rw-r--r--addons/account/models/account_bank_statement.py124
-rw-r--r--addons/account/models/account_invoice.py119
-rw-r--r--addons/account/models/account_move.py46
-rw-r--r--addons/account/report/account_invoice_report.py5
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js50
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_renderer.js10
-rw-r--r--addons/account/static/tests/reconciliation_tests.js20
-rw-r--r--addons/account/tests/test_bank_statement_reconciliation.py30
-rw-r--r--addons/account/tests/test_invoice_taxes.py20
-rw-r--r--addons/account/tests/test_payment.py5
-rw-r--r--addons/account/views/account_analytic_view.xml2
-rw-r--r--addons/account/views/account_invoice_view.xml11
-rw-r--r--addons/account/views/account_menuitem.xml7
-rw-r--r--addons/account/views/account_move_views.xml232
-rw-r--r--addons/account/wizard/account_invoice_send.py17
-rw-r--r--addons/account/wizard/account_invoice_send_views.xml3
17 files changed, 506 insertions, 210 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 6be56cd4795..326e1574014 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -1097,10 +1097,17 @@ class AccountTax(models.Model):
for record in self:
record._check_repartition_lines(record.invoice_repartition_line_ids)
record._check_repartition_lines(record.refund_repartition_line_ids)
- sum_inv_tax = sum(record.mapped('invoice_repartition_line_ids.factor_percent'))
- sum_refund_tax = sum(record.mapped('refund_repartition_line_ids.factor_percent'))
- if float_compare(sum_inv_tax, sum_refund_tax, precision_digits=4) != 0:
- raise ValidationError(_("You should impact the same total percentage of the tax amount for invoices and refunds."))
+
+ if len(record.invoice_repartition_line_ids) != len(record.refund_repartition_line_ids):
+ raise ValidationError(_("Invoice and credit note repartition should have the same number of lines."))
+
+ index = 0
+ while index < len(record.invoice_repartition_line_ids):
+ inv_rep_ln = record.invoice_repartition_line_ids[index]
+ ref_rep_ln = record.refund_repartition_line_ids[index]
+ if inv_rep_ln.repartition_type != ref_rep_ln.repartition_type or inv_rep_ln.factor_percent != ref_rep_ln.factor_percent:
+ raise ValidationError(_("Invoice and credit note repartitions should match (same percentages, in the same order)."))
+ index += 1
@api.one
@api.constrains('children_tax_ids', 'type_tax_use')
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 5b49747f9cb..b6ab3a50a90 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -444,6 +444,7 @@ 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
@@ -451,19 +452,23 @@ 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 = -sum([x.amount_currency for x in move.line_ids])
+ amount_currency = -amount_sum
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])
+ amount_currency = -amount_sum
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
+ amount_currency = -amount_sum/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
@@ -472,9 +477,8 @@ 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
- return {
+ aml_dict = {
'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 \
@@ -485,22 +489,72 @@ 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.journal_entry_ids.ids:
- vals = {
+ 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 = {
'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.process_reconciliation(new_aml_dicts=[vals])
+ 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, -aml_dict['debit'] if st_line.amount < 0 else aml_dict['credit'])
+ 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})
def _get_communication(self, payment_method_id):
return self.name or ''
@@ -628,8 +682,6 @@ 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
@@ -652,32 +704,12 @@ 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
- 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
+ self._prepare_move_line_for_currency(aml_dict, date)
# Create write-offs
for aml_dict in new_aml_dicts:
@@ -727,6 +759,36 @@ 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 e1d4fd6b35e..11a15b4e054 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -93,11 +93,12 @@ class AccountInvoice(models.Model):
('type', 'in', [TYPE2JOURNAL[ty] for ty in inv_types if ty in TYPE2JOURNAL]),
('company_id', '=', company_id),
]
- journal_with_currency = False
- if self._context.get('default_currency_id'):
- currency_clause = [('currency_id', '=', self._context.get('default_currency_id'))]
- journal_with_currency = self.env['account.journal'].search(domain + currency_clause, limit=1)
- return journal_with_currency or self.env['account.journal'].search(domain, limit=1)
+ company_currency_id = self.env['res.company'].browse(company_id).currency_id.id
+ currency_id = self._context.get('default_currency_id') or company_currency_id
+ currency_clause = [('currency_id', '=', currency_id)]
+ if currency_id == company_currency_id:
+ currency_clause = ['|', ('currency_id', '=', False)] + currency_clause
+ return self.env['account.journal'].search(domain + currency_clause, limit=1)
@api.model
def _default_currency(self):
@@ -1555,80 +1556,22 @@ class AccountInvoice(models.Model):
This function is used in order to fix account.invoice.tax objects generated
for refunds when tax amounts have been modified manually.
- For example, a tax with the following tax repartition...
- - INVOICE:
- 1) 30%
- 2) 70%
- - REFUND
- 3) 40%
- 4) 60%
-
- ... will group line 1) with line 3), and line 2) with lines 3) and 4).
-
- :return: A dictionnary, with invoice repartition line ids as keys, and lists
- of corresponding refund repartition line ids as values.
+ :return: A dictionnary, with invoice repartition line ids as keys, and refund
+ repartition lines as values
"""
rslt = {}
for tax in taxes:
- inv_index = 0
- ref_index = 0
-
- tax_inv_lines = tax.invoice_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')
- tax_ref_lines = tax.refund_repartition_line_ids.filtered(lambda x: x.repartition_type == 'tax')
-
- amount_to_match = tax_inv_lines[inv_index].factor_percent
- while inv_index < len(tax_inv_lines) or ref_index < len(tax_ref_lines):
- inv_rep_line = inv_index < len(tax_inv_lines) and tax_inv_lines[inv_index] or tax_inv_lines[-1]
- ref_rep_line = ref_index < len(tax_ref_lines) and tax_ref_lines[ref_index] or tax_ref_lines[-1]
-
- rslt_list = rslt.get(inv_rep_line.id, [])
- if ref_rep_line.id not in rslt_list:
- rslt_list.append(ref_rep_line.id)
- rslt[inv_rep_line.id] = rslt_list
-
- if amount_to_match > 0:
- amount_to_match -= ref_rep_line.factor_percent
- ref_index += 1
- elif amount_to_match < 0:
- amount_to_match += inv_rep_line.factor_percent
- inv_index +=1
- else:
- inv_index += 1
- if inv_index < len(tax_inv_lines):
- amount_to_match = tax_inv_lines[inv_index].factor_percent
+ index = 0
+ while(index < len(tax.invoice_repartition_line_ids)):
+ # _validate_repartition_lines constraint on taxes ensure invoice and refund repartition are equal, and in the same order
+ inv_rep_ln = tax.invoice_repartition_line_ids[index]
+ ref_rep_ln = tax.refund_repartition_line_ids[index]
+ if inv_rep_ln.repartition_type == 'tax':
+ rslt[inv_rep_ln.id] = ref_rep_ln
+ index += 1
return rslt
- def _group_tax_lines_by_repartition(self):
- self.ensure_one()
- rslt = {}
- for tax_line in self.tax_line_ids:
- rslt[tax_line.tax_repartition_line_id.id] = tax_line
- return rslt
-
- @api.model
- def _fix_refund_tax_lines(self, invoice, refund):
- """ Modifies the tax_line_ids of a draft refund invoice in order to make it
- match the manual tax modifications that were made on its original invoice.
- """
- invoice_tax_lines_map = invoice._group_tax_lines_by_repartition()
- refund_tax_lines_map = refund._group_tax_lines_by_repartition()
- refund_repartition_map = self._create_refund_repartition_mapping(invoice.mapped('invoice_line_ids.invoice_line_tax_ids'))
- computed_tax_values = invoice.get_taxes_values()
-
- for tax_data in computed_tax_values.values():
- rep_line_id = tax_data['tax_repartition_line_id']
-
- matching_invoice_tax = invoice_tax_lines_map.get(rep_line_id)
-
- if matching_invoice_tax:
- ref_rep_line_id = refund_repartition_map[rep_line_id][0]
- refund_tax_line = refund_tax_lines_map.get(ref_rep_line_id)
- manual_difference = matching_invoice_tax.amount - tax_data['amount']
-
- if not invoice.currency_id.is_zero(manual_difference):
- refund_tax_line.amount += manual_difference
-
@api.model
def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None):
""" Prepare the dict of values to create the new credit note from the invoice.
@@ -1677,6 +1620,35 @@ class AccountInvoice(models.Model):
values['date'] = date
if description:
values['name'] = description
+
+ # Treat refund tax lines.
+ # We copy them from the invoice and replace their account_id and
+ # tag_ids based on the refund repartition of the corresponding taxes.
+ tax_rep_ln_mapping = {}
+ for invoice_tax_entry in invoice.tax_line_ids:
+ if not invoice_tax_entry.tax_repartition_line_id.id in tax_rep_ln_mapping:
+ tax_rep_ln_mapping[invoice_tax_entry.tax_id.id] = self._create_refund_repartition_mapping(invoice_tax_entry.tax_id)
+
+ tax_line_vals = []
+ for invoice_tax_entry in invoice.tax_line_ids:
+ ref_rep_ln = tax_rep_ln_mapping[invoice_tax_entry.tax_id.id][invoice_tax_entry.tax_repartition_line_id.id]
+ tax_line_vals.append({
+ 'name': invoice_tax_entry.name,
+ 'tax_id': invoice_tax_entry.tax_id.id,
+ 'tax_repartition_line_id': ref_rep_ln.id,
+ 'account_id': ref_rep_ln.account_id.id or invoice_tax_entry.account_id.id, # If the refund repartition line has no account set, we use the one from the original invoice
+ 'account_analytic_id': invoice_tax_entry.account_analytic_id.id,
+ 'analytic_tag_ids': [(6, 0, invoice_tax_entry.analytic_tag_ids.ids)],
+ 'amount': invoice_tax_entry.amount,
+ 'amount_rounding': invoice_tax_entry.amount_rounding,
+ 'manual': invoice_tax_entry.manual,
+ 'sequence': invoice_tax_entry.sequence,
+ 'base': invoice_tax_entry.base,
+ 'tax_ids': [(6, 0, invoice_tax_entry.tax_ids.ids)],
+ 'tag_ids': [(6, 0, ref_rep_ln.tag_ids.ids)],
+ })
+ values['tax_line_ids'] = [(0, 0, tax_line_val) for tax_line_val in tax_line_vals]
+
return values
@api.multi
@@ -1692,7 +1664,6 @@ class AccountInvoice(models.Model):
message = _("This customer invoice credit note has been created from: <a href=# data-oe-model=account.invoice data-oe-id=%d>%s</a><br>Reason: %s") % (invoice.id, invoice.number, description)
else:
message = _("This vendor bill credit note has been created from: <a href=# data-oe-model=account.invoice data-oe-id=%d>%s</a><br>Reason: %s") % (invoice.id, invoice.number, description)
- self._fix_refund_tax_lines(invoice, refund_invoice)
refund_invoice.message_post(body=message)
new_invoices += refund_invoice
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index fef8868f80f..6b9469f7a18 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -19,7 +19,7 @@ from lxml import etree
class AccountMove(models.Model):
_name = "account.move"
_description = "Journal Entries"
- _order = 'date desc, id desc'
+ _order = 'date desc, name desc, id desc'
_inherit = ['mail.thread', 'mail.activity.mixin']
@api.model
@@ -34,10 +34,11 @@ class AccountMove(models.Model):
def name_get(self):
result = []
for move in self:
+ name = move.name
+ if self.env.context.get('name_groupby'):
+ name = "**{date}**, {name} {partner}".format(date=format_date(self.env, move.date), name=move.name, partner=move.partner_id.name or "")
if move.state == 'draft':
name = '* ' + str(move.id)
- else:
- name = move.name
result.append((move.id, name))
return result
@@ -87,12 +88,14 @@ 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
@@ -433,6 +436,7 @@ class AccountMove(models.Model):
@api.multi
def _reverse_move(self, date=None, journal_id=None):
self.ensure_one()
+ date = date or fields.Date.today()
with self.env.norecompute():
reversed_move = self.copy(default={
'date': date,
@@ -510,7 +514,7 @@ class AccountMove(models.Model):
class AccountMoveLine(models.Model):
_name = "account.move.line"
_description = "Journal Item"
- _order = "date desc, id desc"
+ _order = "date desc, move_name desc, id desc"
@api.onchange('debit', 'credit', 'tax_ids', 'analytic_account_id', 'analytic_tag_ids')
def onchange_tax_ids_create_aml(self):
@@ -614,6 +618,7 @@ class AccountMoveLine(models.Model):
record.parent_state = record.move_id.state
name = fields.Char(string="Label")
+ move_name = fields.Char(string='Number', related='move_id.name', store=True, index=True)
quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'),
help="The optional quantity expressed by this line, eg: number of product sold. The quantity is not a legal requirement but is very useful for some reports.")
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
@@ -677,8 +682,8 @@ class AccountMoveLine(models.Model):
tax_audit = fields.Char(string="Tax Audit String", compute="_compute_tax_audit", store=True, help="Computed field, listing the tax grids impacted by this line, and the amount it applies to each of them.")
_sql_constraints = [
- ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
- ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
+ ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry! Credit or debit should be zero.'),
+ ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry! Credit and debit should be positive.'),
]
@api.model
@@ -813,22 +818,21 @@ 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
- 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)
+ 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)
seen = set()
while todo:
- 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]
+ 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)
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
@@ -1532,7 +1536,7 @@ class AccountMoveLine(models.Model):
@api.multi
def open_reconcile_view(self):
- [action] = self.env.ref('account.action_account_moves_all_a').read()
+ [action] = self.env.ref('account.action_account_moves_all').read()
ids = []
for aml in self:
if aml.account_id.reconcile:
diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py
index b94bd711c63..2cebf29d6d5 100644
--- a/addons/account/report/account_invoice_report.py
+++ b/addons/account/report/account_invoice_report.py
@@ -96,7 +96,7 @@ class AccountInvoiceReport(models.Model):
sub.payment_term_id, sub.uom_name, sub.currency_id, sub.journal_id,
sub.fiscal_position_id, sub.user_id, sub.company_id, sub.nbr, sub.invoice_id, sub.type, sub.state,
sub.categ_id, sub.date_due, sub.account_id, sub.account_line_id, sub.partner_bank_id,
- sub.product_qty, sub.price_total as price_total, sub.price_average as price_average, sub.amount_total as amount_total,
+ sub.product_qty, sub.price_total as price_total, sub.price_average as price_average, sub.amount_total / COALESCE(cr.rate, 1) as amount_total,
COALESCE(cr.rate, 1) as currency_rate, sub.residual as residual, sub.commercial_partner_id as commercial_partner_id
"""
return select_str
@@ -114,8 +114,7 @@ class AccountInvoiceReport(models.Model):
ai.partner_bank_id,
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_signed / (SELECT count(*) FROM account_invoice_line l where invoice_id = ai.id) *
- count(*) * invoice_type.sign AS amount_total,
+ SUM(ail.price_total * invoice_type.sign_qty) AS amount_total,
SUM(ABS(ail.price_subtotal_signed)) / CASE
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))
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index d4dce5581ca..2846e2d2e2f 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -281,7 +281,6 @@ var StatementModel = BasicModel.extend({
if (last && !this._isValid(last)) {
return Promise.resolve(false);
}
-
prop = this._formatQuickCreate(line);
line.reconciliation_proposition.push(prop);
line.createForm = _.pick(prop, this.quickCreateFields);
@@ -493,13 +492,6 @@ var StatementModel = BasicModel.extend({
this._blurProposition(handle);
var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields));
focus.reconcileModelId = reconcileModelId;
- if (!line.reconciliation_proposition.every(function(prop) {return prop.to_check == focus.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.")
- }});
- return Promise.resolve();
- }
line.reconciliation_proposition.push(focus);
if (reconcileModel.has_second_line) {
var second = {};
@@ -619,19 +611,16 @@ var StatementModel = BasicModel.extend({
var self = this;
var line = this.getLine(handle);
var prop = _.last(_.filter(line.reconciliation_proposition, '__focus'));
+ if ('to_check' in values && values.to_check === false) {
+ // check if we have another line with to_check and if yes don't change value of this proposition
+ prop.to_check = line.reconciliation_proposition.some(function(rec_prop, index) {
+ return rec_prop.id !== prop.id && rec_prop.to_check;
+ });
+ }
if (!prop) {
prop = this._formatQuickCreate(line);
line.reconciliation_proposition.push(prop);
}
- 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:visible').prop('checked', !values.to_check).change();
- return Promise.resolve();
- }
_.each(values, function (value, fieldName) {
if (fieldName === 'analytic_tag_ids') {
switch (value.operation) {
@@ -683,7 +672,6 @@ var StatementModel = BasicModel.extend({
prop.__tax_to_recompute = true;
}
line.createForm = _.pick(prop, this.quickCreateFields);
-
// If you check/uncheck the force_tax_included box, reset the createForm amount.
if(prop.base_amount)
line.createForm.amount = prop.base_amount;
@@ -744,16 +732,8 @@ var StatementModel = BasicModel.extend({
"new_aml_dicts": _.map(_.filter(props, function (prop) {
return isNaN(prop.id) && prop.display;
}), self._formatToProcessReconciliation.bind(self, line)),
+ "to_check": line.to_check,
};
- line.reconciliation_proposition.some(function(prop) {
- if (prop.to_check) {
- values_dict['to_check'] = true;
- return true;
- }
- });
- if (line.reconciliation_proposition[0].to_check) {
- values_dict['to_check'] = true;
- }
// If the lines are not fully balanced, create an unreconciled amount.
// line.st_line.currency_id is never false here because its equivalent to
@@ -870,7 +850,12 @@ var StatementModel = BasicModel.extend({
var formatOptions = {
currency_id: line.st_line.currency_id,
};
+ line.to_check = false;
_.each(line.reconciliation_proposition, function (prop) {
+ if (prop.to_check) {
+ // If one of the proposition is to_check, set the global to_check flag to true
+ line.to_check = true;
+ }
if (prop.is_tax) {
if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) {
reconciliation_proposition.push(prop);
@@ -1025,6 +1010,7 @@ var StatementModel = BasicModel.extend({
prop.label = prop.name;
prop.account_id = self._formatNameGet(prop.account_id || line.account_id);
prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str;
+ prop.to_check = !!prop.to_check;
});
}
},
@@ -1160,7 +1146,7 @@ var StatementModel = BasicModel.extend({
'link': values.link,
'display': true,
'invalid': true,
- 'to_check': values.to_check,
+ 'to_check': !!values.to_check,
'__tax_to_recompute': true,
'is_tax': values.is_tax,
'__focus': '__focus' in values ? values.__focus : true,
@@ -1406,11 +1392,8 @@ var ManualModel = StatementModel.extend({
return self.loadData(lines);
});
default:
- var partner_ids = context.partner_ids;
- var account_ids = context.account_ids || self.account_ids;
- if (partner_ids && !account_ids) account_ids = [];
- if (!partner_ids && account_ids) partner_ids = [];
- account_ids = null; // TOFIX: REMOVE ME
+ var partner_ids = context.partner_ids || null;
+ var account_ids = context.account_ids || self.account_ids || null;
return self._rpc({
model: 'account.reconciliation.widget',
method: 'get_all_data_for_manual_reconciliation',
@@ -1627,6 +1610,7 @@ var ManualModel = StatementModel.extend({
prop.debit = prop.debit !== 0 ? 0 : tmp_value;
prop.amount = -prop.amount;
prop.journal_id = self._formatNameGet(prop.journal_id || line.journal_id);
+ prop.to_check = !!prop.to_check;
});
}
},
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index e982f9e67a1..8693b2c9d30 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -344,7 +344,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
update: function (state) {
var self = this;
// isValid
- var to_check_checked = !!(state.reconciliation_proposition[0] && state.reconciliation_proposition[0].to_check);
+ var to_check_checked = !!(state.to_check);
this.$('caption .o_buttons button.o_validate').toggleClass('d-none', !!state.balance.type && !to_check_checked);
this.$('caption .o_buttons button.o_reconcile').toggleClass('d-none', state.balance.type <= 0 || to_check_checked);
this.$('caption .o_buttons .o_no_valid').toggleClass('d-none', state.balance.type >= 0);
@@ -474,9 +474,13 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
}
}
});
- return true;
+ if (state.to_check) {
+ // Set the to_check field to true if global to_check is set
+ self.$('.create_to_check input').prop('checked', state.to_check).change();
+ }
+ return true;
});
- });
+ });
}
this.$('.create .add_line').toggle(!!state.balance.amount_currency);
},
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js
index d46c0d261c1..fb46b72719e 100644
--- a/addons/account/static/tests/reconciliation_tests.js
+++ b/addons/account/static/tests/reconciliation_tests.js
@@ -477,7 +477,7 @@ var auto_reconciliation = {
};
var data_for_manual_reconciliation_widget = {
- '[[],null]': {
+ '[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]': {
'customers': [
{'account_id': 287, 'partner_name': "Agrolait", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-14 12:30:31", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'mode': "customers"},
{'account_id': 7, 'partner_name': "Camptocamp", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-13 14:24:55", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'mode': "customers"}
@@ -744,7 +744,7 @@ QUnit.module('account', {
"name": "INV/2017/0003",
"analytic_tag_ids": [[6, null, []]]
}],
- payment_aml_ids: [], new_aml_dicts: []}]],
+ payment_aml_ids: [], new_aml_dicts: [], to_check: false}]],
"Should call process_bank_statement_line with ids");
var def = testUtils.makeTestPromise();
def.abort = function () {};
@@ -782,8 +782,10 @@ QUnit.module('account', {
// line is send back to server.
testUtils.mock.intercept(clientAction, 'call_service', function (event) {
assert.deepEqual(event.data.args[1].args,
- [[5],[{partner_id: 8, counterpart_aml_dicts: [],
- payment_aml_ids: [], new_aml_dicts: [{
+ [[5],[{partner_id: 8, to_check: false, counterpart_aml_dicts: [],
+ payment_aml_ids: [],
+ to_check: false,
+ new_aml_dicts: [{
account_id: 287,
credit: 1175,
debit: 0,
@@ -834,7 +836,7 @@ QUnit.module('account', {
// We also create a line which is the open balance.
testUtils.mock.intercept(clientAction, 'call_service', function (event) {
assert.deepEqual(event.data.args[1].args,
- [[5],[{partner_id: 8,
+ [[5],[{partner_id: 8, to_check: false,
counterpart_aml_dicts: [{
counterpart_aml_id: 109,
credit: 650,
@@ -884,6 +886,7 @@ QUnit.module('account', {
}],
payment_aml_ids: [],
new_aml_dicts: [],
+ to_check: false,
}]
], "should call process_bank_statement_line with partial reconcile values");
}
@@ -1408,6 +1411,7 @@ QUnit.module('account', {
payment_aml_ids: [],
partner_id: 8,
+ to_check: false,
new_aml_dicts: []}]
], "should call process_bank_statement_line with partial reconcile values");
}
@@ -1500,6 +1504,7 @@ QUnit.module('account', {
payment_aml_ids: [],
partner_id: 8,
+ to_check: false,
new_aml_dicts: [
{account_id: 282,
credit: 0,
@@ -1609,6 +1614,7 @@ QUnit.module('account', {
payment_aml_ids: [],
partner_id: 8,
+ to_check: false,
new_aml_dicts: []
}]
], "should call process_bank_statement_line with correct counterpart_aml_dicts");
@@ -1662,7 +1668,7 @@ QUnit.module('account', {
assert.expect(5);
// tweak the data to fit our needs
- this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'] = _.extend({}, this.params.data_for_manual_reconciliation_widget['[[],null]']);
+ this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'] = _.extend({}, this.params.data_for_manual_reconciliation_widget['[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]']);
this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'].accounts[0].reconciliation_proposition = [
{account_id: 283, account_type: "other", amount_currency_str: "", currency_id: false, date_maturity: "2017-03-18", date: "2017-02-16",
total_amount_str: "$ 500.00", partner_id: 8, account_name: "101000 Current Assets", name: "INV/2017/0987", partner_name: "Agrolait",
@@ -1772,7 +1778,7 @@ QUnit.module('account', {
QUnit.test('Tax on account receivable', async function(assert){
assert.expect(21);
- this.params.data_for_manual_reconciliation_widget['[[],null]'].accounts = [];
+ this.params.data_for_manual_reconciliation_widget['[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]'].accounts = [];
var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options);
testUtils.mock.addMockEnvironment(clientAction, {
data: this.params.data,
diff --git a/addons/account/tests/test_bank_statement_reconciliation.py b/addons/account/tests/test_bank_statement_reconciliation.py
index 0e0e2ceadfa..a0a5fe2e65b 100644
--- a/addons/account/tests/test_bank_statement_reconciliation.py
+++ b/addons/account/tests/test_bank_statement_reconciliation.py
@@ -13,6 +13,8 @@ class TestBankStatementReconciliation(AccountingTestCase):
self.bsl_model = self.env['account.bank.statement.line']
self.reconciliation_widget = self.env['account.reconciliation.widget']
self.partner = self.env['res.partner'].create({'name': 'test'})
+ self.currency_usd_id = self.env.ref("base.USD").id
+ self.currency_euro_id = self.env.ref("base.EUR").id
def test_reconciliation_proposition(self):
rcv_mv_line = self.create_invoice(100)
@@ -108,3 +110,31 @@ class TestBankStatementReconciliation(AccountingTestCase):
})
return bank_stmt_line
+
+ def test_confirm_statement_usd(self):
+ company = self.env.ref('base.main_company')
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.currency_euro_id, company.id])
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'currency_id': self.currency_usd_id,
+ 'rate': 2.0,
+ 'name': '2001-01-01',
+ })
+ bank_journal_usd = self.env['account.journal'].create({
+ 'name': 'Bank US',
+ 'type': 'bank',
+ 'code': 'BNK68',
+ 'currency_id': self.currency_usd_id,
+ })
+ statement = self.bs_model.create({
+ 'journal_id': bank_journal_usd.id,
+ 'balance_end_real': 100,
+ 'line_ids': [(0, 0, {
+ 'name': '_',
+ 'partner_id': self.partner.id,
+ 'amount': 100,
+ 'account_id': bank_journal_usd.default_debit_account_id.id,
+ })],
+ })
+ statement.button_open()
+ statement.button_confirm_bank()
diff --git a/addons/account/tests/test_invoice_taxes.py b/addons/account/tests/test_invoice_taxes.py
index b6759271579..dd2e8b52f15 100644
--- a/addons/account/tests/test_invoice_taxes.py
+++ b/addons/account/tests/test_invoice_taxes.py
@@ -189,7 +189,13 @@ class TestInvoiceTaxes(AccountingTestCase):
}),
(0,0, {
- 'factor_percent': 100,
+ 'factor_percent': 10,
+ 'repartition_type': 'tax',
+ 'tag_ids': [(4, ref_tax_tag.id, 0)],
+ }),
+
+ (0,0, {
+ 'factor_percent': 90,
'repartition_type': 'tax',
'account_id': account_1.id,
'tag_ids': [(4, ref_tax_tag.id, 0)],
@@ -219,13 +225,13 @@ class TestInvoiceTaxes(AccountingTestCase):
refund.action_invoice_open()
refund_move = refund.move_id
- self.assertEqual(len(refund_move.line_ids), 3, "There should be 4 account move lines created for the refund: payable, base and tax line")
+ self.assertEqual(len(refund_move.line_ids), 4, "There should be 4 account move lines created for the refund: payable, base and 2 tax lines")
ref_base_line = refund_move.line_ids.filtered(lambda x: not x.tax_repartition_line_id and x.account_id.user_type_id.type != 'receivable')
self.assertEqual(len(ref_base_line), 1, "There should be only one base line generated")
self.assertEqual(abs(ref_base_line.balance), 100, "Base amount should be 100")
self.assertEqual(ref_base_line.tag_ids, ref_base_tag, "Base line should have received base tag")
- ref_tax_line = refund_move.line_ids.filtered(lambda x: x.tax_repartition_line_id.repartition_type == 'tax')
- self.assertEqual(len(ref_tax_line), 1, "There should be only one tax line")
- self.assertEqual(ref_tax_line.account_id, account_1, "Tax line should have been made on account 1")
- self.assertEqual(abs(ref_tax_line.balance), 42, "Tax line should have been made on account 1")
- self.assertEqual(ref_tax_line.tag_ids, ref_tax_tag, "Tax line should have the right tag")
+ ref_tax_lines = refund_move.line_ids.filtered(lambda x: x.tax_repartition_line_id.repartition_type == 'tax')
+ self.assertEqual(len(ref_tax_lines), 2, "There should be two refund tax lines")
+ self.assertEqual(abs(ref_tax_lines.filtered(lambda x: x.account_id == ref_base_line.account_id).balance), 4.2, "Refund tax line on base account should amount to 4.2 (10% of 42)")
+ self.assertEqual(abs(ref_tax_lines.filtered(lambda x: x.account_id == account_1).balance), 37.8, "Refund tax line on account 1 should amount to 37.8 (90% of 42)")
+ self.assertEqual(ref_tax_lines.mapped('tag_ids'), ref_tax_tag, "Refund tax lines should have the right tag")
diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py
index 808393d89a5..1556bb14628 100644
--- a/addons/account/tests/test_payment.py
+++ b/addons/account/tests/test_payment.py
@@ -129,11 +129,10 @@ class TestPayment(AccountingTestCase):
})
payment.post()
self.assertRecordValues(payment.move_line_ids, [
+ {'account_id': self.account_usd.id, 'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
+ {'account_id': self.transfer_account.id, 'debit': 32.70, 'credit': 0.0, 'amount_currency': 50, 'currency_id': self.currency_usd_id},
{'account_id': self.transfer_account.id, 'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
{'account_id': self.account_eur.id, 'debit': 32.70, 'credit': 0.0, 'amount_currency': 0, 'currency_id': False},
- {'account_id': self.account_usd.id, 'debit': 0.0, 'credit': 32.70, 'amount_currency': -50, 'currency_id': self.currency_usd_id},
- {'account_id': self.transfer_account.id, 'debit': 32.70, 'credit': 0.0, 'amount_currency': 50,
- 'currency_id': self.currency_usd_id},
])
def test_payment_chf_journal_usd(self):
diff --git a/addons/account/views/account_analytic_view.xml b/addons/account/views/account_analytic_view.xml
index e3c773b1ce7..7f8164329b7 100644
--- a/addons/account/views/account_analytic_view.xml
+++ b/addons/account/views/account_analytic_view.xml
@@ -68,7 +68,7 @@
<menuitem
name="Analytic Items"
- parent="account.menu_finance_entries_accounting_entries"
+ parent="account.menu_finance_entries_accounting_miscellaneous"
action="analytic.account_analytic_line_action_entries"
id="menu_action_analytic_lines_tree"
sequence="35"
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index dfd50988785..545b0453dec 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -354,13 +354,13 @@
domain="[('company_id', '=', parent.company_id)]"
context="{'default_partner_id': parent.partner_id}"
optional="show"/>
- <field name="analytic_tag_ids" groups="analytic.group_analytic_tags" widget="many2many_tags" options="{'color_field': 'color'}"/>
+ <field name="analytic_tag_ids" groups="analytic.group_analytic_tags" widget="many2many_tags" options="{'color_field': 'color'}" optional="show"/>
<field name="quantity"/>
<field name="uom_id" string="UoM" groups="uom.group_uom" optional="show"/>
<field name="price_unit"/>
- <field name="discount" string="Disc.%" groups="base.group_no_one" optional="show"/>
+ <field name="discount" string="Disc.%" optional="hide"/>
<field name="invoice_line_tax_ids" widget="many2many_tags" options="{'no_create': True}" context="{'type':parent.type, 'tree_view_ref': 'account.account_tax_view_tree', 'search_view_ref': 'account.account_tax_view_search'}"
- domain="[('type_tax_use','=','purchase'),('company_id', '=', parent.company_id)]" optional="show"/>
+ domain="[('type_tax_use','=','purchase'),('company_id', '=', parent.company_id)]"/>
<field name="price_subtotal" string="Amount"/>
<field name="currency_id" invisible="1"/>
</tree>
@@ -539,10 +539,9 @@
<field name="quantity"/>
<field name="uom_id" string="UoM" groups="uom.group_uom" optional="show"/>
<field name="price_unit" string="Price"/>
- <field name="discount" groups="base.group_no_one" string="Disc.%" optional="show"/>
+ <field name="discount" string="Disc.%" optional="hide"/>
<field name="invoice_line_tax_ids" widget="many2many_tags" options="{'no_create': True}" context="{'type':parent.type, 'tree_view_ref': 'account.account_tax_view_tree', 'search_view_ref': 'account.account_tax_view_search'}"
- domain="[('type_tax_use','=','sale'),('company_id', '=', parent.company_id)]"
- optional="hide"/>
+ domain="[('type_tax_use','=','sale'),('company_id', '=', parent.company_id)]"/>
<field name="price_subtotal" string="Subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
<field name="price_total" string="Total" groups="account.group_show_line_subtotals_tax_included"/>
<field name="currency_id" invisible="1"/>
diff --git a/addons/account/views/account_menuitem.xml b/addons/account/views/account_menuitem.xml
index 14f667247ad..e1a8b60121e 100644
--- a/addons/account/views/account_menuitem.xml
+++ b/addons/account/views/account_menuitem.xml
@@ -13,9 +13,11 @@
<menuitem id="menu_finance_payables" name="Vendors" parent="menu_finance" sequence="3"/>
<menuitem id="menu_finance_entries" name="Accounting" parent="menu_finance" sequence="4" groups="account.group_account_user"/>
<!-- Adviser sub-menus -->
- <menuitem id="menu_finance_entries_accounting_entries" name="Accounting Entries" parent="account.menu_finance_entries"/>
+ <menuitem id="menu_finance_entries_accounting_miscellaneous" name="Miscellaneous" parent="account.menu_finance_entries" sequence="1"/>
+ <menuitem id="menu_finance_entries_accounting_journals" name="Journals" parent="account.menu_finance_entries" sequence="2"/>
+ <menuitem id="menu_finance_entries_accounting_ledgers" name="Ledgers" parent="account.menu_finance_entries" sequence="3"/>
<menuitem id="menu_finance_entries_management" name="Management" parent="menu_finance_entries" sequence="10" groups="account.group_account_invoice"/>
- <menuitem id="menu_finance_entries_actions" name="Actions" parent="account.menu_finance_entries"/>
+ <menuitem id="menu_finance_entries_actions" name="Actions" parent="account.menu_finance_entries" sequence="11"/>
<menuitem id="menu_finance_entries_generate_entries" name="Generate Entries" parent="account.menu_finance_entries"/>
<menuitem id="menu_finance_reports" name="Reporting" parent="menu_finance" sequence="20" groups="account.group_account_manager"/>
@@ -34,4 +36,3 @@
</data>
</odoo>
-
diff --git a/addons/account/views/account_move_views.xml b/addons/account/views/account_move_views.xml
index 6aee6ab3e28..2ba5e3c33ed 100644
--- a/addons/account/views/account_move_views.xml
+++ b/addons/account/views/account_move_views.xml
@@ -131,7 +131,7 @@
<record id="view_move_line_tree" model="ir.ui.view">
<field name="name">account.move.line.tree</field>
<field name="model">account.move.line</field>
- <field eval="1" name="priority"/>
+ <field eval="100" name="priority"/>
<field name="arch" type="xml">
<tree string="Journal Items" create="false" editable="bottom">
<field name="date"/>
@@ -154,17 +154,111 @@
<field name="company_currency_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="parent_state" invisible="1"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="view_move_line_tree_grouped" model="ir.ui.view">
+ <field name="name">account.move.line.tree.grouped</field>
+ <field name="model">account.move.line</field>
+ <field eval="1" name="priority"/>
+ <field name="arch" type="xml">
+ <tree string="Journal Items" create="false" editable="bottom" expand="context.get('group_by') in ('move_id', 'account_id') and 1 or 0">
+ <field name="date" optional="show" attrs="{'readonly':[('parent_state','=','posted')]}"/>
+ <field name="ref" optional="hide"/>
+ <field name="move_id" optional="show" readonly="1"/>
+ <field name="name"/>
+ <field name="statement_id" invisible="1"/>
+ <field name="account_id" options="{'no_open': True, 'no_create': True}" domain="[('company_id', '=', company_id)]" groups="account.group_account_user" attrs="{'readonly':[('parent_state','=','posted')]}" optional="show"/>
+ <field name="partner_id" optional="show"/>
+ <field name="reconciled" invisible="1"/>
+ <field name="full_reconcile_id" string="Matching" optional="show" readonly="1"/>
+ <field name="reconcile_model_id" invisible="1"/>
+ <field name="date_maturity" optional="hide"/>
+ <field name="debit" sum="Total Debit" readonly="1"/>
+ <field name="credit" sum="Total Credit" readonly="1"/>
+ <field name="amount_currency" readonly="True" groups="base.group_multi_currency"/>
+ <field name="currency_id" readonly="True" invisible="1" />
+ <field name="company_currency_id" invisible="1"/>
+ <field name="company_id" invisible="1"/>
+ <field name="parent_state" invisible="1"/>
+ <field name="tax_ids" widget="many2many_tags" width_factor="0.5" optional="hide" attrs="{'readonly':[('parent_state','=','posted')]}"/>
+ <field name="tag_ids" widget="many2many_tags" width_factor="0.5" optional="hide"/>
<groupby name="move_id">
<field name="state" invisible="1"/>
- <button name="edit" type="edit" string="Edit"/>
- <button name="action_post" states="draft" string="Post" type="object" groups="account.group_account_invoice"/>
- <button name="%(action_view_account_move_reversal)d" states="posted" string="Reverse Entry" type="action" groups="account.group_account_invoice"/>
- <button name="action_duplicate" string="Duplicate" type="object" groups="account.group_account_invoice"/>
+ <button name="edit" type="edit" icon="fa-edit" title="Edit"/>
+ <button name="action_post" states="draft" icon="fa-check" title="Post" type="object" groups="account.group_account_invoice"/>
+ <button name="%(action_view_account_move_reversal)d" states="posted" title="Reverse" icon="fa-refresh" type="action" groups="account.group_account_invoice"/>
+ <button name="action_duplicate" icon="fa-files-o" title="Duplicate" type="object" groups="account.group_account_invoice"/>
</groupby>
</tree>
</field>
</record>
+ <record id="view_move_line_tree_grouped_sales_purchases" model="ir.ui.view">
+ <field name="name">account.move.line.tree.grouped.sales.purchase</field>
+ <field name="model">account.move.line</field>
+ <field name="mode">primary</field>
+ <field name="inherit_id" ref="account.view_move_line_tree_grouped"/>
+ <field name="arch" type="xml">
+ <field name="date" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="move_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="partner_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="full_reconcile_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="tag_ids" position="attributes">
+ <attribute name="optional">show</attribute>
+ <attribute name="string">Tax Grid</attribute>
+ <attribute name="domain">[('applicability', '=', 'taxes')]</attribute>
+ </field>
+ </field>
+ </record>
+
+ <record id="view_move_line_tree_grouped_bank_cash" model="ir.ui.view">
+ <field name="name">account.move.line.tree.grouped.bank.cash</field>
+ <field name="model">account.move.line</field>
+ <field name="mode">primary</field>
+ <field name="inherit_id" ref="account.view_move_line_tree_grouped"/>
+ <field name="arch" type="xml">
+ <field name="date" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="move_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="ref" position="attributes"><attribute name="optional">show</attribute></field>
+ <field name="full_reconcile_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ </field>
+ </record>
+
+ <record id="view_move_line_tree_grouped_misc" model="ir.ui.view">
+ <field name="name">account.move.line.tree.grouped.misc</field>
+ <field name="model">account.move.line</field>
+ <field name="mode">primary</field>
+ <field name="inherit_id" ref="account.view_move_line_tree_grouped"/>
+ <field name="arch" type="xml">
+ <field name="date" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="move_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ </field>
+ </record>
+
+ <record id="view_move_line_tree_grouped_general" model="ir.ui.view">
+ <field name="name">account.move.line.tree.grouped.misc</field>
+ <field name="model">account.move.line</field>
+ <field name="mode">primary</field>
+ <field name="inherit_id" ref="account.view_move_line_tree_grouped"/>
+ <field name="arch" type="xml">
+ <field name="account_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ </field>
+ </record>
+
+
+ <record id="view_move_line_tree_grouped_partner" model="ir.ui.view">
+ <field name="name">account.move.line.tree.grouped.partner</field>
+ <field name="model">account.move.line</field>
+ <field name="mode">primary</field>
+ <field name="inherit_id" ref="account.view_move_line_tree_grouped"/>
+ <field name="arch" type="xml">
+ <field name="partner_id" position="attributes"><attribute name="optional">hide</attribute></field>
+ <field name="date_maturity" position="attributes"><attribute name="optional">show</attribute></field>
+ </field>
+ </record>
+
<record id="view_move_line_tax_audit_tree" model="ir.ui.view">
<field name="name">account.move.line.tax.audit.tree</field>
<field name="model">account.move.line</field>
@@ -172,11 +266,16 @@
<field name="inherit_id" ref="account.view_move_line_tree"/>
<field name="arch" type="xml">
<field name="full_reconcile_id" position="after">
- <field name="tax_line_id"/>
+ <field name="tax_line_id" string="Tax"/>
<field name="tax_base_amount" sum="Total Base Amount"/>
<field name="tax_audit"/>
+ <field name="move_id"/>
</field>
<field name="date_maturity" position="replace"/>
+ <field name="analytic_account_id" position="replace"/>
+ <field name="full_reconcile_id" position="replace"/>
+ <field name="debit" position="replace"/>
+ <field name="credit" position="replace"/>
</field>
</record>
@@ -201,6 +300,13 @@
<separator/>
<filter string="Unposted" name="unposted" domain="[('move_id.state','=','draft')]" help="Unposted Journal Items"/>
<filter name="posted" string="Posted" domain="[('move_id.state','=','posted')]" help="Posted Journal Items"/>
+ <filter string="To Check" name="to_check" domain="[('move_id.to_check', '=', True)]"/>
+ <separator/>
+ <filter string="Sales" name="sales" domain="[('move_id.journal_id.type','=','sale')]" context="{'default_journal_type': 'sale'}"/>
+ <filter string="Purchases" name="purchases" domain="[('move_id.journal_id.type','=','purchase')]" context="{'default_journal_type': 'purchase'}"/>
+ <filter string="Bank" name="bank" domain="[('move_id.journal_id.type', '=', 'bank')]" context="{'default_journal_type': 'bank'}"/>
+ <filter string="Cash" name="cash" domain="[('move_id.journal_id.type', '=', 'cash')]" context="{'default_journal_type': 'cash'}"/>
+ <filter string="Miscellaneous Operations" domain="[('move_id.journal_id.type','=','general')]" name="misc_filter" context="{'default_journal_type': 'general'}"/>
<separator/>
<filter string="Unreconciled" domain="[('full_reconcile_id', '=', False), ('balance','!=', 0), ('account_id.reconcile','=',True)]" help="Journal items where matching number isn't set" name="unreconciled"/>
<separator/>
@@ -216,9 +322,9 @@
<field name="reconcile_model_id"/>
<group expand="0" string="Group By">
<filter string="Journal Entry" name="group_by_move" domain="[]" context="{'group_by':'move_id'}"/>
- <filter string="Partner" name="partner" domain="[]" context="{'group_by':'partner_id'}"/>
+ <filter string="Account" name="group_by_account" domain="[]" context="{'group_by':'account_id'}"/>
+ <filter string="Partner" name="group_by_partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Journal" name="journal" domain="[]" context="{'group_by':'journal_id'}"/>
- <filter string="Account" name="account" context="{'group_by':'account_id'}"/>
<filter string="Date" name="groupby_date" domain="[]" context="{'group_by':'date'}"/>
</group>
</search>
@@ -451,10 +557,58 @@
view_id="account.view_move_line_tax_audit_tree"/>
<record id="action_account_moves_all_a" model="ir.actions.act_window">
- <field name="context">{'journal_type':'general', 'search_default_group_by_move': 1, 'search_default_posted':1}</field>
+ <field name="context">{'journal_type':'general', 'search_default_group_by_move': 1, 'group_by':'move_id', 'search_default_posted':1, 'search_default_sales':1, 'name_groupby':1}</field>
<field name="name">Journal Items</field>
<field name="res_model">account.move.line</field>
- <field name="view_id" ref="view_move_line_tree"/>
+ <field name="view_id" ref="view_move_line_tree_grouped"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
+ <record id="action_account_moves_journal_sales" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'sales', 'search_default_group_by_move': 1, 'group_by':'move_id', 'search_default_posted':1, 'search_default_sales':1, 'name_groupby':1}</field>
+ <field name="name">Sales</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree_grouped_sales_purchases"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
+ <record id="action_account_moves_journal_purchase" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'purchase', 'search_default_group_by_move': 1, 'group_by':'move_id', 'search_default_posted':1, 'search_default_purchases':1, 'name_groupby':1}</field>
+ <field name="name">Purchase</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree_grouped_sales_purchases"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
+ <record id="action_account_moves_journal_bank_cash" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'bank', 'search_default_group_by_move': 1, 'group_by':'move_id', 'search_default_posted':1, 'search_default_bank':1, 'search_default_cash':1, 'name_groupby':1}</field>
+ <field name="name">Bank and Cash</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree_grouped_bank_cash"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
+ <record id="action_account_moves_journal_misc" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'general', 'search_default_group_by_move': 1, 'group_by':'move_id', 'search_default_posted':1, 'search_default_misc_filter':1, 'name_groupby':1}</field>
+ <field name="name">Miscellaneous</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree_grouped_misc"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
+ <record id="action_account_moves_ledger_general" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'general', 'search_default_group_by_account': 1, 'group_by':'account_id', 'search_default_posted':1}</field>
+ <field name="name">General Ledger</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree_grouped_general"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
+ <record id="action_account_moves_ledger_partner" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'general', 'search_default_group_by_partner': 1, 'group_by':'partner_id', 'search_default_posted':1, 'search_default_payable':1, 'search_default_receivable':1, 'search_default_unreconciled':1}</field>
+ <field name="name">Partner Ledger</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree_grouped_partner"/>
<field name="view_mode">tree,pivot,graph,form,kanban</field>
</record>
@@ -465,6 +619,14 @@
<field name="view_id" ref="view_move_line_tree"/>
</record>
+ <record id="action_account_moves_all" model="ir.actions.act_window">
+ <field name="context">{'journal_type':'general', 'search_default_posted':1}</field>
+ <field name="name">Journal Items</field>
+ <field name="res_model">account.move.line</field>
+ <field name="view_id" ref="view_move_line_tree"/>
+ <field name="view_mode">tree,pivot,graph,form,kanban</field>
+ </record>
+
<record id="action_move_line_graph" model="ir.actions.act_window">
<field name="name">Journal Items</field>
<field name="view_mode">graph,pivot</field>
@@ -520,15 +682,57 @@
<menuitem
id="menu_action_account_moves_all"
- action="action_account_moves_all_a"
- parent="account.menu_finance_entries_accounting_entries"
- groups="group_account_manager"
+ action="action_account_moves_all"
+ parent="account.menu_finance_entries_accounting_miscellaneous"
+ groups="base.group_no_one"
sequence="10"/>
<menuitem
id="menu_action_move_journal_line_form"
action="action_move_journal_line"
- parent="account.menu_finance_entries_accounting_entries"
+ parent="account.menu_finance_entries_accounting_miscellaneous"
+ groups="account.group_account_user"
+ sequence="1"/>
+
+ <menuitem
+ id="menu_action_account_moves_journal_sales"
+ action="action_account_moves_journal_sales"
+ parent="account.menu_finance_entries_accounting_journals"
+ groups="account.group_account_user"
+ sequence="1"/>
+
+ <menuitem
+ id="menu_action_account_moves_journal_purchase"
+ action="action_account_moves_journal_purchase"
+ parent="account.menu_finance_entries_accounting_journals"
+ groups="account.group_account_user"
+ sequence="1"/>
+
+ <menuitem
+ id="menu_action_account_moves_journal_bank_cash"
+ action="action_account_moves_journal_bank_cash"
+ parent="account.menu_finance_entries_accounting_journals"
+ groups="account.group_account_user"
+ sequence="1"/>
+
+ <menuitem
+ id="menu_action_account_moves_journal_misc"
+ action="action_account_moves_journal_misc"
+ parent="account.menu_finance_entries_accounting_journals"
+ groups="account.group_account_user"
+ sequence="1"/>
+
+ <menuitem
+ id="menu_action_account_moves_ledger_general"
+ action="action_account_moves_ledger_general"
+ parent="account.menu_finance_entries_accounting_ledgers"
+ groups="account.group_account_user"
+ sequence="1"/>
+
+ <menuitem
+ id="menu_action_account_moves_ledger_partner"
+ action="action_account_moves_ledger_partner"
+ parent="account.menu_finance_entries_accounting_ledgers"
groups="account.group_account_user"
sequence="1"/>
diff --git a/addons/account/wizard/account_invoice_send.py b/addons/account/wizard/account_invoice_send.py
index 17e539de230..da564fa97b8 100644
--- a/addons/account/wizard/account_invoice_send.py
+++ b/addons/account/wizard/account_invoice_send.py
@@ -11,6 +11,7 @@ class AccountInvoiceSend(models.TransientModel):
_description = 'Account Invoice Send'
is_email = fields.Boolean('Email', default=lambda self: self.env.company.invoice_is_email)
+ invoice_without_email = fields.Text(compute='_compute_invoice_without_email', string='invoice(s) that will not be sent')
is_print = fields.Boolean('Print', default=lambda self: self.env.company.invoice_is_print)
printed = fields.Boolean('Is Printed', default=False)
invoice_ids = fields.Many2many('account.invoice', 'account_invoice_account_invoice_send_rel', string='Invoices')
@@ -45,6 +46,22 @@ class AccountInvoiceSend(models.TransientModel):
self.composer_id.template_id = self.template_id.id
self.composer_id.onchange_template_id_wrapper()
+ @api.onchange('is_email')
+ def _compute_invoice_without_email(self):
+ for wizard in self:
+ if wizard.is_email and len(wizard.invoice_ids) > 1:
+ invoices = self.env['account.invoice'].search([
+ ('id', 'in', self.env.context.get('active_ids')),
+ ('partner_id.email', '=', False)
+ ])
+ if invoices:
+ wizard.invoice_without_email = "%s\n%s" % (
+ _("The following invoice(s) will not be sent by email, because the customers don't have email address."),
+ "\n".join([i.reference for i in invoices])
+ )
+ else:
+ wizard.invoice_without_email = False
+
@api.multi
def _send_email(self):
if self.is_email:
diff --git a/addons/account/wizard/account_invoice_send_views.xml b/addons/account/wizard/account_invoice_send_views.xml
index b104fa38c01..4f8888e8f54 100644
--- a/addons/account/wizard/account_invoice_send_views.xml
+++ b/addons/account/wizard/account_invoice_send_views.xml
@@ -23,6 +23,9 @@
<field name="is_email" />
<b><label for="is_email"/></b>
</div>
+ <div class="text-left d-inline-block mr8" attrs="{'invisible': ['|', ('is_email','=', False), ('invoice_without_email', '=', False)]}">
+ <field name="invoice_without_email" class="mr4"/>
+ </div>
<div name="mail_form" attrs="{'invisible': [('is_email', '=', False)]}">
<!-- visible wizard -->
<div attrs="{'invisible': [('composition_mode', '=', 'mass_mail')]}">