mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
[ADD] account_reconciliation_widget: first working alpha
This commit is contained in:
@@ -33,6 +33,7 @@ class AccountBankStatementLine(models.Model):
|
||||
|
||||
_inherit = "account.bank.statement.line"
|
||||
|
||||
# FIXME: is this necessary now?
|
||||
move_name = fields.Char(
|
||||
string="Journal Entry Name",
|
||||
readonly=True,
|
||||
@@ -129,22 +130,23 @@ class AccountBankStatementLine(models.Model):
|
||||
and user_type_id not in account_types
|
||||
):
|
||||
account_types |= user_type_id
|
||||
if suspense_moves_mode:
|
||||
if any(not line.journal_entry_ids for line in self):
|
||||
raise UserError(
|
||||
_(
|
||||
"Some selected statement line were not already "
|
||||
"reconciled with an account move."
|
||||
)
|
||||
)
|
||||
else:
|
||||
if any(line.journal_entry_ids for line in self):
|
||||
raise UserError(
|
||||
_(
|
||||
"A selected statement line was already reconciled with "
|
||||
"an account move."
|
||||
)
|
||||
)
|
||||
# FIXME: review
|
||||
# if suspense_moves_mode:
|
||||
# if any(not line.journal_entry_ids for line in self):
|
||||
# raise UserError(
|
||||
# _(
|
||||
# "Some selected statement line were not already "
|
||||
# "reconciled with an account move."
|
||||
# )
|
||||
# )
|
||||
# else:
|
||||
# if any(line.journal_entry_ids for line in self):
|
||||
# raise UserError(
|
||||
# _(
|
||||
# "A selected statement line was already reconciled with "
|
||||
# "an account move."
|
||||
# )
|
||||
# )
|
||||
|
||||
# Fully reconciled moves are just linked to the bank statement
|
||||
total = self.amount
|
||||
@@ -171,7 +173,7 @@ class AccountBankStatementLine(models.Model):
|
||||
# it.
|
||||
aml_rec.move_id.date = self.date
|
||||
aml_rec.payment_id.payment_date = self.date
|
||||
aml_rec.move_id.post()
|
||||
aml_rec.move_id.action_post()
|
||||
# We check the paid status of the invoices reconciled with this
|
||||
# payment
|
||||
for invoice in aml_rec.payment_id.reconciled_invoice_ids:
|
||||
@@ -181,86 +183,10 @@ class AccountBankStatementLine(models.Model):
|
||||
# (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:
|
||||
|
||||
# Create the move
|
||||
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
|
||||
move_vals = self._prepare_reconciliation_move(self.statement_id.name)
|
||||
if suspense_moves_mode:
|
||||
self.button_cancel_reconciliation()
|
||||
move = (
|
||||
self.env["account.move"]
|
||||
.with_context(default_journal_id=move_vals["journal_id"])
|
||||
.create(move_vals)
|
||||
counterpart_moves = self._create_counterpart_and_new_aml(
|
||||
counterpart_moves, counterpart_aml_dicts, new_aml_dicts
|
||||
)
|
||||
counterpart_moves = counterpart_moves | move
|
||||
|
||||
# Create The payment
|
||||
payment = self.env["account.payment"]
|
||||
partner_id = (
|
||||
self.partner_id
|
||||
or (aml_dict.get("move_line") and aml_dict["move_line"].partner_id)
|
||||
or self.env["res.partner"]
|
||||
)
|
||||
if abs(total) > 0.00001:
|
||||
payment_vals = self._prepare_payment_vals(total)
|
||||
if not payment_vals["partner_id"]:
|
||||
payment_vals["partner_id"] = partner_id.id
|
||||
if payment_vals["partner_id"] and len(account_types) == 1:
|
||||
payment_vals["partner_type"] = (
|
||||
"customer"
|
||||
if account_types == receivable_account_type
|
||||
else "supplier"
|
||||
)
|
||||
payment = payment.create(payment_vals)
|
||||
|
||||
# Complete dicts to create both counterpart move lines and write-offs
|
||||
to_create = counterpart_aml_dicts + new_aml_dicts
|
||||
date = self.date or fields.Date.today()
|
||||
for aml_dict in to_create:
|
||||
aml_dict["move_id"] = move.id
|
||||
aml_dict["partner_id"] = self.partner_id.id
|
||||
aml_dict["statement_line_id"] = self.id
|
||||
self._prepare_move_line_for_currency(aml_dict, date)
|
||||
|
||||
# Create write-offs
|
||||
for aml_dict in new_aml_dicts:
|
||||
aml_dict["payment_id"] = payment and payment.id or False
|
||||
aml_obj.with_context(check_move_validity=False).create(aml_dict)
|
||||
|
||||
# Create counterpart move lines and reconcile them
|
||||
for aml_dict in counterpart_aml_dicts:
|
||||
if (
|
||||
aml_dict["move_line"].payment_id
|
||||
and not aml_dict["move_line"].statement_line_id
|
||||
):
|
||||
aml_dict["move_line"].write({"statement_line_id": self.id})
|
||||
if aml_dict["move_line"].partner_id.id:
|
||||
aml_dict["partner_id"] = aml_dict["move_line"].partner_id.id
|
||||
aml_dict["account_id"] = aml_dict["move_line"].account_id.id
|
||||
aml_dict["payment_id"] = payment and payment.id or False
|
||||
|
||||
counterpart_move_line = aml_dict.pop("move_line")
|
||||
new_aml = aml_obj.with_context(check_move_validity=False).create(
|
||||
aml_dict
|
||||
)
|
||||
|
||||
(new_aml | counterpart_move_line).reconcile()
|
||||
|
||||
self._check_invoice_state(counterpart_move_line.move_id)
|
||||
|
||||
# Balance the move
|
||||
st_line_amount = -sum([x.balance for x in move.line_ids])
|
||||
aml_dict = self._prepare_reconciliation_move_line(move, st_line_amount)
|
||||
aml_dict["payment_id"] = payment and payment.id or False
|
||||
aml_obj.with_context(check_move_validity=False).create(aml_dict)
|
||||
|
||||
# Needs to be called manually as lines were created 1 by 1
|
||||
move.update_lines_tax_exigibility()
|
||||
move.post()
|
||||
# record the move name on the statement line to be able to retrieve
|
||||
# it in case of unreconciliation
|
||||
self.write({"move_name": move.name})
|
||||
payment and payment.write({"payment_reference": move.name})
|
||||
elif self.move_name:
|
||||
raise UserError(
|
||||
_(
|
||||
@@ -277,177 +203,71 @@ class AccountBankStatementLine(models.Model):
|
||||
if self.account_number and self.partner_id and not self.bank_account_id:
|
||||
# Search bank account without partner to handle the case the
|
||||
# res.partner.bank already exists but is set on a different partner.
|
||||
self.bank_account_id = self._find_or_create_bank_account()
|
||||
self.partner_bank_id = self._find_or_create_bank_account()
|
||||
|
||||
counterpart_moves._check_balanced()
|
||||
return counterpart_moves
|
||||
|
||||
def _prepare_reconciliation_move(self, move_ref):
|
||||
"""Prepare the dict of values to create the move from a statement line.
|
||||
This method may be overridden to adapt domain logic through model
|
||||
inheritance (make sure to call super() to establish a clean extension
|
||||
chain).
|
||||
def _create_counterpart_and_new_aml(
|
||||
self, counterpart_moves, counterpart_aml_dicts, new_aml_dicts
|
||||
):
|
||||
|
||||
:param char move_ref: will be used as the reference of the generated
|
||||
account move
|
||||
:return: dict of value to create() the account.move
|
||||
"""
|
||||
ref = move_ref or ""
|
||||
if self.ref:
|
||||
ref = move_ref + " - " + self.ref if move_ref else self.ref
|
||||
data = {
|
||||
"type": "entry",
|
||||
"journal_id": self.statement_id.journal_id.id,
|
||||
"currency_id": self.statement_id.currency_id.id,
|
||||
"date": self.statement_id.accounting_date or self.date,
|
||||
"partner_id": self.partner_id.id,
|
||||
"ref": ref,
|
||||
}
|
||||
if self.move_name:
|
||||
data.update(name=self.move_name)
|
||||
return data
|
||||
aml_obj = self.env["account.move.line"]
|
||||
|
||||
def _prepare_reconciliation_move_line(self, move, amount):
|
||||
"""Prepare the dict of values to balance the move.
|
||||
# Delete previous move_lines
|
||||
self.move_id.line_ids.with_context(force_delete=True).unlink()
|
||||
|
||||
: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
|
||||
statement_currency = self.journal_id.currency_id or company_currency
|
||||
st_line_currency = self.currency_id or statement_currency
|
||||
amount_currency = False
|
||||
st_line_currency_rate = (
|
||||
self.currency_id and (self.amount_currency / self.amount) or False
|
||||
)
|
||||
if isinstance(move, dict):
|
||||
amount_sum = sum(x[2].get("amount_currency", 0) for x in move["line_ids"])
|
||||
else:
|
||||
amount_sum = sum(x.amount_currency for x in move.line_ids)
|
||||
# We have several use case here to compare the currency and amount
|
||||
# currency of counterpart line to balance the move:
|
||||
if (
|
||||
st_line_currency != company_currency
|
||||
and st_line_currency == statement_currency
|
||||
):
|
||||
# company in currency A, statement in currency B and transaction in
|
||||
# currency B
|
||||
# counterpart line must have currency B and correct amount is
|
||||
# inverse of already existing lines
|
||||
amount_currency = -amount_sum
|
||||
elif (
|
||||
st_line_currency != company_currency
|
||||
and statement_currency == company_currency
|
||||
):
|
||||
# company in currency A, statement in currency A and transaction in
|
||||
# currency B
|
||||
# counterpart line must have currency B and correct amount is
|
||||
# inverse of already existing lines
|
||||
amount_currency = -amount_sum
|
||||
elif (
|
||||
st_line_currency != company_currency
|
||||
and st_line_currency != statement_currency
|
||||
):
|
||||
# company in currency A, statement in currency B and transaction in
|
||||
# currency C
|
||||
# counterpart line must have currency B and use rate between B and
|
||||
# C to compute correct amount
|
||||
amount_currency = -amount_sum / st_line_currency_rate
|
||||
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
|
||||
amount_currency = amount / st_line_currency_rate
|
||||
# Create liquidity line
|
||||
liquidity_aml_dict = self._prepare_liquidity_move_line_vals()
|
||||
aml_obj.with_context(check_move_validity=False).create(liquidity_aml_dict)
|
||||
|
||||
# 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
|
||||
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
|
||||
counterpart_moves = counterpart_moves | self.move_id
|
||||
|
||||
# Check if default_debit or default_credit account are properly configured
|
||||
account_id = (
|
||||
amount >= 0
|
||||
and self.statement_id.journal_id.default_credit_account_id.id
|
||||
or self.statement_id.journal_id.default_debit_account_id.id
|
||||
)
|
||||
# Complete dicts to create both counterpart move lines and write-offs
|
||||
to_create = counterpart_aml_dicts + new_aml_dicts
|
||||
date = self.date or fields.Date.today()
|
||||
for aml_dict in to_create:
|
||||
aml_dict["move_id"] = self.move_id.id
|
||||
aml_dict["partner_id"] = self.partner_id.id
|
||||
aml_dict["statement_line_id"] = self.id
|
||||
self._prepare_move_line_for_currency(aml_dict, date)
|
||||
|
||||
if not account_id:
|
||||
raise UserError(
|
||||
_(
|
||||
"No default debit and credit account defined on journal %s "
|
||||
"(ids: %s)."
|
||||
% (
|
||||
self.statement_id.journal_id.name,
|
||||
self.statement_id.journal_id.ids,
|
||||
)
|
||||
)
|
||||
)
|
||||
# Create write-offs
|
||||
for aml_dict in new_aml_dicts:
|
||||
aml_obj.with_context(check_move_validity=False).create(aml_dict)
|
||||
|
||||
aml_dict = {
|
||||
"name": self.name,
|
||||
"partner_id": self.partner_id and self.partner_id.id or False,
|
||||
"account_id": account_id,
|
||||
"credit": amount < 0 and -amount or 0.0,
|
||||
"debit": amount > 0 and amount or 0.0,
|
||||
"statement_line_id": self.id,
|
||||
"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
|
||||
# Create counterpart move lines and reconcile them
|
||||
aml_to_reconcile = []
|
||||
for aml_dict in counterpart_aml_dicts:
|
||||
if not aml_dict["move_line"].statement_line_id:
|
||||
aml_dict["move_line"].write({"statement_line_id": self.id})
|
||||
if aml_dict["move_line"].partner_id.id:
|
||||
aml_dict["partner_id"] = aml_dict["move_line"].partner_id.id
|
||||
aml_dict["account_id"] = aml_dict["move_line"].account_id.id
|
||||
|
||||
def _get_communication(self, payment_method_id):
|
||||
return self.name or ""
|
||||
counterpart_move_line = aml_dict.pop("move_line")
|
||||
new_aml = aml_obj.with_context(check_move_validity=False).create(aml_dict)
|
||||
|
||||
def _prepare_payment_vals(self, total):
|
||||
"""Prepare the dict of values to create the payment from a statement
|
||||
line. This method may be overridden for update dict
|
||||
through model inheritance (make sure to call super() to establish a
|
||||
clean extension chain).
|
||||
aml_to_reconcile.append((new_aml, counterpart_move_line))
|
||||
|
||||
:param float total: will be used as the amount of the generated payment
|
||||
:return: dict of value to create() the account.payment
|
||||
"""
|
||||
self.ensure_one()
|
||||
partner_type = False
|
||||
if self.partner_id:
|
||||
if total < 0:
|
||||
partner_type = "supplier"
|
||||
else:
|
||||
partner_type = "customer"
|
||||
if not partner_type and self.env.context.get("default_partner_type"):
|
||||
partner_type = self.env.context["default_partner_type"]
|
||||
currency = self.journal_id.currency_id or self.company_id.currency_id
|
||||
payment_methods = (
|
||||
(total > 0)
|
||||
and self.journal_id.inbound_payment_method_ids
|
||||
or self.journal_id.outbound_payment_method_ids
|
||||
)
|
||||
return {
|
||||
"payment_method_id": payment_methods and payment_methods[0].id or False,
|
||||
"payment_type": total > 0 and "inbound" or "outbound",
|
||||
"partner_id": self.partner_id.id,
|
||||
"partner_type": partner_type,
|
||||
"journal_id": self.statement_id.journal_id.id,
|
||||
"payment_date": self.date,
|
||||
"state": "reconciled",
|
||||
"currency_id": currency.id,
|
||||
"amount": abs(total),
|
||||
"communication": self._get_communication(
|
||||
payment_methods[0] if payment_methods else False
|
||||
),
|
||||
"name": self.statement_id.name or _("Bank Statement %s") % self.date,
|
||||
}
|
||||
# Post to allow reconcile
|
||||
self.move_id.with_context(skip_account_move_synchronization=True).action_post()
|
||||
|
||||
# Reconcile new lines with counterpart
|
||||
for new_aml, counterpart_move_line in aml_to_reconcile:
|
||||
(new_aml | counterpart_move_line).reconcile()
|
||||
|
||||
self._check_invoice_state(counterpart_move_line.move_id)
|
||||
|
||||
# Needs to be called manually as lines were created 1 by 1
|
||||
self.move_id.update_lines_tax_exigibility()
|
||||
self.move_id.with_context(skip_account_move_synchronization=True).action_post()
|
||||
# record the move name on the statement line to be able to retrieve
|
||||
# it in case of unreconciliation
|
||||
self.write({"move_name": self.move_id.name})
|
||||
|
||||
return counterpart_moves
|
||||
|
||||
def _prepare_move_line_for_currency(self, aml_dict, date):
|
||||
self.ensure_one()
|
||||
|
||||
Reference in New Issue
Block a user