summaryrefslogtreecommitdiff
path: root/addons/account/models/account_partial_reconcile.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/account/models/account_partial_reconcile.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/account/models/account_partial_reconcile.py')
-rw-r--r--addons/account/models/account_partial_reconcile.py617
1 files changed, 617 insertions, 0 deletions
diff --git a/addons/account/models/account_partial_reconcile.py b/addons/account/models/account_partial_reconcile.py
new file mode 100644
index 00000000..a9e1d03b
--- /dev/null
+++ b/addons/account/models/account_partial_reconcile.py
@@ -0,0 +1,617 @@
+# -*- coding: utf-8 -*-
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError, ValidationError
+
+from datetime import date
+
+
+class AccountPartialReconcile(models.Model):
+ _name = "account.partial.reconcile"
+ _description = "Partial Reconcile"
+ _rec_name = "id"
+
+ # ==== Reconciliation fields ====
+ debit_move_id = fields.Many2one(
+ comodel_name='account.move.line',
+ index=True, required=True)
+ credit_move_id = fields.Many2one(
+ comodel_name='account.move.line',
+ index=True, required=True)
+ full_reconcile_id = fields.Many2one(
+ comodel_name='account.full.reconcile',
+ string="Full Reconcile", copy=False)
+
+ # ==== Currency fields ====
+ company_currency_id = fields.Many2one(
+ comodel_name='res.currency',
+ string="Company Currency",
+ related='company_id.currency_id',
+ help="Utility field to express amount currency")
+ debit_currency_id = fields.Many2one(
+ comodel_name='res.currency',
+ store=True,
+ compute='_compute_debit_currency_id',
+ string="Currency of the debit journal item.")
+ credit_currency_id = fields.Many2one(
+ comodel_name='res.currency',
+ store=True,
+ compute='_compute_credit_currency_id',
+ string="Currency of the credit journal item.")
+
+ # ==== Amount fields ====
+ amount = fields.Monetary(
+ currency_field='company_currency_id',
+ help="Always positive amount concerned by this matching expressed in the company currency.")
+ debit_amount_currency = fields.Monetary(
+ currency_field='debit_currency_id',
+ help="Always positive amount concerned by this matching expressed in the debit line foreign currency.")
+ credit_amount_currency = fields.Monetary(
+ currency_field='credit_currency_id',
+ help="Always positive amount concerned by this matching expressed in the credit line foreign currency.")
+
+ # ==== Other fields ====
+ company_id = fields.Many2one(
+ comodel_name='res.company',
+ string="Company", store=True, readonly=False,
+ related='debit_move_id.company_id')
+ max_date = fields.Date(
+ string="Max Date of Matched Lines", store=True,
+ compute='_compute_max_date',
+ help="Technical field used to determine at which date this reconciliation needs to be shown on the "
+ "aged receivable/payable reports.")
+
+ # -------------------------------------------------------------------------
+ # CONSTRAINT METHODS
+ # -------------------------------------------------------------------------
+
+ @api.constrains('debit_currency_id', 'credit_currency_id')
+ def _check_required_computed_currencies(self):
+ bad_partials = self.filtered(lambda partial: not partial.debit_currency_id or not partial.credit_currency_id)
+ if bad_partials:
+ raise ValidationError(_("Missing foreign currencies on partials having ids: %s", bad_partials.ids))
+
+ # -------------------------------------------------------------------------
+ # COMPUTE METHODS
+ # -------------------------------------------------------------------------
+
+ @api.depends('debit_move_id.date', 'credit_move_id.date')
+ def _compute_max_date(self):
+ for partial in self:
+ partial.max_date = max(
+ partial.debit_move_id.date,
+ partial.credit_move_id.date
+ )
+
+ @api.depends('debit_move_id')
+ def _compute_debit_currency_id(self):
+ for partial in self:
+ partial.debit_currency_id = partial.debit_move_id.currency_id \
+ or partial.debit_move_id.company_currency_id
+
+ @api.depends('credit_move_id')
+ def _compute_credit_currency_id(self):
+ for partial in self:
+ partial.credit_currency_id = partial.credit_move_id.currency_id \
+ or partial.credit_move_id.company_currency_id
+
+ # -------------------------------------------------------------------------
+ # LOW-LEVEL METHODS
+ # -------------------------------------------------------------------------
+
+ def unlink(self):
+ # OVERRIDE to unlink full reconcile linked to the current partials
+ # and reverse the tax cash basis journal entries.
+
+ # Avoid cyclic unlink calls when removing the partials that could remove some full reconcile
+ # and then, loop again and again.
+ if not self:
+ return True
+
+ # Retrieve the matching number to unlink.
+ full_to_unlink = self.full_reconcile_id
+
+ # Retrieve the CABA entries to reverse.
+ moves_to_reverse = self.env['account.move'].search([('tax_cash_basis_rec_id', 'in', self.ids)])
+
+ # Unlink partials before doing anything else to avoid 'Record has already been deleted' due to the recursion.
+ res = super().unlink()
+
+ # Reverse CABA entries.
+ today = fields.Date.context_today(self)
+ default_values_list = [{
+ 'date': move.date if move.date > (move.company_id.period_lock_date or date.min) else today,
+ 'ref': _('Reversal of: %s') % move.name,
+ } for move in moves_to_reverse]
+ moves_to_reverse._reverse_moves(default_values_list, cancel=True)
+
+ # Remove the matching numbers.
+ full_to_unlink.unlink()
+
+ return res
+
+ # -------------------------------------------------------------------------
+ # RECONCILIATION METHODS
+ # -------------------------------------------------------------------------
+
+ def _collect_tax_cash_basis_values(self):
+ ''' Collect all information needed to create the tax cash basis journal entries on the current partials.
+ :return: A dictionary mapping each move_id to the result of 'account_move._collect_tax_cash_basis_values'.
+ Also, add the 'partials' keys being a list of dictionary, one for each partial to process:
+ * partial: The account.partial.reconcile record.
+ * percentage: The reconciled percentage represented by the partial.
+ * payment_rate: The applied rate of this partial.
+ '''
+ tax_cash_basis_values_per_move = {}
+
+ if not self:
+ return {}
+
+ for partial in self:
+ for move in {partial.debit_move_id.move_id, partial.credit_move_id.move_id}:
+
+ # Collect data about cash basis.
+ if move.id not in tax_cash_basis_values_per_move:
+ tax_cash_basis_values_per_move[move.id] = move._collect_tax_cash_basis_values()
+
+ # Nothing to process on the move.
+ if not tax_cash_basis_values_per_move.get(move.id):
+ continue
+ move_values = tax_cash_basis_values_per_move[move.id]
+
+ # Check the cash basis configuration only when at least one cash basis tax entry need to be created.
+ journal = partial.company_id.tax_cash_basis_journal_id
+
+ if not journal:
+ raise UserError(_("There is no tax cash basis journal defined for the '%s' company.\n"
+ "Configure it in Accounting/Configuration/Settings") % partial.company_id.display_name)
+
+ partial_amount = 0.0
+ partial_amount_currency = 0.0
+ rate_amount = 0.0
+ rate_amount_currency = 0.0
+ if partial.debit_move_id.move_id == move:
+ partial_amount += partial.amount
+ partial_amount_currency += partial.debit_amount_currency
+ rate_amount -= partial.credit_move_id.balance
+ rate_amount_currency -= partial.credit_move_id.amount_currency
+ source_line = partial.debit_move_id
+ counterpart_line = partial.credit_move_id
+ if partial.credit_move_id.move_id == move:
+ partial_amount += partial.amount
+ partial_amount_currency += partial.credit_amount_currency
+ rate_amount += partial.debit_move_id.balance
+ rate_amount_currency += partial.debit_move_id.amount_currency
+ source_line = partial.credit_move_id
+ counterpart_line = partial.debit_move_id
+
+ if move_values['currency'] == move.company_id.currency_id:
+ # Percentage made on company's currency.
+ percentage = partial_amount / move_values['total_balance']
+ else:
+ # Percentage made on foreign currency.
+ percentage = partial_amount_currency / move_values['total_amount_currency']
+
+ if source_line.currency_id != counterpart_line.currency_id:
+ # When the invoice and the payment are not sharing the same foreign currency, the rate is computed
+ # on-the-fly using the payment date.
+ payment_rate = self.env['res.currency']._get_conversion_rate(
+ counterpart_line.company_currency_id,
+ source_line.currency_id,
+ counterpart_line.company_id,
+ counterpart_line.date,
+ )
+ elif rate_amount:
+ payment_rate = rate_amount_currency / rate_amount
+ else:
+ payment_rate = 0.0
+
+ partial_vals = {
+ 'partial': partial,
+ 'percentage': percentage,
+ 'payment_rate': payment_rate,
+ }
+
+ # Add partials.
+ move_values.setdefault('partials', [])
+ move_values['partials'].append(partial_vals)
+
+ # Clean-up moves having nothing to process.
+ return {k: v for k, v in tax_cash_basis_values_per_move.items() if v}
+
+ @api.model
+ def _prepare_cash_basis_base_line_vals(self, base_line, balance, amount_currency):
+ ''' Prepare the values to be used to create the cash basis journal items for the tax base line
+ passed as parameter.
+
+ :param base_line: An account.move.line being the base of some taxes.
+ :param balance: The balance to consider for this line.
+ :param amount_currency: The balance in foreign currency to consider for this line.
+ :return: A python dictionary that could be passed to the create method of
+ account.move.line.
+ '''
+ account = base_line.company_id.account_cash_basis_base_account_id or base_line.account_id
+ return {
+ 'name': base_line.move_id.name,
+ 'debit': balance if balance > 0.0 else 0.0,
+ 'credit': -balance if balance < 0.0 else 0.0,
+ 'amount_currency': amount_currency,
+ 'currency_id': base_line.currency_id.id,
+ 'partner_id': base_line.partner_id.id,
+ 'account_id': account.id,
+ 'tax_ids': [(6, 0, base_line.tax_ids.ids)],
+ 'tax_tag_ids': [(6, 0, base_line._convert_tags_for_cash_basis(base_line.tax_tag_ids).ids)],
+ 'tax_exigible': True,
+ }
+
+ @api.model
+ def _prepare_cash_basis_counterpart_base_line_vals(self, cb_base_line_vals):
+ ''' Prepare the move line used as a counterpart of the line created by
+ _prepare_cash_basis_base_line_vals.
+
+ :param cb_base_line_vals: The line returned by _prepare_cash_basis_base_line_vals.
+ :return: A python dictionary that could be passed to the create method of
+ account.move.line.
+ '''
+ return {
+ 'name': cb_base_line_vals['name'],
+ 'debit': cb_base_line_vals['credit'],
+ 'credit': cb_base_line_vals['debit'],
+ 'account_id': cb_base_line_vals['account_id'],
+ 'amount_currency': -cb_base_line_vals['amount_currency'],
+ 'currency_id': cb_base_line_vals['currency_id'],
+ 'partner_id': cb_base_line_vals['partner_id'],
+ 'tax_exigible': True,
+ }
+
+ @api.model
+ def _prepare_cash_basis_tax_line_vals(self, tax_line, balance, amount_currency):
+ ''' Prepare the move line corresponding to a tax in the cash basis entry.
+
+ :param tax_line: An account.move.line record being a tax line.
+ :param balance: The balance to consider for this line.
+ :param amount_currency: The balance in foreign currency to consider for this line.
+ :return: A python dictionary that could be passed to the create method of
+ account.move.line.
+ '''
+ return {
+ 'name': tax_line.name,
+ 'debit': balance if balance > 0.0 else 0.0,
+ 'credit': -balance if balance < 0.0 else 0.0,
+ 'tax_base_amount': tax_line.tax_base_amount,
+ 'tax_repartition_line_id': tax_line.tax_repartition_line_id.id,
+ 'tax_ids': [(6, 0, tax_line.tax_ids.ids)],
+ 'tax_tag_ids': [(6, 0, tax_line._convert_tags_for_cash_basis(tax_line.tax_tag_ids).ids)],
+ 'account_id': tax_line.tax_repartition_line_id.account_id.id or tax_line.account_id.id,
+ 'amount_currency': amount_currency,
+ 'currency_id': tax_line.currency_id.id,
+ 'partner_id': tax_line.partner_id.id,
+ 'tax_exigible': True,
+ }
+
+ @api.model
+ def _prepare_cash_basis_counterpart_tax_line_vals(self, tax_line, cb_tax_line_vals):
+ ''' Prepare the move line used as a counterpart of the line created by
+ _prepare_cash_basis_tax_line_vals.
+
+ :param tax_line: An account.move.line record being a tax line.
+ :param cb_tax_line_vals: The result of _prepare_cash_basis_counterpart_tax_line_vals.
+ :return: A python dictionary that could be passed to the create method of
+ account.move.line.
+ '''
+ return {
+ 'name': cb_tax_line_vals['name'],
+ 'debit': cb_tax_line_vals['credit'],
+ 'credit': cb_tax_line_vals['debit'],
+ 'account_id': tax_line.account_id.id,
+ 'amount_currency': -cb_tax_line_vals['amount_currency'],
+ 'currency_id': cb_tax_line_vals['currency_id'],
+ 'partner_id': cb_tax_line_vals['partner_id'],
+ 'tax_exigible': True,
+ }
+
+ @api.model
+ def _get_cash_basis_base_line_grouping_key_from_vals(self, base_line_vals):
+ ''' Get the grouping key of a cash basis base line that hasn't yet been created.
+ :param base_line_vals: The values to create a new account.move.line record.
+ :return: The grouping key as a tuple.
+ '''
+ return (
+ base_line_vals['currency_id'],
+ base_line_vals['partner_id'],
+ base_line_vals['account_id'],
+ tuple(base_line_vals['tax_ids'][0][2]), # Decode [(6, 0, [...])] command
+ tuple(base_line_vals['tax_tag_ids'][0][2]), # Decode [(6, 0, [...])] command
+ )
+
+ @api.model
+ def _get_cash_basis_base_line_grouping_key_from_record(self, base_line, account=None):
+ ''' Get the grouping key of a journal item being a base line.
+ :param base_line: An account.move.line record.
+ :param account: Optional account to shadow the current base_line one.
+ :return: The grouping key as a tuple.
+ '''
+ return (
+ base_line.currency_id.id,
+ base_line.partner_id.id,
+ (account or base_line.account_id).id,
+ tuple(base_line.tax_ids.ids),
+ tuple(base_line._convert_tags_for_cash_basis(base_line.tax_tag_ids).ids),
+ )
+
+ @api.model
+ def _get_cash_basis_tax_line_grouping_key_from_vals(self, tax_line_vals):
+ ''' Get the grouping key of a cash basis tax line that hasn't yet been created.
+ :param tax_line_vals: The values to create a new account.move.line record.
+ :return: The grouping key as a tuple.
+ '''
+ return (
+ tax_line_vals['currency_id'],
+ tax_line_vals['partner_id'],
+ tax_line_vals['account_id'],
+ tuple(tax_line_vals['tax_ids'][0][2]), # Decode [(6, 0, [...])] command
+ tuple(tax_line_vals['tax_tag_ids'][0][2]), # Decode [(6, 0, [...])] command
+ tax_line_vals['tax_repartition_line_id'],
+ )
+
+ @api.model
+ def _get_cash_basis_tax_line_grouping_key_from_record(self, tax_line, account=None):
+ ''' Get the grouping key of a journal item being a tax line.
+ :param tax_line: An account.move.line record.
+ :param account: Optional account to shadow the current tax_line one.
+ :return: The grouping key as a tuple.
+ '''
+ return (
+ tax_line.currency_id.id,
+ tax_line.partner_id.id,
+ (account or tax_line.account_id).id,
+ tuple(tax_line.tax_ids.ids),
+ tuple(tax_line._convert_tags_for_cash_basis(tax_line.tax_tag_ids).ids),
+ tax_line.tax_repartition_line_id.id,
+ )
+
+ @api.model
+ def _fix_cash_basis_full_balance_coverage(self, move_values, partial_values, pending_cash_basis_lines, partial_lines_to_create):
+ ''' This method is used to ensure the full coverage of the current move when it becomes fully paid.
+ For example, suppose a line of 0.03 paid 50-50. Without this method, each cash basis entry will report
+ 0.03 / 0.5 = 0.015 ~ 0.02 per cash entry on the tax report as base amount, for a total of 0.04.
+ This is wrong because we expect 0.03.on the tax report as base amount. This is wrong because we expect 0.03.
+
+ :param move_values: The collected values about cash basis for the current move.
+ :param partial_values: The collected values about cash basis for the current partial.
+ :param pending_cash_basis_lines: The previously generated lines during this reconciliation but not yet created.
+ :param partial_lines_to_create: The generated lines for the current and last partial making the move fully paid.
+ '''
+ # DEPRECATED: TO BE REMOVED IN MASTER
+ residual_amount_per_group = {}
+ move = move_values['move']
+
+ # ==========================================================================
+ # Part 1:
+ # Add the balance of all journal items that are not tax exigible in order to
+ # ensure the exact balance will be report on the Tax Report.
+ # This part is needed when the move will be fully paid after the current
+ # reconciliation.
+ # ==========================================================================
+
+ for line in move_values['to_process_lines']:
+ if line.tax_repartition_line_id:
+ # Tax line.
+ grouping_key = self._get_cash_basis_tax_line_grouping_key_from_record(
+ line,
+ account=line.tax_repartition_line_id.account_id,
+ )
+ residual_amount_per_group.setdefault(grouping_key, 0.0)
+ residual_amount_per_group[grouping_key] += line['amount_currency']
+
+ elif line.tax_ids:
+ # Base line.
+ grouping_key = self._get_cash_basis_base_line_grouping_key_from_record(
+ line,
+ account=line.company_id.account_cash_basis_base_account_id,
+ )
+ residual_amount_per_group.setdefault(grouping_key, 0.0)
+ residual_amount_per_group[grouping_key] += line['amount_currency']
+
+ # ==========================================================================
+ # Part 2:
+ # Subtract all previously created cash basis journal items during previous
+ # reconciliation.
+ # ==========================================================================
+
+ previous_tax_cash_basis_moves = self.env['account.move'].search([
+ '|',
+ ('tax_cash_basis_rec_id', 'in', self.ids),
+ ('tax_cash_basis_move_id', '=', move.id),
+ ])
+ for line in previous_tax_cash_basis_moves.line_ids:
+ if line.tax_repartition_line_id:
+ # Tax line.
+ grouping_key = self._get_cash_basis_tax_line_grouping_key_from_record(line)
+ elif line.tax_ids:
+ # Base line.
+ grouping_key = self._get_cash_basis_base_line_grouping_key_from_record(line)
+ else:
+ continue
+
+ if grouping_key not in residual_amount_per_group:
+ # The grouping_key is unknown regarding the current lines.
+ # Maybe this move has been created before migration and then,
+ # we are not able to ensure the full coverage of the balance.
+ return
+
+ residual_amount_per_group[grouping_key] -= line['amount_currency']
+
+ # ==========================================================================
+ # Part 3:
+ # Subtract all pending cash basis journal items that will be created during
+ # this reconciliation.
+ # ==========================================================================
+
+ for grouping_key, balance in pending_cash_basis_lines:
+ residual_amount_per_group[grouping_key] -= balance
+
+ # ==========================================================================
+ # Part 4:
+ # Fix the current cash basis journal items in progress by replacing the
+ # balance by the residual one.
+ # ==========================================================================
+
+ for grouping_key, aggregated_vals in partial_lines_to_create.items():
+ line_vals = aggregated_vals['vals']
+
+ amount_currency = residual_amount_per_group[grouping_key]
+ balance = partial_values['payment_rate'] and amount_currency / partial_values['payment_rate'] or 0.0
+ line_vals.update({
+ 'debit': balance if balance > 0.0 else 0.0,
+ 'credit': -balance if balance < 0.0 else 0.0,
+ 'amount_currency': amount_currency,
+ })
+
+ def _create_tax_cash_basis_moves(self):
+ ''' Create the tax cash basis journal entries.
+ :return: The newly created journal entries.
+ '''
+ tax_cash_basis_values_per_move = self._collect_tax_cash_basis_values()
+
+ moves_to_create = []
+ to_reconcile_after = []
+ for move_values in tax_cash_basis_values_per_move.values():
+ move = move_values['move']
+ pending_cash_basis_lines = []
+
+ for partial_values in move_values['partials']:
+ partial = partial_values['partial']
+
+ # Init the journal entry.
+ move_vals = {
+ 'move_type': 'entry',
+ 'date': partial.max_date,
+ 'ref': move.name,
+ 'journal_id': partial.company_id.tax_cash_basis_journal_id.id,
+ 'line_ids': [],
+ 'tax_cash_basis_rec_id': partial.id,
+ 'tax_cash_basis_move_id': move.id,
+ }
+
+ # Tracking of lines grouped all together.
+ # Used to reduce the number of generated lines and to avoid rounding issues.
+ partial_lines_to_create = {}
+
+ for line in move_values['to_process_lines']:
+
+ # ==========================================================================
+ # Compute the balance of the current line on the cash basis entry.
+ # This balance is a percentage representing the part of the journal entry
+ # that is actually paid by the current partial.
+ # ==========================================================================
+
+ # Percentage expressed in the foreign currency.
+ amount_currency = line.currency_id.round(line.amount_currency * partial_values['percentage'])
+ balance = partial_values['payment_rate'] and amount_currency / partial_values['payment_rate'] or 0.0
+
+ # ==========================================================================
+ # Prepare the mirror cash basis journal item of the current line.
+ # Group them all together as much as possible to reduce the number of
+ # generated journal items.
+ # Also track the computed balance in order to avoid rounding issues when
+ # the journal entry will be fully paid. At that case, we expect the exact
+ # amount of each line has been covered by the cash basis journal entries
+ # and well reported in the Tax Report.
+ # ==========================================================================
+
+ if line.tax_repartition_line_id:
+ # Tax line.
+
+ cb_line_vals = self._prepare_cash_basis_tax_line_vals(line, balance, amount_currency)
+ grouping_key = self._get_cash_basis_tax_line_grouping_key_from_vals(cb_line_vals)
+ elif line.tax_ids:
+ # Base line.
+
+ cb_line_vals = self._prepare_cash_basis_base_line_vals(line, balance, amount_currency)
+ grouping_key = self._get_cash_basis_base_line_grouping_key_from_vals(cb_line_vals)
+
+ if grouping_key in partial_lines_to_create:
+ aggregated_vals = partial_lines_to_create[grouping_key]['vals']
+
+ debit = aggregated_vals['debit'] + cb_line_vals['debit']
+ credit = aggregated_vals['credit'] + cb_line_vals['credit']
+ balance = debit - credit
+
+ aggregated_vals.update({
+ 'debit': balance if balance > 0 else 0,
+ 'credit': -balance if balance < 0 else 0,
+ 'amount_currency': aggregated_vals['amount_currency'] + cb_line_vals['amount_currency'],
+ })
+
+ if line.tax_repartition_line_id:
+ aggregated_vals.update({
+ 'tax_base_amount': aggregated_vals['tax_base_amount'] + cb_line_vals['tax_base_amount'],
+ })
+ partial_lines_to_create[grouping_key]['tax_line'] += line
+ else:
+ partial_lines_to_create[grouping_key] = {
+ 'vals': cb_line_vals,
+ }
+ if line.tax_repartition_line_id:
+ partial_lines_to_create[grouping_key].update({
+ 'tax_line': line,
+ })
+
+ # ==========================================================================
+ # Create the counterpart journal items.
+ # ==========================================================================
+
+ # To be able to retrieve the correct matching between the tax lines to reconcile
+ # later, the lines will be created using a specific sequence.
+ sequence = 0
+
+ for grouping_key, aggregated_vals in partial_lines_to_create.items():
+ line_vals = aggregated_vals['vals']
+ line_vals['sequence'] = sequence
+
+ pending_cash_basis_lines.append((grouping_key, line_vals['amount_currency']))
+
+ if 'tax_repartition_line_id' in line_vals:
+ # Tax line.
+
+ tax_line = aggregated_vals['tax_line']
+ counterpart_line_vals = self._prepare_cash_basis_counterpart_tax_line_vals(tax_line, line_vals)
+ counterpart_line_vals['sequence'] = sequence + 1
+
+ if tax_line.account_id.reconcile:
+ move_index = len(moves_to_create)
+ to_reconcile_after.append((tax_line, move_index, counterpart_line_vals['sequence']))
+
+ else:
+ # Base line.
+
+ counterpart_line_vals = self._prepare_cash_basis_counterpart_base_line_vals(line_vals)
+ counterpart_line_vals['sequence'] = sequence + 1
+
+ sequence += 2
+
+ move_vals['line_ids'] += [(0, 0, counterpart_line_vals), (0, 0, line_vals)]
+
+ moves_to_create.append(move_vals)
+
+ moves = self.env['account.move'].create(moves_to_create)
+ moves._post(soft=False)
+
+ # Reconcile the tax lines being on a reconcile tax basis transfer account.
+ for lines, move_index, sequence in to_reconcile_after:
+
+ # In expenses, all move lines are created manually without any grouping on tax lines.
+ # In that case, 'lines' could be already reconciled.
+ lines = lines.filtered(lambda x: not x.reconciled)
+ if not lines:
+ continue
+
+ counterpart_line = moves[move_index].line_ids.filtered(lambda line: line.sequence == sequence)
+
+ # When dealing with tiny amounts, the line could have a zero amount and then, be already reconciled.
+ if counterpart_line.reconciled:
+ continue
+
+ (lines + counterpart_line).reconcile()
+
+ return moves