diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-06-18 10:24:25 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-06-18 10:24:25 +0200 |
| commit | 9035fb8a2a4ddfae2951ed1f540060896924bb44 (patch) | |
| tree | 024ce185effd5b1cb59463d2a187a9fa4db35472 /addons/account | |
| parent | 0fc33c37c3cea3cadf69c2352f49c88b295bd63f (diff) | |
| parent | 618ea1ab675d6454280deac7d6f853b2556e5be1 (diff) | |
[MERGE] forward port branch saas-12.4 up to 618ea1ab675
Diffstat (limited to 'addons/account')
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')]}"> |
