diff --git a/.tx/config b/.tx/config index fd6064fccfc..b16eb4f2c36 100644 --- a/.tx/config +++ b/.tx/config @@ -17,6 +17,11 @@ file_filter = addons/account_analytic_default/i18n/.po source_file = addons/account_analytic_default/i18n/account_analytic_default.pot source_lang = en +[odoo-master.account_analytic_default_hr_expense] +file_filter = addons/account_analytic_default_hr_expense/i18n/.po +source_file = addons/account_analytic_default_hr_expense/i18n/account_analytic_default_hr_expense.pot +source_lang = en + [odoo-master.account_bank_statement_import] file_filter = addons/account_bank_statement_import/i18n/.po source_file = addons/account_bank_statement_import/i18n/account_bank_statement_import.pot @@ -32,6 +37,16 @@ file_filter = addons/account_check_printing/i18n/.po source_file = addons/account_check_printing/i18n/account_check_printing.pot source_lang = en +[odoo-master.account_facturx] +file_filter = addons/account_facturx/i18n/.po +source_file = addons/account_facturx/i18n/account_facturx.pot +source_lang = en + +[odoo-master.account_lock] +file_filter = addons/account_lock/i18n/.po +source_file = addons/account_lock/i18n/account_lock.pot +source_lang = en + [odoo-master.account_payment] file_filter = addons/account_payment/i18n/.po source_file = addons/account_payment/i18n/account_payment.pot @@ -42,11 +57,6 @@ file_filter = addons/account_tax_python/i18n/.po source_file = addons/account_tax_python/i18n/account_tax_python.pot source_lang = en -[odoo-master.account_test] -file_filter = addons/account_test/i18n/.po -source_file = addons/account_test/i18n/account_test.pot -source_lang = en - [odoo-master.account_voucher] file_filter = addons/account_voucher/i18n/.po source_file = addons/account_voucher/i18n/account_voucher.pot @@ -82,11 +92,6 @@ file_filter = addons/auth_password_policy/i18n/.po source_file = addons/auth_password_policy/i18n/auth_password_policy.pot source_lang = en -[odoo-master.auth_password_policy_signup] -file_filter = addons/auth_password_policy_signup/i18n/.po -source_file = addons/auth_password_policy_signup/i18n/auth_password_policy_signup.pot -source_lang = en - [odoo-master.auth_signup] file_filter = addons/auth_signup/i18n/.po source_file = addons/auth_signup/i18n/auth_signup.pot @@ -277,6 +282,11 @@ file_filter = addons/hr_attendance/i18n/.po source_file = addons/hr_attendance/i18n/hr_attendance.pot source_lang = en +[odoo-master.hr_attendance_presence] +file_filter = addons/hr_attendance_presence/i18n/.po +source_file = addons/hr_attendance_presence/i18n/hr_attendance_presence.pot +source_lang = en + [odoo-master.hr_contract] file_filter = addons/hr_contract/i18n/.po source_file = addons/hr_contract/i18n/hr_contract.pot @@ -317,16 +327,6 @@ file_filter = addons/hr_org_chart/i18n/.po source_file = addons/hr_org_chart/i18n/hr_org_chart.pot source_lang = en -[odoo-master.hr_payroll] -file_filter = addons/hr_payroll/i18n/.po -source_file = addons/hr_payroll/i18n/hr_payroll.pot -source_lang = en - -[odoo-master.hr_payroll_account] -file_filter = addons/hr_payroll_account/i18n/.po -source_file = addons/hr_payroll_account/i18n/hr_payroll_account.pot -source_lang = en - [odoo-master.hr_presence] file_filter = addons/hr_presence/i18n/.po source_file = addons/hr_presence/i18n/hr_presence.pot @@ -447,14 +447,19 @@ file_filter = addons/mrp/i18n/.po source_file = addons/mrp/i18n/mrp.pot source_lang = en +[odoo-master.mrp_account] +file_filter = addons/mrp_account/i18n/.po +source_file = addons/mrp_account/i18n/mrp_account.pot +source_lang = en + [odoo-master.mrp_bom_cost] file_filter = addons/mrp_bom_cost/i18n/.po source_file = addons/mrp_bom_cost/i18n/mrp_bom_cost.pot source_lang = en -[odoo-master.mrp_byproduct] -file_filter = addons/mrp_byproduct/i18n/.po -source_file = addons/mrp_byproduct/i18n/mrp_byproduct.pot +[odoo-master.mrp_subcontracting] +file_filter = addons/mrp_subcontracting/i18n/.po +source_file = addons/mrp_subcontracting/i18n/mrp_subcontracting.pot source_lang = en [odoo-master.note] @@ -482,6 +487,11 @@ file_filter = addons/partner_autocomplete/i18n/.po source_file = addons/partner_autocomplete/i18n/partner_autocomplete.pot source_lang = en +[odoo-master.partner_autocomplete_address_extended] +file_filter = addons/partner_autocomplete_address_extended/i18n/.po +source_file = addons/partner_autocomplete_address_extended/i18n/partner_autocomplete_address_extended.pot +source_lang = en + [odoo-master.payment] file_filter = addons/payment/i18n/.po source_file = addons/payment/i18n/payment.pot @@ -632,6 +642,11 @@ file_filter = addons/purchase_mrp/i18n/.po source_file = addons/purchase_mrp/i18n/purchase_mrp.pot source_lang = en +[odoo-master.purchase_mrp_subcontracting] +file_filter = addons/purchase_mrp_subcontracting/i18n/.po +source_file = addons/purchase_mrp_subcontracting/i18n/purchase_mrp_subcontracting.pot +source_lang = en + [odoo-master.purchase_requisition] file_filter = addons/purchase_requisition/i18n/.po source_file = addons/purchase_requisition/i18n/purchase_requisition.pot @@ -667,6 +682,16 @@ file_filter = addons/sale/i18n/.po source_file = addons/sale/i18n/sale.pot source_lang = en +[odoo-master.sale_coupon] +file_filter = addons/sale_coupon/i18n/.po +source_file = addons/sale_coupon/i18n/sale_coupon.pot +source_lang = en + +[odoo-master.sale_coupon_delivery] +file_filter = addons/sale_coupon_delivery/i18n/.po +source_file = addons/sale_coupon_delivery/i18n/sale_coupon_delivery.pot +source_lang = en + [odoo-master.sale_crm] file_filter = addons/sale_crm/i18n/.po source_file = addons/sale_crm/i18n/sale_crm.pot @@ -692,6 +717,11 @@ file_filter = addons/sale_mrp/i18n/.po source_file = addons/sale_mrp/i18n/sale_mrp.pot source_lang = en +[odoo-master.sale_product_configurator] +file_filter = addons/sale_product_configurator/i18n/.po +source_file = addons/sale_product_configurator/i18n/sale_product_configurator.pot +source_lang = en + [odoo-master.sale_purchase] file_filter = addons/sale_purchase/i18n/.po source_file = addons/sale_purchase/i18n/sale_purchase.pot @@ -952,6 +982,16 @@ file_filter = addons/website_sale_comparison/i18n/.po source_file = addons/website_sale_comparison/i18n/website_sale_comparison.pot source_lang = en +[odoo-master.website_sale_coupon] +file_filter = addons/website_sale_coupon/i18n/.po +source_file = addons/website_sale_coupon/i18n/website_sale_coupon.pot +source_lang = en + +[odoo-master.website_sale_coupon_delivery] +file_filter = addons/website_sale_coupon_delivery/i18n/.po +source_file = addons/website_sale_coupon_delivery/i18n/website_sale_coupon_delivery.pot +source_lang = en + [odoo-master.website_sale_delivery] file_filter = addons/website_sale_delivery/i18n/.po source_file = addons/website_sale_delivery/i18n/website_sale_delivery.pot @@ -972,6 +1012,11 @@ file_filter = addons/website_sale_management/i18n/.po source_file = addons/website_sale_management/i18n/website_sale_management.pot source_lang = en +[odoo-master.website_sale_product_configurator] +file_filter = addons/website_sale_product_configurator/i18n/.po +source_file = addons/website_sale_product_configurator/i18n/website_sale_product_configurator.pot +source_lang = en + [odoo-master.website_sale_slides] file_filter = addons/website_sale_slides/i18n/.po source_file = addons/website_sale_slides/i18n/website_sale_slides.pot 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: %s
Reason: %s") % (invoice.id, invoice.number, description) else: message = _("This vendor bill credit note has been created from: %s
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 @@ - + - + + domain="[('type_tax_use','=','purchase'),('company_id', '=', parent.company_id)]"/> @@ -539,10 +539,9 @@ - + + domain="[('type_tax_use','=','sale'),('company_id', '=', parent.company_id)]"/> 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 @@ - + + + - + @@ -34,4 +36,3 @@ - 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 @@ account.move.line.tree account.move.line - + @@ -154,17 +154,111 @@ + + + + + + account.move.line.tree.grouped + account.move.line + + + + + + + + + + + + + + + + + + + + + + + -