Browse Source

[UPDT] Reconcilation issue in kit

pull/164/head
Ajmal Cybro 4 years ago
parent
commit
942dce8810
  1. 2
      base_accounting_kit/__manifest__.py
  2. 5
      base_accounting_kit/doc/changelog.md
  3. 279
      base_accounting_kit/models/payment_matching.py
  4. 3
      base_accounting_kit/views/payment_matching.xml

2
base_accounting_kit/__manifest__.py

@ -22,7 +22,7 @@
{
'name': 'Odoo 14 Full Accounting Kit',
'version': '14.0.2.6.5',
'version': '14.0.2.7.6',
'category': 'Accounting',
'live_test_url': 'https://www.youtube.com/watch?v=peAp2Tx_XIs',
'summary': """ Asset and Budget Management,

5
base_accounting_kit/doc/changelog.md

@ -43,3 +43,8 @@
- Asset Issue updated
#### 05.04.2021
#### Version 14.0.2.7.6
#### UPDT
- Reconcilation issue.

279
base_accounting_kit/models/payment_matching.py

@ -17,15 +17,15 @@ class AccountReconciliation(models.AbstractModel):
@api.model
def process_bank_statement_line(self, st_line_ids, data):
""" Handles data sent from the bank statement reconciliation widget
(and can otherwise serve as an old-API bridge)
:param st_line_ids
:param list of dicts data: must contains the keys
'counterpart_aml_dicts', 'payment_aml_ids' and 'new_aml_dicts',
whose value is the same as described in process_reconciliation
except that ids are used instead of recordsets.
:returns dict: used as a hook to add additional keys.
"""Handles data sent from the bank statement reconciliation widget
(and can otherwise serve as an old-API bridge)
:param st_line_ids
:param list of dicts data: must contains the keys
'counterpart_aml_dicts', 'payment_aml_ids' and 'new_aml_dicts',
whose value is the same as described in process_reconciliation
except that ids are used instead of recordsets.
:returns dict: used as a hook to add additional keys.
"""
st_lines = self.env['account.bank.statement.line'].browse(st_line_ids)
AccountMoveLine = self.env['account.move.line']
@ -36,15 +36,20 @@ class AccountReconciliation(models.AbstractModel):
payment_aml_rec = AccountMoveLine.browse(datum.get('payment_aml_ids', []))
for aml_dict in datum.get('counterpart_aml_dicts', []):
aml_dict['move_line'] = AccountMoveLine.browse(aml_dict['counterpart_aml_id'])
aml_dict['move_line'] = AccountMoveLine.browse(
aml_dict['counterpart_aml_id']
)
del aml_dict['counterpart_aml_id']
if datum.get('partner_id') is not None:
st_line.write({'partner_id': datum['partner_id']})
ctx['default_to_check'] = datum.get('to_check')
moves = st_line.with_context(ctx).reconcile(datum.get('lines_vals_list', []), to_check=datum.get('to_check', False))
moves = st_line.with_context(ctx).process_reconciliation(
datum.get('counterpart_aml_dicts', []),
payment_aml_rec,
datum.get('new_aml_dicts', []))
processed_moves = (processed_moves | moves)
return {'moves': processed_moves.ids, 'statement_line_ids': processed_moves.mapped('line_ids.statement_line_id').ids}
@api.model
@ -587,8 +592,8 @@ class AccountReconciliation(models.AbstractModel):
]
domain = expression.AND([domain, domain_post_at])
# if st_line.company_id.account_bank_reconciliation_start:
# domain = expression.AND([domain, [('date', '>=', st_line.company_id.account_bank_reconciliation_start)]])
if st_line.company_id.account_bank_reconciliation_start:
domain = expression.AND([domain, [('date', '>=', st_line.company_id.account_bank_reconciliation_start)]])
return domain
@api.model
@ -842,6 +847,11 @@ class AccountInvoiceLine(models.Model):
_inherit = 'account.move.line'
def _create_writeoff(self, writeoff_vals):
""" Create a writeoff move per journal for the account.move.lines in self. If debit/credit is not specified in vals,
the writeoff amount will be computed as the sum of amount_residual of the given recordset.
:param writeoff_vals: list of dicts containing values suitable for account_move_line.create(). The data in vals will
be processed to create bot writeoff account.move.line and their enclosing account.move.
"""
def compute_writeoff_counterpart_vals(values):
line_values = values.copy()
line_values['debit'], line_values['credit'] = line_values['credit'], line_values['debit']
@ -927,3 +937,244 @@ class AccountInvoiceLine(models.Model):
# Return the writeoff move.line which is to be reconciled
return line_to_reconcile
class AccountBankStatement(models.Model):
_inherit = "account.bank.statement"
accounting_date = fields.Date(string="Accounting Date",
help="If set, the accounting entries created during the bank statement reconciliation process will be created at this date.\n"
"This is useful if the accounting period in which the entries should normally be booked is already closed.",
states={'open': [('readonly', False)]}, readonly=True)
def action_bank_reconcile_bank_statements(self):
self.ensure_one()
bank_stmt_lines = self.mapped('line_ids')
return {
'type': 'ir.actions.client',
'tag': 'bank_statement_reconciliation_view',
'context': {'statement_line_ids': bank_stmt_lines.ids, 'company_ids': self.mapped('company_id').ids},
}
class AccountBankStatementLine(models.Model):
_inherit = "account.bank.statement.line"
move_name = fields.Char(string='Journal Entry Name', readonly=True,
default=False, copy=False,
help="Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again.")
def process_reconciliation(self, counterpart_aml_dicts=None, payment_aml_rec=None, new_aml_dicts=None):
"""Match statement lines with existing payments (eg. checks) and/or
payables/receivables (eg. invoices and credit notes) and/or new move
lines (eg. write-offs).
If any new journal item needs to be created (via new_aml_dicts or
counterpart_aml_dicts), a new journal entry will be created and will
contain those items, as well as a journal item for the bank statement
line.
Finally, mark the statement line as reconciled by putting the matched
moves ids in the column journal_entry_ids.
:param self: browse collection of records that are supposed to have no
accounting entries already linked.
:param (list of dicts) counterpart_aml_dicts: move lines to create to
reconcile with existing payables/receivables.
The expected keys are :
- 'name'
- 'debit'
- 'credit'
- 'move_line'
# The move line to reconcile (partially if specified
# debit/credit is lower than move line's credit/debit)
:param (list of recordsets) payment_aml_rec: recordset move lines
representing existing payments (which are already fully reconciled)
:param (list of dicts) new_aml_dicts: move lines to create. The expected
keys are :
- 'name'
- 'debit'
- 'credit'
- 'account_id'
- (optional) 'tax_ids'
- (optional) Other account.move.line fields like analytic_account_id
or analytics_id
- (optional) 'reconcile_model_id'
:returns: The journal entries with which the transaction was matched.
If there was at least an entry in counterpart_aml_dicts or
new_aml_dicts, this list contains the move created by the
reconciliation, containing entries for the statement.line (1), the
counterpart move lines (0..*) and the new move lines (0..*).
"""
payable_account_type = self.env.ref("account.data_account_type_payable")
receivable_account_type = self.env.ref("account.data_account_type_receivable")
suspense_moves_mode = self._context.get("suspense_moves_mode")
counterpart_aml_dicts = counterpart_aml_dicts or []
payment_aml_rec = payment_aml_rec or self.env["account.move.line"]
new_aml_dicts = new_aml_dicts or []
aml_obj = self.env["account.move.line"]
company_currency = self.journal_id.company_id.currency_id
statement_currency = self.journal_id.currency_id or company_currency
counterpart_moves = self.env["account.move"]
# Check and prepare received data
if any(rec.statement_id for rec in payment_aml_rec):
raise UserError(_("A selected move line was already reconciled."))
for aml_dict in counterpart_aml_dicts:
if aml_dict["move_line"].reconciled and not suspense_moves_mode:
raise UserError(_("A selected move line was already reconciled."))
if isinstance(aml_dict["move_line"], int):
aml_dict["move_line"] = aml_obj.browse(aml_dict["move_line"])
account_types = self.env["account.account.type"]
for aml_dict in counterpart_aml_dicts + new_aml_dicts:
if aml_dict.get("tax_ids") and isinstance(aml_dict["tax_ids"][0], int):
# Transform the value in the format required for One2many and
# Many2many fields
aml_dict["tax_ids"] = [(4, id, None) for id in aml_dict["tax_ids"]]
user_type_id = (
self.env["account.account"]
.browse(aml_dict.get("account_id"))
.user_type_id
)
if (
user_type_id in [payable_account_type, receivable_account_type]
and user_type_id not in account_types
):
account_types |= user_type_id
# Fully reconciled moves are just linked to the bank statement
total = self.amount
currency = self.currency_id or statement_currency
for aml_rec in payment_aml_rec:
balance = (
aml_rec.amount_currency if aml_rec.currency_id else aml_rec.balance
)
aml_currency = aml_rec.currency_id or aml_rec.company_currency_id
total -= aml_currency._convert(
balance, currency, aml_rec.company_id, aml_rec.date
)
aml_rec.with_context(check_move_validity=False).write({"statement_line_id": self.id})
counterpart_moves = counterpart_moves | aml_rec.move_id
if aml_rec.journal_id.post_at == "bank_rec" and aml_rec.payment_id and aml_rec.move_id.state == "draft":
# In case the journal is set to only post payments when performing bank
#reconciliation, we modify its date and post it.
aml_rec.move_id.date = self.date
aml_rec.payment_id.payment_date = self.date
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:
self._check_invoice_state(invoice)
# 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:
aml_obj = self.env["account.move.line"]
self.move_id.line_ids.with_context(force_delete=True).unlink()
liquidity_aml_dict = self._prepare_liquidity_move_line_vals()
aml_obj.with_context(check_move_validity=False).create(liquidity_aml_dict)
self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
counterpart_moves = counterpart_moves | self.move_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)
# Create write-offs
for aml_dict in new_aml_dicts:
aml_obj.with_context(check_move_validity=False).create(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
counterpart_move_line = aml_dict.pop("move_line")
new_aml = aml_obj.with_context(check_move_validity=False).create(aml_dict)
aml_to_reconcile.append((new_aml, counterpart_move_line))
# 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})
elif self.move_name:
raise UserError(_('Operation not allowed. Since your statement line already received a number (%s), you cannot reconcile it entirely with existing journal entries otherwise it would make a gap in the numbering. You should book an entry and make a regular revert of it in case you want to cancel it.')% (self.move_name))
# create the res.partner.bank if needed
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.partner_bank_id = self._find_or_create_bank_account()
counterpart_moves._check_balanced()
return counterpart_moves
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.is_invoice(include_receipts=True):
invoice._compute_amount()
class ResCompany(models.Model):
_inherit = "res.company"
account_bank_reconciliation_start = fields.Date(string="Bank Reconciliation Threshold", help="""The bank reconciliation widget won't ask to reconcile payments older than this date.
This is useful if you install accounting after having used invoicing for some time and
don't want to reconcile all the past payments with bank statements.""")

3
base_accounting_kit/views/payment_matching.xml

@ -26,7 +26,8 @@
<button class="oe_stat_button" name="open_payment_matching_screen"
string="Payment Matching" type="object"
groups="account.group_account_user"
attrs="{'invisible':[('is_reconciled','=',True)]}" icon="fa-dollar"/>
attrs="{'invisible': ['|', ('is_reconciled', '=', True), ('state', '!=', 'posted')]}"
icon="fa-dollar"/>
<field name="is_reconciled" invisible="1"/>
</xpath>
</field>

Loading…
Cancel
Save