mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
86
account_reconciliation_widget/README.rst
Normal file
86
account_reconciliation_widget/README.rst
Normal file
@@ -0,0 +1,86 @@
|
||||
=============================
|
||||
account_reconciliation_widget
|
||||
=============================
|
||||
|
||||
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount_reconciliation_widget-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/account_reconciliation_widget/tree/14.0/account_reconciliation_widget
|
||||
:alt: OCA/account_reconciliation_widget
|
||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||
:target: https://translation.odoo-community.org/projects/account_reconciliation_widget-14-0/account_reconciliation_widget-14-0-account_reconciliation_widget
|
||||
:alt: Translate me on Weblate
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4|
|
||||
|
||||
This module restores account reconciliation widget moved from Odoo community to enterpise in V. 14.0
|
||||
Provides two widgets designed to reconcile move lines in a easy way: one focused on bank statements and another for generic use.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
With an user with full accounting features enabled:
|
||||
|
||||
Invoicing --> Accounting --> Actions --> Reconciliation.
|
||||
|
||||
From journal items list view you can select check of them and click Action --> Reconcile.
|
||||
|
||||
From accounting dashboard you can use reconcile button in Bank / Cash journals.
|
||||
|
||||
Also, you can navigate to statements and use the reconcile button.
|
||||
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account_reconciliation_widget/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/account_reconciliation_widget/issues/new?body=module:%20account_reconciliation_widget%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
* Ozono Multimedia
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Tecnativa - Pedro M. Baeza
|
||||
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
This module is part of the `OCA/account_reconciliation_widget <https://github.com/OCA/account_reconciliation_widget/tree/14.0/account_reconciliation_widget>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
1
account_reconciliation_widget/__init__.py
Normal file
1
account_reconciliation_widget/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
24
account_reconciliation_widget/__manifest__.py
Normal file
24
account_reconciliation_widget/__manifest__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright 2020 Ozono Multimedia - Iván Antón
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "account_reconciliation_widget",
|
||||
"version": "14.0.1.0.0",
|
||||
"category": "Accounting",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Account reconciliation widget",
|
||||
"author": "Odoo, Ozono Multimedia, Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/account-reconcile",
|
||||
"depends": ["account"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"views/assets.xml",
|
||||
"views/account_view.xml",
|
||||
"views/account_bank_statement_view.xml",
|
||||
"views/account_journal_dashboard_view.xml",
|
||||
],
|
||||
"qweb": [
|
||||
"static/src/xml/account_reconciliation.xml",
|
||||
],
|
||||
"installable": True,
|
||||
}
|
||||
5
account_reconciliation_widget/models/__init__.py
Normal file
5
account_reconciliation_widget/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from . import account_move
|
||||
from . import account_bank_statement
|
||||
from . import account_journal
|
||||
from . import reconciliation_widget
|
||||
from . import res_company
|
||||
333
account_reconciliation_widget/models/account_bank_statement.py
Normal file
333
account_reconciliation_widget/models/account_bank_statement.py
Normal file
@@ -0,0 +1,333 @@
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
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"
|
||||
|
||||
# FIXME: is this necessary now?
|
||||
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
|
||||
# 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
|
||||
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:
|
||||
counterpart_moves = self._create_counterpart_and_new_aml(
|
||||
counterpart_moves, counterpart_aml_dicts, new_aml_dicts
|
||||
)
|
||||
|
||||
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 _create_counterpart_and_new_aml(
|
||||
self, counterpart_moves, counterpart_aml_dicts, new_aml_dicts
|
||||
):
|
||||
|
||||
aml_obj = self.env["account.move.line"]
|
||||
|
||||
# Delete previous move_lines
|
||||
self.move_id.line_ids.with_context(force_delete=True).unlink()
|
||||
|
||||
# Create liquidity line
|
||||
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})
|
||||
|
||||
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()
|
||||
37
account_reconciliation_widget/models/account_journal.py
Normal file
37
account_reconciliation_widget/models/account_journal.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
|
||||
_inherit = "account.journal"
|
||||
|
||||
def action_open_reconcile(self):
|
||||
# Open reconciliation view for bank statements belonging to this journal
|
||||
bank_stmt = (
|
||||
self.env["account.bank.statement"]
|
||||
.search([("journal_id", "in", self.ids)])
|
||||
.mapped("line_ids")
|
||||
)
|
||||
return {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "bank_statement_reconciliation_view",
|
||||
"context": {
|
||||
"statement_line_ids": bank_stmt.ids,
|
||||
"company_ids": self.mapped("company_id").ids,
|
||||
},
|
||||
}
|
||||
|
||||
def action_open_reconcile_to_check(self):
|
||||
self.ensure_one()
|
||||
ids = self.to_check_ids().ids
|
||||
action_context = {
|
||||
"show_mode_selector": False,
|
||||
"company_ids": self.mapped("company_id").ids,
|
||||
"suspense_moves_mode": True,
|
||||
"statement_line_ids": ids,
|
||||
}
|
||||
return {
|
||||
"type": "ir.actions.client",
|
||||
"tag": "bank_statement_reconciliation_view",
|
||||
"context": action_context,
|
||||
}
|
||||
127
account_reconciliation_widget/models/account_move.py
Normal file
127
account_reconciliation_widget/models/account_move.py
Normal file
@@ -0,0 +1,127 @@
|
||||
from odoo import _, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class AccountMoveLine(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 both 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"],
|
||||
)
|
||||
if "amount_currency" in values:
|
||||
line_values["amount_currency"] = -line_values["amount_currency"]
|
||||
return line_values
|
||||
|
||||
# Group writeoff_vals by journals
|
||||
writeoff_dict = {}
|
||||
for val in writeoff_vals:
|
||||
journal_id = val.get("journal_id", False)
|
||||
if not writeoff_dict.get(journal_id, False):
|
||||
writeoff_dict[journal_id] = [val]
|
||||
else:
|
||||
writeoff_dict[journal_id].append(val)
|
||||
|
||||
partner_id = (
|
||||
self.env["res.partner"]._find_accounting_partner(self[0].partner_id).id
|
||||
)
|
||||
company_currency = self[0].account_id.company_id.currency_id
|
||||
writeoff_currency = self[0].account_id.currency_id or company_currency
|
||||
line_to_reconcile = self.env["account.move.line"]
|
||||
# Iterate and create one writeoff by journal
|
||||
writeoff_moves = self.env["account.move"]
|
||||
for journal_id, lines in writeoff_dict.items():
|
||||
total = 0
|
||||
total_currency = 0
|
||||
writeoff_lines = []
|
||||
date = fields.Date.today()
|
||||
for vals in lines:
|
||||
# Check and complete vals
|
||||
if "account_id" not in vals or "journal_id" not in vals:
|
||||
raise UserError(
|
||||
_(
|
||||
"It is mandatory to specify an account and a "
|
||||
"journal to create a write-off."
|
||||
)
|
||||
)
|
||||
if ("debit" in vals) ^ ("credit" in vals):
|
||||
raise UserError(_("Either pass both debit and credit or none."))
|
||||
if "date" not in vals:
|
||||
vals["date"] = self._context.get("date_p") or fields.Date.today()
|
||||
vals["date"] = fields.Date.to_date(vals["date"])
|
||||
if vals["date"] and vals["date"] < date:
|
||||
date = vals["date"]
|
||||
if "name" not in vals:
|
||||
vals["name"] = self._context.get("comment") or _("Write-Off")
|
||||
if "analytic_account_id" not in vals:
|
||||
vals["analytic_account_id"] = self.env.context.get(
|
||||
"analytic_id", False
|
||||
)
|
||||
# compute the writeoff amount if not given
|
||||
if "credit" not in vals and "debit" not in vals:
|
||||
amount = sum([r.amount_residual for r in self])
|
||||
vals["credit"] = amount > 0 and amount or 0.0
|
||||
vals["debit"] = amount < 0 and abs(amount) or 0.0
|
||||
vals["partner_id"] = partner_id
|
||||
total += vals["debit"] - vals["credit"]
|
||||
if (
|
||||
"amount_currency" not in vals
|
||||
and writeoff_currency != company_currency
|
||||
):
|
||||
vals["currency_id"] = writeoff_currency.id
|
||||
sign = 1 if vals["debit"] > 0 else -1
|
||||
vals["amount_currency"] = sign * abs(
|
||||
sum([r.amount_residual_currency for r in self])
|
||||
)
|
||||
total_currency += vals["amount_currency"]
|
||||
|
||||
writeoff_lines.append(compute_writeoff_counterpart_vals(vals))
|
||||
|
||||
# Create balance line
|
||||
writeoff_lines.append(
|
||||
{
|
||||
"name": _("Write-Off"),
|
||||
"debit": total > 0 and total or 0.0,
|
||||
"credit": total < 0 and -total or 0.0,
|
||||
"amount_currency": total_currency,
|
||||
"currency_id": total_currency and writeoff_currency.id or False,
|
||||
"journal_id": journal_id,
|
||||
"account_id": self[0].account_id.id,
|
||||
"partner_id": partner_id,
|
||||
}
|
||||
)
|
||||
|
||||
# Create the move
|
||||
writeoff_move = self.env["account.move"].create(
|
||||
{
|
||||
"journal_id": journal_id,
|
||||
"date": date,
|
||||
"state": "draft",
|
||||
"line_ids": [(0, 0, line) for line in writeoff_lines],
|
||||
}
|
||||
)
|
||||
writeoff_moves += writeoff_move
|
||||
line_to_reconcile += writeoff_move.line_ids.filtered(
|
||||
lambda r: r.account_id == self[0].account_id
|
||||
).sorted(key="id")[-1:]
|
||||
|
||||
# post all the writeoff moves at once
|
||||
if writeoff_moves:
|
||||
writeoff_moves.action_post()
|
||||
|
||||
# Return the writeoff move.line which is to be reconciled
|
||||
return line_to_reconcile
|
||||
1165
account_reconciliation_widget/models/reconciliation_widget.py
Normal file
1165
account_reconciliation_widget/models/reconciliation_widget.py
Normal file
File diff suppressed because it is too large
Load Diff
14
account_reconciliation_widget/models/res_company.py
Normal file
14
account_reconciliation_widget/models/res_company.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
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.\n"
|
||||
"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.",
|
||||
)
|
||||
1
account_reconciliation_widget/readme/CONTRIBUTORS.rst
Normal file
1
account_reconciliation_widget/readme/CONTRIBUTORS.rst
Normal file
@@ -0,0 +1 @@
|
||||
* Tecnativa - Pedro M. Baeza
|
||||
2
account_reconciliation_widget/readme/DESCRIPTION.rst
Normal file
2
account_reconciliation_widget/readme/DESCRIPTION.rst
Normal file
@@ -0,0 +1,2 @@
|
||||
This module restores account reconciliation widget moved from Odoo community to enterpise in V. 14.0
|
||||
Provides two widgets designed to reconcile move lines in a easy way: one focused on bank statements and another for generic use.
|
||||
9
account_reconciliation_widget/readme/USAGE.rst
Normal file
9
account_reconciliation_widget/readme/USAGE.rst
Normal file
@@ -0,0 +1,9 @@
|
||||
With an user with full accounting features enabled:
|
||||
|
||||
Invoicing --> Accounting --> Actions --> Reconciliation.
|
||||
|
||||
From journal items list view you can select check of them and click Action --> Reconcile.
|
||||
|
||||
From accounting dashboard you can use reconcile button in Bank / Cash journals.
|
||||
|
||||
Also, you can navigate to statements and use the reconcile button.
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_reconciliation_widget_group_invoice,account_reconciliation_widget.group_invoice,model_account_reconciliation_widget,account.group_account_invoice,1,1,1,1
|
||||
|
429
account_reconciliation_widget/static/description/index.html
Normal file
429
account_reconciliation_widget/static/description/index.html
Normal file
@@ -0,0 +1,429 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
|
||||
<title>account_reconciliation_widget</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller }
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title, .code .error {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top }
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle }
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { color: grey; } /* line numbers */
|
||||
pre.code, code { background-color: #eeeeee }
|
||||
pre.code .comment, code .comment { color: #5C6576 }
|
||||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="account-reconciliation-widget">
|
||||
<h1 class="title">account_reconciliation_widget</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/account_reconciliation_widget/tree/14.0/account_reconciliation_widget"><img alt="OCA/account_reconciliation_widget" src="https://img.shields.io/badge/github-OCA%2Faccount_reconciliation_widget-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/account_reconciliation_widget-14-0/account_reconciliation_widget-14-0-account_reconciliation_widget"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a></p>
|
||||
<p>This module restores account reconciliation widget moved from Odoo community to enterpise in V. 14.0
|
||||
Provides two widgets designed to reconcile move lines in a easy way: one focused on bank statements and another for generic use.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#id1">Usage</a></h1>
|
||||
<p>With an user with full accounting features enabled:</p>
|
||||
<p>Invoicing –> Accounting –> Actions –> Reconciliation.</p>
|
||||
<p>From journal items list view you can select check of them and click Action –> Reconcile.</p>
|
||||
<p>From accounting dashboard you can use reconcile button in Bank / Cash journals.</p>
|
||||
<p>Also, you can navigate to statements and use the reconcile button.</p>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account_reconciliation_widget/issues">GitHub Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||
<a class="reference external" href="https://github.com/OCA/account_reconciliation_widget/issues/new?body=module:%20account_reconciliation_widget%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#id3">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id4">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Ozono Multimedia</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id5">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Tecnativa - Pedro M. Baeza</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.</p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account_reconciliation_widget/tree/14.0/account_reconciliation_widget">OCA/account_reconciliation_widget</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,544 @@
|
||||
odoo.define("account.ReconciliationClientAction", function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractAction = require("web.AbstractAction");
|
||||
var ReconciliationModel = require("account.ReconciliationModel");
|
||||
var ReconciliationRenderer = require("account.ReconciliationRenderer");
|
||||
var core = require("web.core");
|
||||
var QWeb = core.qweb;
|
||||
|
||||
/**
|
||||
* Widget used as action for 'account.bank.statement' reconciliation
|
||||
*/
|
||||
var StatementAction = AbstractAction.extend({
|
||||
hasControlPanel: true,
|
||||
withSearchBar: true,
|
||||
loadControlPanel: true,
|
||||
title: core._t("Bank Reconciliation"),
|
||||
contentTemplate: "reconciliation",
|
||||
custom_events: {
|
||||
change_mode: "_onAction",
|
||||
change_filter: "_onAction",
|
||||
change_offset: "_onAction",
|
||||
change_partner: "_onAction",
|
||||
add_proposition: "_onAction",
|
||||
remove_proposition: "_onAction",
|
||||
update_proposition: "_onAction",
|
||||
create_proposition: "_onAction",
|
||||
getPartialAmount: "_onActionPartialAmount",
|
||||
quick_create_proposition: "_onAction",
|
||||
partial_reconcile: "_onAction",
|
||||
validate: "_onValidate",
|
||||
close_statement: "_onCloseStatement",
|
||||
load_more: "_onLoadMore",
|
||||
reload: "reload",
|
||||
navigation_move: "_onNavigationMove",
|
||||
},
|
||||
config: _.extend({}, AbstractAction.prototype.config, {
|
||||
// Used to instantiate the model
|
||||
Model: ReconciliationModel.StatementModel,
|
||||
// Used to instantiate the action interface
|
||||
ActionRenderer: ReconciliationRenderer.StatementRenderer,
|
||||
// Used to instantiate each widget line
|
||||
LineRenderer: ReconciliationRenderer.LineRenderer,
|
||||
// Used context params
|
||||
params: ["statement_line_ids"],
|
||||
// Number of statements/partners/accounts to display
|
||||
defaultDisplayQty: 10,
|
||||
// Number of moves lines displayed in 'match' mode
|
||||
limitMoveLines: 15,
|
||||
}),
|
||||
|
||||
_onNavigationMove: function (ev) {
|
||||
var non_reconciled_keys = _.keys(
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
_.pick(this.model.lines, function (value, key, object) {
|
||||
return !value.reconciled;
|
||||
})
|
||||
);
|
||||
var currentIndex = _.indexOf(non_reconciled_keys, ev.data.handle);
|
||||
var widget = false;
|
||||
switch (ev.data.direction) {
|
||||
case "up":
|
||||
ev.stopPropagation();
|
||||
widget = this._getWidget(non_reconciled_keys[currentIndex - 1]);
|
||||
break;
|
||||
case "down":
|
||||
ev.stopPropagation();
|
||||
widget = this._getWidget(non_reconciled_keys[currentIndex + 1]);
|
||||
break;
|
||||
case "validate":
|
||||
ev.stopPropagation();
|
||||
widget = this._getWidget(non_reconciled_keys[currentIndex]);
|
||||
widget.$("caption .o_buttons button:visible").click();
|
||||
break;
|
||||
}
|
||||
if (widget) widget.$el.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Object} params
|
||||
* @param {Object} params.context
|
||||
*
|
||||
*/
|
||||
init: function (parent, params) {
|
||||
this._super.apply(this, arguments);
|
||||
this.action_manager = parent;
|
||||
this.params = params;
|
||||
this.searchModelConfig.modelName = "account.bank.statement.line";
|
||||
this.controlPanelProps.cp_content = {};
|
||||
this.model = new this.config.Model(this, {
|
||||
modelName: "account.reconciliation.widget",
|
||||
defaultDisplayQty:
|
||||
(params.params && params.params.defaultDisplayQty) ||
|
||||
this.config.defaultDisplayQty,
|
||||
limitMoveLines:
|
||||
(params.params && params.params.limitMoveLines) ||
|
||||
this.config.limitMoveLines,
|
||||
});
|
||||
this.widgets = [];
|
||||
// Adding values from the context is necessary to put this information in the url via the action manager so that
|
||||
// you can retrieve it if the person shares his url or presses f5
|
||||
_.each(params.params, function (value, name) {
|
||||
params.context[name] =
|
||||
name.indexOf("_ids") !== -1
|
||||
? _.map(String(value).split(","), parseFloat)
|
||||
: value;
|
||||
});
|
||||
params.params = {};
|
||||
_.each(this.config.params, function (name) {
|
||||
if (params.context[name]) {
|
||||
params.params[name] = params.context[name];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Instantiate the action renderer
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
willStart: function () {
|
||||
var self = this;
|
||||
var def = this.model.load(this.params.context).then(this._super.bind(this));
|
||||
return def.then(function () {
|
||||
if (!self.model.context || !self.model.context.active_id) {
|
||||
self.model.context = {
|
||||
active_id: self.params.context.active_id,
|
||||
active_model: self.params.context.active_model,
|
||||
};
|
||||
}
|
||||
var journal_id = self.params.context.journal_id;
|
||||
if (
|
||||
self.model.context.active_id &&
|
||||
self.model.context.active_model === "account.journal"
|
||||
) {
|
||||
journal_id = journal_id || self.model.context.active_id;
|
||||
}
|
||||
if (journal_id) {
|
||||
var promise = self._rpc({
|
||||
model: "account.journal",
|
||||
method: "read",
|
||||
args: [journal_id, ["display_name"]],
|
||||
});
|
||||
} else {
|
||||
var promise = Promise.resolve();
|
||||
}
|
||||
return promise.then(function (result) {
|
||||
var title =
|
||||
result && result[0]
|
||||
? result[0].display_name
|
||||
: self.params.display_name || "";
|
||||
self._setTitle(title);
|
||||
self.renderer = new self.config.ActionRenderer(self, self.model, {
|
||||
bank_statement_line_id: self.model.bank_statement_line_id,
|
||||
valuenow: self.model.valuenow,
|
||||
valuemax: self.model.valuemax,
|
||||
defaultDisplayQty: self.model.defaultDisplayQty,
|
||||
title: title,
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
reload: function () {
|
||||
// On reload destroy all rendered line widget, reload data and then rerender widget
|
||||
var self = this;
|
||||
|
||||
self.$(".o_reconciliation_lines").addClass("d-none"); // Prevent the browser from recomputing css after each destroy for HUGE perf improvement on a lot of lines
|
||||
_.each(this.widgets, function (widget) {
|
||||
widget.destroy();
|
||||
});
|
||||
this.widgets = [];
|
||||
self.$(".o_reconciliation_lines").removeClass("d-none");
|
||||
return this.model.reload().then(function () {
|
||||
return self._renderLinesOrRainbow();
|
||||
});
|
||||
},
|
||||
|
||||
_renderLinesOrRainbow: function () {
|
||||
var self = this;
|
||||
return self._renderLines().then(function () {
|
||||
var initialState = self.renderer._initialState;
|
||||
var valuenow = self.model.statement
|
||||
? self.model.statement.value_min
|
||||
: initialState.valuenow;
|
||||
var valuemax = self.model.statement
|
||||
? self.model.statement.value_max
|
||||
: initialState.valuemax;
|
||||
// No more lines to reconcile, trigger the rainbowman.
|
||||
if (valuenow === valuemax) {
|
||||
initialState.valuenow = valuenow;
|
||||
initialState.context = self.model.getContext();
|
||||
self.renderer.showRainbowMan(initialState);
|
||||
self.controlPanelProps.cp_content = {
|
||||
$buttons: $(),
|
||||
$pager: $(),
|
||||
};
|
||||
} else {
|
||||
// Create a notification if some lines have been reconciled automatically.
|
||||
if (initialState.valuenow > 0)
|
||||
self.renderer._renderNotifications(
|
||||
self.model.statement.notifications
|
||||
);
|
||||
self._openFirstLine();
|
||||
self.renderer.$('[data-toggle="tooltip"]').tooltip();
|
||||
self.do_show();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Append the renderer and instantiate the line renderers
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
var args = arguments;
|
||||
var sup = this._super;
|
||||
|
||||
return this.renderer.prependTo(self.$(".o_form_sheet")).then(function () {
|
||||
return self._renderLinesOrRainbow().then(function () {
|
||||
self.do_show();
|
||||
return sup.apply(self, args);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the control panel and breadcrumbs
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
do_show: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.action_manager) {
|
||||
this.$pager = $(
|
||||
QWeb.render("reconciliation.control.pager", {widget: this.renderer})
|
||||
);
|
||||
|
||||
this.controlPanelProps.cp_content = {
|
||||
$buttons: $(),
|
||||
$pager: this.$pager,
|
||||
};
|
||||
this.renderer.$progress = this.$pager;
|
||||
$(this.renderer.$progress)
|
||||
.parent()
|
||||
.css("width", "100%")
|
||||
.css("padding-left", "0");
|
||||
}
|
||||
},
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Private
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {String} handle
|
||||
* @returns {Widget} widget line
|
||||
*/
|
||||
_getWidget: function (handle) {
|
||||
return _.find(this.widgets, function (widget) {
|
||||
return widget.handle === handle;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_loadMore: function (qty) {
|
||||
var self = this;
|
||||
return this.model.loadMore(qty).then(function () {
|
||||
return self._renderLines();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Sitch to 'match' the first available line
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_openFirstLine: function (previous_handle) {
|
||||
var self = this;
|
||||
previous_handle = previous_handle || "rline0";
|
||||
var handle = _.compact(
|
||||
_.map(this.model.lines, function (line, handle) {
|
||||
return line.reconciled ||
|
||||
parseInt(handle.substr(5)) < parseInt(previous_handle.substr(5))
|
||||
? null
|
||||
: handle;
|
||||
})
|
||||
)[0];
|
||||
if (handle) {
|
||||
var line = this.model.getLine(handle);
|
||||
this.model
|
||||
.changeMode(handle, "default")
|
||||
.then(function () {
|
||||
self._getWidget(handle).update(line);
|
||||
})
|
||||
.guardedCatch(function () {
|
||||
self._getWidget(handle).update(line);
|
||||
})
|
||||
.then(function () {
|
||||
self._getWidget(handle).$el.focus();
|
||||
});
|
||||
}
|
||||
return handle;
|
||||
},
|
||||
|
||||
_forceUpdate: function () {
|
||||
var self = this;
|
||||
_.each(this.model.lines, function (handle) {
|
||||
var widget = self._getWidget(handle.handle);
|
||||
if (widget && handle.need_update) {
|
||||
widget.update(handle);
|
||||
widget.need_update = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Render line widget and append to view
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_renderLines: function () {
|
||||
var self = this;
|
||||
var linesToDisplay = this.model.getStatementLines();
|
||||
var linePromises = [];
|
||||
_.each(linesToDisplay, function (line, handle) {
|
||||
var widget = new self.config.LineRenderer(self, self.model, line);
|
||||
widget.handle = handle;
|
||||
self.widgets.push(widget);
|
||||
linePromises.push(widget.appendTo(self.$(".o_reconciliation_lines")));
|
||||
});
|
||||
if (this.model.hasMoreLines() === false) {
|
||||
this.renderer.hideLoadMoreButton(true);
|
||||
} else {
|
||||
this.renderer.hideLoadMoreButton(false);
|
||||
}
|
||||
return Promise.all(linePromises);
|
||||
},
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* dispatch on the camelcased event name to model method then update the
|
||||
* line renderer with the new state. If the mode was switched from 'inactive'
|
||||
* to 'create' or 'match_rp' or 'match_other', the other lines switch to
|
||||
* 'inactive' mode
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onAction: function (event) {
|
||||
var self = this;
|
||||
var handle = event.target.handle;
|
||||
var current_line = this.model.getLine(handle);
|
||||
this.model[_.str.camelize(event.name)](handle, event.data.data).then(
|
||||
function () {
|
||||
var widget = self._getWidget(handle);
|
||||
if (widget) {
|
||||
widget.update(current_line);
|
||||
}
|
||||
if (current_line.mode !== "inactive") {
|
||||
_.each(self.model.lines, function (line, _handle) {
|
||||
if (line.mode !== "inactive" && _handle !== handle) {
|
||||
self.model.changeMode(_handle, "inactive");
|
||||
var widget = self._getWidget(_handle);
|
||||
if (widget) {
|
||||
widget.update(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} searchQuery
|
||||
*/
|
||||
_onSearch: function (searchQuery) {
|
||||
var self = this;
|
||||
this.model.domain = searchQuery.domain;
|
||||
this.model.display_context = "search";
|
||||
self.reload().then(function () {
|
||||
self.renderer._updateProgressBar({
|
||||
valuenow: self.model.valuenow,
|
||||
valuemax: self.model.valuemax,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onActionPartialAmount: function (event) {
|
||||
var self = this;
|
||||
var handle = event.target.handle;
|
||||
var amount = this.model.getPartialReconcileAmount(handle, event.data);
|
||||
self._getWidget(handle).updatePartialAmount(event.data.data, amount);
|
||||
},
|
||||
|
||||
/**
|
||||
* Call 'closeStatement' model method
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
_onCloseStatement: function (event) {
|
||||
var self = this;
|
||||
return this.model.closeStatement().then(function (result) {
|
||||
self.do_action({
|
||||
name: "Bank Statements",
|
||||
res_model: "account.bank.statement",
|
||||
res_id: result,
|
||||
views: [[false, "form"]],
|
||||
type: "ir.actions.act_window",
|
||||
view_mode: "form",
|
||||
});
|
||||
$(".o_reward").remove();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Load more statement and render them
|
||||
*
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
_onLoadMore: function (event) {
|
||||
return this._loadMore(this.model.defaultDisplayQty);
|
||||
},
|
||||
/**
|
||||
* Call 'validate' model method then destroy the
|
||||
* validated lines and update the action renderer with the new status bar
|
||||
* values and notifications then open the first available line
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onValidate: function (event) {
|
||||
var self = this;
|
||||
var handle = event.target.handle;
|
||||
this.model.validate(handle).then(function (result) {
|
||||
self.renderer.update({
|
||||
valuenow: self.model.valuenow,
|
||||
valuemax: self.model.valuemax,
|
||||
title: self.title,
|
||||
time: Date.now() - self.time,
|
||||
notifications: result.notifications,
|
||||
context: self.model.getContext(),
|
||||
});
|
||||
self._forceUpdate();
|
||||
_.each(result.handles, function (handle) {
|
||||
var widget = self._getWidget(handle);
|
||||
if (widget) {
|
||||
widget.destroy();
|
||||
var index = _.findIndex(self.widgets, function (widget) {
|
||||
return widget.handle === handle;
|
||||
});
|
||||
self.widgets.splice(index, 1);
|
||||
}
|
||||
});
|
||||
// Get number of widget and if less than constant and if there are more to load, load until constant
|
||||
if (
|
||||
self.widgets.length < self.model.defaultDisplayQty &&
|
||||
self.model.valuemax - self.model.valuenow >=
|
||||
self.model.defaultDisplayQty
|
||||
) {
|
||||
var toLoad = self.model.defaultDisplayQty - self.widgets.length;
|
||||
self._loadMore(toLoad);
|
||||
}
|
||||
self._openFirstLine(handle);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Widget used as action for 'account.move.line' and 'res.partner' for the
|
||||
* manual reconciliation and mark data as reconciliate
|
||||
*/
|
||||
var ManualAction = StatementAction.extend({
|
||||
title: core._t("Journal Items to Reconcile"),
|
||||
withSearchBar: false,
|
||||
config: _.extend({}, StatementAction.prototype.config, {
|
||||
Model: ReconciliationModel.ManualModel,
|
||||
ActionRenderer: ReconciliationRenderer.ManualRenderer,
|
||||
LineRenderer: ReconciliationRenderer.ManualLineRenderer,
|
||||
params: ["company_ids", "mode", "partner_ids", "account_ids"],
|
||||
defaultDisplayQty: 30,
|
||||
limitMoveLines: 15,
|
||||
}),
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Handlers
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* call 'validate' model method then destroy the
|
||||
* reconcilied lines, update the not reconcilied and update the action
|
||||
* renderer with the new status bar values and notifications then open the
|
||||
* first available line
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} event
|
||||
*/
|
||||
_onValidate: function (event) {
|
||||
var self = this;
|
||||
var handle = event.target.handle;
|
||||
var method = "validate";
|
||||
this.model[method](handle).then(function (result) {
|
||||
_.each(result.reconciled, function (handle) {
|
||||
self._getWidget(handle).destroy();
|
||||
});
|
||||
_.each(result.updated, function (handle) {
|
||||
self._getWidget(handle).update(self.model.getLine(handle));
|
||||
});
|
||||
self.renderer.update({
|
||||
valuenow: _.compact(_.invoke(self.widgets, "isDestroyed")).length,
|
||||
valuemax: self.widgets.length,
|
||||
title: self.title,
|
||||
time: Date.now() - self.time,
|
||||
});
|
||||
if (
|
||||
!_.any(result.updated, function (handle) {
|
||||
return self.model.getLine(handle).mode !== "inactive";
|
||||
})
|
||||
) {
|
||||
self._openFirstLine(handle);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
core.action_registry.add("bank_statement_reconciliation_view", StatementAction);
|
||||
core.action_registry.add("manual_reconciliation_view", ManualAction);
|
||||
|
||||
return {
|
||||
StatementAction: StatementAction,
|
||||
ManualAction: ManualAction,
|
||||
};
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,382 @@
|
||||
.progress-reconciliation {
|
||||
.progress-bar {
|
||||
font-size: 1.08333333rem;
|
||||
height: 14px;
|
||||
background-color: $o-enterprise-color;
|
||||
span {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_reconciliation {
|
||||
.o_filter_input_wrapper {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
margin: 0.5rem !important;
|
||||
.searchIcon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
.o_filter_input {
|
||||
border: none;
|
||||
border-bottom: 1px black solid;
|
||||
}
|
||||
}
|
||||
|
||||
.import_to_suspense {
|
||||
margin: 0.5rem !important;
|
||||
}
|
||||
|
||||
.notification_area {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.o_view_noreconciliation {
|
||||
max-width: none;
|
||||
padding: 0 10%;
|
||||
color: $o-main-color-muted;
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
.accounting_view {
|
||||
width: 100%;
|
||||
|
||||
.cell_left {
|
||||
border-right: 1px solid #333;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.edit_amount {
|
||||
margin-left: 20px;
|
||||
color: #bbb;
|
||||
}
|
||||
.cell:hover .edit_amount {
|
||||
color: #00a09d;
|
||||
}
|
||||
.strike_amount {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
tbody tr:hover .cell_account_code::before {
|
||||
content: "\f068";
|
||||
font-family: FontAwesome;
|
||||
position: relative;
|
||||
margin-left: -17px;
|
||||
left: -4px;
|
||||
line-height: 0;
|
||||
padding: 3px 2px 5px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_multi_currency {
|
||||
margin-right: 5px;
|
||||
&.o_multi_currency_color_0 {
|
||||
color: #dd6666;
|
||||
}
|
||||
&.o_multi_currency_color_1 {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
&.o_multi_currency_color_2 {
|
||||
color: #66dd66;
|
||||
}
|
||||
&.o_multi_currency_color_3 {
|
||||
color: #6666dd;
|
||||
}
|
||||
&.o_multi_currency_color_4 {
|
||||
color: #dddd66;
|
||||
}
|
||||
&.o_multi_currency_color_5 {
|
||||
color: #dd66dd;
|
||||
}
|
||||
&.o_multi_currency_color_6 {
|
||||
color: #66dddd;
|
||||
}
|
||||
&.o_multi_currency_color_7 {
|
||||
color: #aaa333;
|
||||
}
|
||||
}
|
||||
|
||||
.o_reconciliation_line {
|
||||
margin-bottom: 30px;
|
||||
table {
|
||||
width: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
tr.already_reconciled {
|
||||
color: $o-account-info-color;
|
||||
}
|
||||
tr.invalid {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
td {
|
||||
padding: 1px 2px;
|
||||
}
|
||||
thead td {
|
||||
border-top: $o-account-light-border;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 5px;
|
||||
background-color: $o-account-initial-line-background;
|
||||
}
|
||||
tfoot td {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
/* columns */
|
||||
|
||||
.cell_action {
|
||||
width: 15px;
|
||||
color: gray("700");
|
||||
background: #fff;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
.fa-add-remove:before {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
tr:hover .cell_action .fa-add-remove:before {
|
||||
content: "\f068";
|
||||
}
|
||||
.is_tax .cell_action .fa-add-remove:before {
|
||||
position: relative;
|
||||
top: -18px;
|
||||
}
|
||||
.cell_account_code {
|
||||
width: 80px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.cell_due_date {
|
||||
width: 100px;
|
||||
}
|
||||
.cell_label {
|
||||
width: auto;
|
||||
}
|
||||
.cell_left {
|
||||
padding-right: 5px;
|
||||
}
|
||||
.cell_right,
|
||||
.cell_left {
|
||||
text-align: right;
|
||||
width: 120px;
|
||||
}
|
||||
.cell_info_popover {
|
||||
text-align: right;
|
||||
width: 15px;
|
||||
color: #ccc;
|
||||
|
||||
&:empty {
|
||||
padding: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
table.accounting_view {
|
||||
.cell_right,
|
||||
.cell_left,
|
||||
.cell_label,
|
||||
.cell_due_date,
|
||||
.cell_account_code,
|
||||
.cell_info_popover {
|
||||
box-shadow: 0 1px 0 #eaeaea;
|
||||
}
|
||||
}
|
||||
/* info popover */
|
||||
.popover {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
table.details {
|
||||
vertical-align: top;
|
||||
td:first-child {
|
||||
vertical-align: top;
|
||||
padding-right: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
tr.one_line_info {
|
||||
td {
|
||||
padding-top: 10px;
|
||||
text-align: center;
|
||||
color: $o-account-info-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
|
||||
.toggle_match,
|
||||
.toggle_create {
|
||||
transform: rotate(0deg);
|
||||
transition: transform 300ms ease 0s;
|
||||
}
|
||||
.visible_toggle,
|
||||
&[data-mode="match"] .toggle_match,
|
||||
&[data-mode="create"] .toggle_create {
|
||||
visibility: visible !important;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.toggle_create {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Match view & Create view */
|
||||
> .o_notebook {
|
||||
display: none;
|
||||
|
||||
> .o_notebook_headers {
|
||||
margin-right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .o_notebook > .tab-content > div {
|
||||
border: 1px solid #ddd;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
> .o_notebook .match table tr:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&:not([data-mode="inactive"]) > .o_notebook {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:not(:focus-within) .o_web_accesskey_overlay {
|
||||
display: none;
|
||||
}
|
||||
&:focus caption .o_buttons button {
|
||||
outline: none;
|
||||
box-shadow: 4px 4px 4px 0px $o-enterprise-color;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_reconcile_models .btn-primary {
|
||||
margin: 0 2px 3px 0;
|
||||
}
|
||||
|
||||
/* Match view */
|
||||
|
||||
.match {
|
||||
.cell_action .fa-add-remove:before {
|
||||
content: "";
|
||||
}
|
||||
tr:hover .cell_action .fa-add-remove:before {
|
||||
content: "\f067";
|
||||
}
|
||||
.match_controls {
|
||||
padding: 5px 0 5px
|
||||
($o-account-action-col-width + $o-account-main-table-borders-padding);
|
||||
|
||||
.filter {
|
||||
width: 240px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.fa-chevron-left,
|
||||
.fa-chevron-right {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fa-chevron-left {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.fa-chevron-left.disabled,
|
||||
.fa-chevron-right.disabled {
|
||||
color: #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
.show_more {
|
||||
display: inline-block;
|
||||
margin-left: (
|
||||
$o-account-action-col-width + $o-account-main-table-borders-padding
|
||||
);
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create view */
|
||||
.create {
|
||||
> div > div.quick_add > .o_reconcile_models {
|
||||
max-width: 100%;
|
||||
max-height: 70px;
|
||||
flex-wrap: wrap;
|
||||
overflow: auto;
|
||||
|
||||
& > * {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
.quick_add {
|
||||
margin-bottom: 7px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.o_group table.o_group_col_6 {
|
||||
width: 49%;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.o_group table.o_group_col_6:first-child {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.btn {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.add_line_container {
|
||||
text-align: center;
|
||||
clear: both;
|
||||
color: $o-enterprise-primary-color;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.o_notebook .tab-content > .tab-pane {
|
||||
padding: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*Manual Reconciliation*/
|
||||
.o_manual_statement {
|
||||
.accounting_view {
|
||||
td[colspan="3"] span:first-child {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
td[colspan="2"] {
|
||||
border-bottom: 1px solid #333;
|
||||
text-align: center;
|
||||
width: 240px;
|
||||
}
|
||||
.do_partial_reconcile_true {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is rtl language specific fix
|
||||
// It will flip the fa-fa play icon in left direction
|
||||
.o_rtl {
|
||||
.o_reconciliation {
|
||||
.o_reconciliation_line {
|
||||
.toggle_match,
|
||||
.toggle_create {
|
||||
transform: rotate(180deg);
|
||||
transition: transform 300ms;
|
||||
}
|
||||
.visible_toggle,
|
||||
&[data-mode="match"] .toggle_match,
|
||||
&[data-mode="create"] .toggle_create {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<div t-name="reconciliation" class="o_reconciliation">
|
||||
<div class="o_form_view">
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_sheet" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t t-name="reconciliation.control.pager">
|
||||
<div class="progress progress-reconciliation">
|
||||
<div
|
||||
aria-valuemin="0"
|
||||
t-att-aria-valuenow="widget._initialState.valuenow"
|
||||
t-att-aria-valuemax="widget._initialState.valuemax"
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
style="width: 0%;"
|
||||
><span class="valuenow"><t
|
||||
t-esc="widget._initialState.valuenow"
|
||||
/></span> / <span class="valuemax"><t
|
||||
t-esc="widget._initialState.valuemax"
|
||||
/></span></div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="reconciliation.statement">
|
||||
<div t-if="widget._initialState.valuemax">
|
||||
<div class="notification_area" />
|
||||
<div class="o_reconciliation_lines" />
|
||||
<div
|
||||
t-if="widget._initialState.valuemax > widget._initialState.defaultDisplayQty"
|
||||
>
|
||||
<button class="btn btn-secondary js_load_more">Load more</button>
|
||||
</div>
|
||||
</div>
|
||||
<div t-else="" class="o_view_noreconciliation">
|
||||
<p>Nothing to do!</p>
|
||||
<p
|
||||
>This page displays all the bank transactions that are to be reconciled and provides with a neat interface to do so.</p>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="reconciliation.manual.statement" t-extend="reconciliation.statement">
|
||||
<t t-jquery="div:first" t-operation="attributes">
|
||||
<attribute name="class" value="o_manual_statement" />
|
||||
</t>
|
||||
<t t-jquery=".o_view_noreconciliation p" t-operation="replace" />
|
||||
<t t-jquery=".o_filter_input_wrapper" t-operation="replace" />
|
||||
<t t-jquery=".o_view_noreconciliation" t-operation="append">
|
||||
<p><b>Good Job!</b> There is nothing to reconcile.</p>
|
||||
<p
|
||||
>All invoices and payments have been matched, your accounts' balances are clean.</p>
|
||||
<p>
|
||||
From now on, you may want to:
|
||||
<ul>
|
||||
<li>Check that you have no bank statement lines to <a
|
||||
href="#"
|
||||
rel="do_action"
|
||||
data-tag="bank_statement_reconciliation_view"
|
||||
>reconcile</a></li>
|
||||
<li>Verify <a
|
||||
href="#"
|
||||
rel="do_action"
|
||||
data-action_name="Unpaid Customer Invoices"
|
||||
data-model="account.move"
|
||||
data-domain="[('move_type', 'in', ('out_invoice', 'out_refund'))]"
|
||||
data-context="{'search_default_unpaid': 1}"
|
||||
>unpaid invoices</a> and follow-up customers</li>
|
||||
<li>Pay your <a
|
||||
href="#"
|
||||
rel="do_action"
|
||||
data-action_name="Unpaid Vendor Bills"
|
||||
data-model="account.move"
|
||||
data-domain="[('move_type', 'in', ('in_invoice', 'in_refund'))]"
|
||||
data-context="{'search_default_unpaid': 1}"
|
||||
>vendor bills</a></li>
|
||||
<li>Check all <a
|
||||
href="#"
|
||||
rel="do_action"
|
||||
data-action_name="Unreconciled Entries"
|
||||
data-model="account.move.line"
|
||||
data-context="{'search_default_unreconciled': 1}"
|
||||
>unreconciled entries</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<div t-name="reconciliation.done" class="done_message">
|
||||
<h2>Congrats, you're all done!</h2>
|
||||
<p>You reconciled <strong><t t-esc="number" /></strong> transactions in <strong><t
|
||||
t-esc="duration"
|
||||
/></strong>.
|
||||
<t t-if="number > 1">
|
||||
<br />That's on average <t
|
||||
t-esc="timePerTransaction"
|
||||
/> seconds per transaction.
|
||||
</t>
|
||||
</p>
|
||||
<t t-if="context && context.active_model">
|
||||
<p
|
||||
t-if="context['active_model'] === 'account.journal' || context['active_model'] === 'account.bank.statement' || context['active_model'] === 'account.bank.statement.import'"
|
||||
class="actions_buttons"
|
||||
>
|
||||
<t t-if="context.journal_id">
|
||||
<button
|
||||
class="button_back_to_statement btn btn-secondary"
|
||||
t-att-data_journal_id='context.journal_id'
|
||||
>Go to bank statement(s)</button>
|
||||
</t>
|
||||
<t t-if="context['active_model'] === 'account.bank.statement'">
|
||||
<button
|
||||
class="button_close_statement btn btn-primary"
|
||||
style="display: inline-block;"
|
||||
>Close statement</button>
|
||||
</t>
|
||||
</p>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<t t-name="reconciliation.line">
|
||||
<t t-set="state" t-value="widget._initialState" />
|
||||
<div class="o_reconciliation_line" t-att-data-mode="state.mode" tabindex="0">
|
||||
<table class="accounting_view">
|
||||
<caption style="caption-side: top;">
|
||||
<div class="float-right o_buttons">
|
||||
<button
|
||||
t-attf-class="o_no_valid btn btn-secondary #{state.balance.type < 0 ? '' : 'd-none'}"
|
||||
disabled="disabled"
|
||||
data-toggle="tooltip"
|
||||
title="Select a partner or choose a counterpart"
|
||||
accesskey=""
|
||||
>Validate</button>
|
||||
<button
|
||||
t-attf-class="o_validate btn btn-secondary #{!state.balance.type ? '' : 'd-none'}"
|
||||
>Validate</button>
|
||||
<button
|
||||
t-attf-class="o_reconcile btn btn-primary #{state.balance.type > 0 ? '' : 'd-none'}"
|
||||
>Validate</button>
|
||||
</div>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="cell_account_code"><t
|
||||
t-esc="state.st_line.account_code"
|
||||
/></td>
|
||||
<td class="cell_due_date"><t t-esc="state.st_line.date" /></td>
|
||||
<td class="cell_label"><t
|
||||
t-if="state.st_line.name"
|
||||
t-esc="state.st_line.name"
|
||||
/> <t t-if="state.st_line.amount_currency_str"> (<t
|
||||
t-esc="state.st_line.amount_currency_str"
|
||||
/>)</t></td>
|
||||
<td class="cell_left"><t t-if="state.st_line.amount > 0"><t
|
||||
t-raw="state.st_line.amount_str"
|
||||
/></t></td>
|
||||
<td class="cell_right"><t t-if="state.st_line.amount < 0"><t
|
||||
t-raw="state.st_line.amount_str"
|
||||
/></t></td>
|
||||
<td class="cell_info_popover" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="state.reconciliation_proposition" t-as="line"><t
|
||||
t-call="reconciliation.line.mv_line"
|
||||
/></t>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<t t-call="reconciliation.line.balance" />
|
||||
</tfoot>
|
||||
</table>
|
||||
<div class="o_notebook">
|
||||
<div class="o_notebook_headers">
|
||||
<ul class="nav nav-tabs ml-0 mr-0">
|
||||
<li
|
||||
class="nav-item"
|
||||
t-attf-title="{{'Match statement with existing lines on receivable/payable accounts<br>* Black line: existing journal entry that should be matched<br>* Blue lines: existing payment that should be matched'}}"
|
||||
data-toggle="tooltip"
|
||||
><a
|
||||
data-toggle="tab"
|
||||
disable_anchor="true"
|
||||
t-attf-href="#notebook_page_match_rp_#{state.st_line.id}"
|
||||
class="nav-link active nav-match_rp"
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
>Customer/Vendor Matching</a></li>
|
||||
<li
|
||||
class="nav-item"
|
||||
title="Match with entries that are not from receivable/payable accounts"
|
||||
data-toggle="tooltip"
|
||||
><a
|
||||
data-toggle="tab"
|
||||
disable_anchor="true"
|
||||
t-attf-href="#notebook_page_match_other_#{state.st_line.id}"
|
||||
class="nav-link nav-match_other"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
>Miscellaneous Matching</a></li>
|
||||
<li
|
||||
class="nav-item"
|
||||
title="Create a counterpart"
|
||||
data-toggle="tooltip"
|
||||
><a
|
||||
data-toggle="tab"
|
||||
disable_anchor="true"
|
||||
t-attf-href="#notebook_page_create_#{state.st_line.id}"
|
||||
class="nav-link nav-create"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
>Manual Operations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div
|
||||
class="tab-pane active"
|
||||
t-attf-id="notebook_page_match_rp_#{state.st_line.id}"
|
||||
>
|
||||
<div class="match">
|
||||
<t t-call="reconciliation.line.match" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
t-attf-id="notebook_page_match_other_#{state.st_line.id}"
|
||||
>
|
||||
<div class="match">
|
||||
<t t-call="reconciliation.line.match" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
t-attf-id="notebook_page_create_#{state.st_line.id}"
|
||||
>
|
||||
<div class="create" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="reconciliation.manual.line" t-extend="reconciliation.line">
|
||||
<t t-jquery=".o_buttons" t-operation="replace">
|
||||
<div class="float-right o_buttons">
|
||||
<button
|
||||
t-attf-class="o_validate btn btn-secondary #{!state.balance.type ? '' : 'd-none'}"
|
||||
>Reconcile</button>
|
||||
<button
|
||||
t-attf-class="o_reconcile btn btn-primary #{state.balance.type > 0 ? '' : 'd-none'}"
|
||||
>Reconcile</button>
|
||||
<button
|
||||
t-attf-class="o_no_valid btn btn-secondary #{state.balance.type < 0 ? '' : 'd-none'}"
|
||||
>Skip</button>
|
||||
</div>
|
||||
</t>
|
||||
<t t-jquery=".accounting_view tbody" t-operation="append">
|
||||
<t t-if='!_.filter(state.reconciliation_proposition, {"display": true}).length'>
|
||||
<t t-set="line" t-value='{}' />
|
||||
<t t-call="reconciliation.line.mv_line" />
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery=".accounting_view thead tr" t-operation="replace">
|
||||
<tr>
|
||||
<td colspan="3"><span /><span
|
||||
t-if="state.last_time_entries_checked"
|
||||
>Last Reconciliation: <t
|
||||
t-esc="state.last_time_entries_checked"
|
||||
/></span></td>
|
||||
<td colspan="2"><t t-esc="state.st_line.account_code" /></td>
|
||||
<td class="cell_info_popover" />
|
||||
</tr>
|
||||
</t>
|
||||
<t t-jquery='div[t-attf-id*="notebook_page_match_rp"]' t-operation="replace" />
|
||||
<t t-jquery='a[t-attf-href*="notebook_page_match_rp"]' t-operation="replace" />
|
||||
</t>
|
||||
|
||||
<t t-name="reconciliation.line.balance">
|
||||
<tr t-if="state.balance.show_balance">
|
||||
<td class="cell_account_code"><t t-esc="state.balance.account_code" /></td>
|
||||
<td class="cell_due_date" />
|
||||
<td class="cell_label"><t t-if="state.st_line.partner_id">Open balance</t><t
|
||||
t-else=""
|
||||
>Choose counterpart or Create Write-off</t></td>
|
||||
<td class="cell_left"><t t-if="state.balance.amount_currency < 0"><span
|
||||
role="img"
|
||||
t-if="state.balance.amount_currency_str"
|
||||
t-attf-class="o_multi_currency o_multi_currency_color_#{state.balance.currency_id%8} line_info_button fa fa-money"
|
||||
t-att-data-content="state.balance.amount_currency_str"
|
||||
t-att-aria-label="state.balance.amount_currency_str"
|
||||
t-att-title="state.balance.amount_currency_str"
|
||||
/><t t-raw="state.balance.amount_str" /></t></td>
|
||||
<td class="cell_right"><t t-if="state.balance.amount_currency > 0"><span
|
||||
role="img"
|
||||
t-if="state.balance.amount_currency_str"
|
||||
t-attf-class="o_multi_currency o_multi_currency_color_#{state.balance.currency_id%8} line_info_button fa fa-money"
|
||||
t-att-data-content="state.balance.amount_currency_str"
|
||||
t-att-aria-label="state.balance.amount_currency_str"
|
||||
t-att-title="state.balance.amount_currency_str"
|
||||
/><t t-raw="state.balance.amount_str" /></t></td>
|
||||
<td class="cell_info_popover" />
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
|
||||
<div t-name="reconciliation.line.match">
|
||||
<div class="match_controls">
|
||||
<span><input
|
||||
class="filter o_input"
|
||||
placeholder="Filter on account, label, partner, amount,..."
|
||||
type="text"
|
||||
t-att-value="state['filter_{{state.mode}}']"
|
||||
/></span>
|
||||
<button class="btn btn-secondary btn-sm fa fa-search" type="button" />
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="load-more text-center">
|
||||
<a href="#">Load more... (<span /> remaining)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div t-name="reconciliation.line.create">
|
||||
<div class="quick_add">
|
||||
<div class="btn-group o_reconcile_models" t-if="state.reconcileModels">
|
||||
<t t-foreach="state.reconcileModels" t-as="reconcileModel">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
t-if="reconcileModel.rule_type === 'writeoff_button' && (reconcileModel.match_journal_ids.length == 0 || reconcileModel.match_journal_ids.includes(state.st_line.journal_id) || state.st_line.journal_id === undefined)"
|
||||
t-att-data-reconcile-model-id="reconcileModel.id"
|
||||
>
|
||||
<t t-esc="reconcileModel.name" />
|
||||
</button>
|
||||
</t>
|
||||
<p
|
||||
t-if="!state.reconcileModels.length"
|
||||
style="color: #bbb;"
|
||||
>To speed up reconciliation, define <a
|
||||
style="cursor: pointer;"
|
||||
class="reconcile_model_create"
|
||||
>reconciliation models</a>.</p>
|
||||
</div>
|
||||
<div class="dropdown float-right">
|
||||
<a data-toggle="dropdown" href="#"><span
|
||||
class="fa fa-cog"
|
||||
role="img"
|
||||
aria-label="Settings"
|
||||
/></a>
|
||||
<div
|
||||
class="dropdown-menu dropdown-menu-right"
|
||||
role="menu"
|
||||
aria-label="Presets config"
|
||||
>
|
||||
<a
|
||||
role="menuitem"
|
||||
class="dropdown-item reconcile_model_create"
|
||||
href="#"
|
||||
>Create model</a>
|
||||
<a
|
||||
role="menuitem"
|
||||
class="dropdown-item reconcile_model_edit"
|
||||
href="#"
|
||||
>Modify models</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix o_form_sheet">
|
||||
<div class="o_group">
|
||||
<table class="o_group o_inner_group o_group_col_6">
|
||||
<tbody>
|
||||
<tr class="create_account_id">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Account</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_tax_id">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Taxes</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_analytic_account_id" t-if="group_acc">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Analytic Acc.</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_analytic_tag_ids" t-if="group_tags">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Analytic Tags.</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="o_group o_inner_group o_group_col_6">
|
||||
<tbody>
|
||||
<tr class="create_journal_id" style="display: none;">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Journal</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_label">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Label</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_amount">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Amount</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_force_tax_included d-none">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Tax Included in Price</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_date d-none">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>Writeoff Date</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
<tr class="create_to_check">
|
||||
<td class="o_td_label"><label
|
||||
class="o_form_label"
|
||||
>To Check</label></td>
|
||||
<td class="o_td_field" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add_line_container">
|
||||
<a
|
||||
class="add_line"
|
||||
t-att-style="!state.balance.amout ? 'display: none;' : null"
|
||||
><i class="fa fa-plus-circle" /> Save and New</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<t t-name="reconciliation.line.mv_line.amount">
|
||||
<span
|
||||
t-att-class="(line.is_move_line && proposition == true) ? 'cell' : ''"
|
||||
>
|
||||
<span class="line_amount">
|
||||
<span
|
||||
t-if="line.amount_currency_str"
|
||||
t-attf-class="o_multi_currency o_multi_currency_color_#{line.currency_id%8} line_info_button fa fa-money"
|
||||
t-att-data-content="line.amount_currency_str"
|
||||
/>
|
||||
<span
|
||||
t-if="line.partial_amount && line.partial_amount != line.amount"
|
||||
class="strike_amount text-muted"
|
||||
>
|
||||
<t t-raw="line.amount_str" />
|
||||
<br />
|
||||
</span>
|
||||
</span>
|
||||
<t t-if="line.is_move_line && proposition == true">
|
||||
<i class="fa fa-pencil edit_amount" />
|
||||
<input class="edit_amount_input text-right d-none" />
|
||||
</t>
|
||||
<span class="line_amount">
|
||||
<t t-if="!line.partial_amount_str" t-raw="line.amount_str" />
|
||||
<t
|
||||
t-if="line.partial_amount_str && line.partial_amount != line.amount"
|
||||
t-raw="line.partial_amount_str"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="reconciliation.line.mv_line">
|
||||
<tr
|
||||
t-if="line.display !== false"
|
||||
t-attf-class="mv_line #{line.already_paid ? ' already_reconciled' : ''} #{line.__invalid ? 'invalid' : ''} #{line.is_tax ? 'is_tax' : ''}"
|
||||
t-att-data-line-id="line.id"
|
||||
t-att-data-selected="selected"
|
||||
>
|
||||
<td class="cell_account_code"><t
|
||||
t-esc="line.account_code"
|
||||
/>​</td> <!-- zero width space to make empty lines the height of the text -->
|
||||
<td class="cell_due_date">
|
||||
<t t-if="typeof(line.id) != 'number' && line.id">
|
||||
<span class="badge badge-secondary">New</span>
|
||||
</t>
|
||||
<t t-else="" t-esc="line.date_maturity || line.date" />
|
||||
</td>
|
||||
<td class="cell_label">
|
||||
<t
|
||||
t-if="line.partner_id && line.partner_id !== state.st_line.partner_id"
|
||||
>
|
||||
<t t-if="line.partner_name.length">
|
||||
<span class="font-weight-bold" t-esc="line.partner_name" />:
|
||||
</t>
|
||||
</t>
|
||||
<t t-esc="line.label || line.name" />
|
||||
<t t-if="line.ref && line.ref.length"> : </t>
|
||||
<t t-esc="line.ref" />
|
||||
</td>
|
||||
<td class="cell_left">
|
||||
<t t-if="line.amount < 0">
|
||||
<t t-call="reconciliation.line.mv_line.amount" />
|
||||
</t>
|
||||
</td>
|
||||
<td class="cell_right">
|
||||
<t t-if="line.amount > 0">
|
||||
<t t-call="reconciliation.line.mv_line.amount" />
|
||||
</t>
|
||||
</td>
|
||||
<td class="cell_info_popover" />
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="reconciliation.line.mv_line.details">
|
||||
<table class='details'>
|
||||
<tr t-if="line.account_code"><td>Account</td><td><t
|
||||
t-esc="line.account_code"
|
||||
/> <t t-esc="line.account_name" /></td></tr>
|
||||
<tr><td>Date</td><td><t t-esc="line.date" /></td></tr>
|
||||
<tr><td>Due Date</td><td><t t-esc="line.date_maturity || line.date" /></td></tr>
|
||||
<tr><td>Journal</td><td><t t-esc="line.journal_id.display_name" /></td></tr>
|
||||
<tr t-if="line.partner_id"><td>Partner</td><td><t
|
||||
t-esc="line.partner_name"
|
||||
/></td></tr>
|
||||
<tr><td>Label</td><td><t t-esc="line.label" /></td></tr>
|
||||
<tr t-if="line.ref"><td>Ref</td><td><t t-esc="line.ref" /></td></tr>
|
||||
<tr><td>Amount</td><td><t t-raw="line.total_amount_str" /><t
|
||||
t-if="line.total_amount_currency_str"
|
||||
> (<t t-esc="line.total_amount_currency_str" />)</t></td></tr>
|
||||
<tr t-if="line.is_partially_reconciled"><td>Residual</td><td>
|
||||
<t t-raw="line.amount_str" /><t t-if="line.amount_currency_str"> (<t
|
||||
t-esc="line.amount_currency_str"
|
||||
/>)</t>
|
||||
</td></tr>
|
||||
<tr class="one_line_info" t-if='line.already_paid'>
|
||||
<td colspan="2">This payment is registered but not reconciled.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="reconciliation.line.statement_line.details">
|
||||
<table class='details'>
|
||||
<tr><td>Date</td><td><t t-esc="state.st_line.date" /></td></tr>
|
||||
<tr t-if="state.st_line.partner_name"><td>Partner</td><td><t
|
||||
t-esc="state.st_line.partner_name"
|
||||
/></td></tr>
|
||||
<tr t-if="state.st_line.ref"><td>Transaction</td><td><t
|
||||
t-esc="state.st_line.ref"
|
||||
/></td></tr>
|
||||
<tr><td>Description</td><td><t t-esc="state.st_line.name" /></td></tr>
|
||||
<tr><td>Amount</td><td><t t-raw="state.st_line.amount_str" /><t
|
||||
t-if="state.st_line.amount_currency_str"
|
||||
> (<t t-esc="state.st_line.amount_currency_str" />)</t></td></tr>
|
||||
<tr><td>Account</td><td><t t-esc="state.st_line.account_code" /> <t
|
||||
t-esc="state.st_line.account_name"
|
||||
/></td></tr>
|
||||
<tr t-if="state.st_line.note"><td>Note</td><td style="white-space: pre;"><t
|
||||
t-esc="state.st_line.note"
|
||||
/></td></tr>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="reconciliation.notification.reconciled">
|
||||
<t t-if="details !== undefined">
|
||||
<a
|
||||
rel="do_action"
|
||||
href="#"
|
||||
aria-label="External link"
|
||||
title="External link"
|
||||
t-att-data-action_name="details.name"
|
||||
t-att-data-model="details.model"
|
||||
t-att-data-ids="details.ids"
|
||||
>
|
||||
<t t-esc="nb_reconciled_lines" />
|
||||
statement lines
|
||||
</a>
|
||||
have been reconciled automatically.
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="reconciliation.notification.default">
|
||||
<t t-esc="message" />
|
||||
<t t-if="details !== undefined">
|
||||
<a
|
||||
class="fa fa-external-link"
|
||||
rel="do_action"
|
||||
href="#"
|
||||
aria-label="External link"
|
||||
title="External link"
|
||||
t-att-data-action_name="details.name"
|
||||
t-att-data-model="details.model"
|
||||
t-att-data-ids="details.ids"
|
||||
>
|
||||
</a>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="reconciliation.notification">
|
||||
<div
|
||||
t-att-class="'notification alert-dismissible alert alert-' + type"
|
||||
role="alert"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close"
|
||||
><span title="Close" class="fa fa-times" /></button>
|
||||
<t t-if="template">
|
||||
<t t-call="{{template}}" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="reconciliation.notification.default" />
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
File diff suppressed because it is too large
Load Diff
1
account_reconciliation_widget/tests/__init__.py
Normal file
1
account_reconciliation_widget/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import test_reconciliation_widget
|
||||
@@ -0,0 +1,239 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
import odoo.tests
|
||||
|
||||
from odoo.addons.account.tests.common import TestAccountReconciliationCommon
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@odoo.tests.tagged("post_install", "-at_install")
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
def test_01_admin_bank_statement_reconciliation(self):
|
||||
bank_stmt_name = "BNK/%s/0001" % time.strftime("%Y")
|
||||
bank_stmt_line = (
|
||||
self.env["account.bank.statement"]
|
||||
.search([("name", "=", bank_stmt_name)])
|
||||
.mapped("line_ids")
|
||||
)
|
||||
if not bank_stmt_line:
|
||||
_logger.info(
|
||||
"Tour bank_statement_reconciliation skipped: bank statement %s "
|
||||
"not found." % bank_stmt_name
|
||||
)
|
||||
return
|
||||
|
||||
admin = self.env.ref("base.user_admin")
|
||||
|
||||
# Tour can't be run if the setup if not the generic one.
|
||||
generic_coa = self.env.ref(
|
||||
"l10n_generic_coa.configurable_chart_template", raise_if_not_found=False
|
||||
)
|
||||
if (
|
||||
not admin.company_id.chart_template_id
|
||||
or admin.company_id.chart_template_id != generic_coa
|
||||
):
|
||||
_logger.info(
|
||||
"Tour bank_statement_reconciliation skipped: generic coa not found."
|
||||
)
|
||||
return
|
||||
|
||||
# To be able to test reconciliation, admin user must have access to
|
||||
# accounting features, so we give him the right group for that
|
||||
admin.write({"groups_id": [(4, self.env.ref("account.group_account_user").id)]})
|
||||
|
||||
payload = {
|
||||
"action": "bank_statement_reconciliation_view",
|
||||
"statement_line_ids[]": bank_stmt_line.ids,
|
||||
}
|
||||
prep = requests.models.PreparedRequest()
|
||||
prep.prepare_url(url="http://localhost/web#", params=payload)
|
||||
|
||||
self.start_tour(
|
||||
prep.url.replace("http://localhost", "").replace("?", "#"),
|
||||
"bank_statement_reconciliation",
|
||||
login="admin",
|
||||
)
|
||||
|
||||
|
||||
@odoo.tests.tagged("post_install", "-at_install")
|
||||
class TestReconciliationWidget(TestAccountReconciliationCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.acc_bank_stmt_model = cls.env["account.bank.statement"]
|
||||
cls.acc_bank_stmt_line_model = cls.env["account.bank.statement.line"]
|
||||
|
||||
def test_statement_suggestion_other_currency(self):
|
||||
# company currency is EUR
|
||||
# payment in USD
|
||||
invoice = self.create_invoice(
|
||||
invoice_amount=50, currency_id=self.currency_usd_id
|
||||
)
|
||||
|
||||
# journal currency in USD
|
||||
|
||||
bank_stmt = self.acc_bank_stmt_model.create(
|
||||
{
|
||||
"journal_id": self.bank_journal_usd.id,
|
||||
"date": time.strftime("%Y-07-15"),
|
||||
"name": "payment %s" % invoice.name,
|
||||
}
|
||||
)
|
||||
|
||||
bank_stmt_line = self.acc_bank_stmt_line_model.create(
|
||||
{
|
||||
"payment_ref": "payment",
|
||||
"statement_id": bank_stmt.id,
|
||||
"partner_id": self.partner_agrolait_id,
|
||||
"amount": 50,
|
||||
"date": time.strftime("%Y-07-15"),
|
||||
}
|
||||
)
|
||||
|
||||
result = self.env["account.reconciliation.widget"].get_bank_statement_line_data(
|
||||
bank_stmt_line.ids
|
||||
)
|
||||
self.assertEqual(
|
||||
result["lines"][0]["reconciliation_proposition"][0]["amount_str"], "$ 50.00"
|
||||
)
|
||||
|
||||
def test_filter_partner1(self):
|
||||
inv1 = self.create_invoice(currency_id=self.currency_euro_id)
|
||||
inv2 = self.create_invoice(currency_id=self.currency_euro_id)
|
||||
partner = inv1.partner_id
|
||||
|
||||
receivable1 = inv1.line_ids.filtered(
|
||||
lambda l: l.account_id.internal_type == "receivable"
|
||||
)
|
||||
receivable2 = inv2.line_ids.filtered(
|
||||
lambda l: l.account_id.internal_type == "receivable"
|
||||
)
|
||||
|
||||
bank_stmt = self.acc_bank_stmt_model.create(
|
||||
{
|
||||
"company_id": self.env.ref("base.main_company").id,
|
||||
"journal_id": self.bank_journal_euro.id,
|
||||
"date": time.strftime("%Y-07-15"),
|
||||
"name": "test",
|
||||
}
|
||||
)
|
||||
|
||||
bank_stmt_line = self.acc_bank_stmt_line_model.create(
|
||||
{
|
||||
"name": "testLine",
|
||||
"statement_id": bank_stmt.id,
|
||||
"amount": 100,
|
||||
"date": time.strftime("%Y-07-15"),
|
||||
"payment_ref": "test",
|
||||
}
|
||||
)
|
||||
|
||||
# This is like input a partner in the widget
|
||||
mv_lines_rec = self.env[
|
||||
"account.reconciliation.widget"
|
||||
].get_move_lines_for_bank_statement_line(
|
||||
bank_stmt_line.id,
|
||||
partner_id=partner.id,
|
||||
excluded_ids=[],
|
||||
search_str=False,
|
||||
mode="rp",
|
||||
)
|
||||
mv_lines_ids = [line["id"] for line in mv_lines_rec]
|
||||
|
||||
self.assertIn(receivable1.id, mv_lines_ids)
|
||||
self.assertIn(receivable2.id, mv_lines_ids)
|
||||
|
||||
# With a partner set, type the invoice reference in the filter
|
||||
mv_lines_rec = self.env[
|
||||
"account.reconciliation.widget"
|
||||
].get_move_lines_for_bank_statement_line(
|
||||
bank_stmt_line.id,
|
||||
partner_id=partner.id,
|
||||
excluded_ids=[],
|
||||
search_str=inv1.payment_reference,
|
||||
mode="rp",
|
||||
)
|
||||
mv_lines_ids = [line["id"] for line in mv_lines_rec]
|
||||
|
||||
self.assertIn(receivable1.id, mv_lines_ids)
|
||||
self.assertNotIn(receivable2.id, mv_lines_ids)
|
||||
|
||||
# Without a partner set, type "deco" in the filter
|
||||
mv_lines_rec = self.env[
|
||||
"account.reconciliation.widget"
|
||||
].get_move_lines_for_bank_statement_line(
|
||||
bank_stmt_line.id,
|
||||
partner_id=False,
|
||||
excluded_ids=[],
|
||||
search_str="deco",
|
||||
mode="rp",
|
||||
)
|
||||
mv_lines_ids = [line["id"] for line in mv_lines_rec]
|
||||
|
||||
self.assertIn(receivable1.id, mv_lines_ids)
|
||||
self.assertIn(receivable2.id, mv_lines_ids)
|
||||
|
||||
# With a partner set, type "deco" in the filter and click on the first
|
||||
# receivable
|
||||
mv_lines_rec = self.env[
|
||||
"account.reconciliation.widget"
|
||||
].get_move_lines_for_bank_statement_line(
|
||||
bank_stmt_line.id,
|
||||
partner_id=partner.id,
|
||||
excluded_ids=[receivable1.id],
|
||||
search_str="deco",
|
||||
mode="rp",
|
||||
)
|
||||
mv_lines_ids = [line["id"] for line in mv_lines_rec]
|
||||
|
||||
self.assertNotIn(receivable1.id, mv_lines_ids)
|
||||
self.assertIn(receivable2.id, mv_lines_ids)
|
||||
|
||||
def test_partner_name_with_parent(self):
|
||||
parent_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "test",
|
||||
}
|
||||
)
|
||||
child_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "test",
|
||||
"parent_id": parent_partner.id,
|
||||
"type": "delivery",
|
||||
}
|
||||
)
|
||||
self.create_invoice_partner(
|
||||
currency_id=self.currency_euro_id, partner_id=child_partner.id
|
||||
)
|
||||
|
||||
bank_stmt = self.acc_bank_stmt_model.create(
|
||||
{
|
||||
"company_id": self.env.ref("base.main_company").id,
|
||||
"journal_id": self.bank_journal_euro.id,
|
||||
"date": time.strftime("%Y-07-15"),
|
||||
"name": "test",
|
||||
}
|
||||
)
|
||||
|
||||
bank_stmt_line = self.acc_bank_stmt_line_model.create(
|
||||
{
|
||||
"name": "testLine",
|
||||
"statement_id": bank_stmt.id,
|
||||
"amount": 100,
|
||||
"date": time.strftime("%Y-07-15"),
|
||||
"payment_ref": "test",
|
||||
"partner_name": "test",
|
||||
}
|
||||
)
|
||||
|
||||
bkstmt_data = self.env[
|
||||
"account.reconciliation.widget"
|
||||
].get_bank_statement_line_data(bank_stmt_line.ids)
|
||||
|
||||
self.assertEqual(len(bkstmt_data["lines"]), 1)
|
||||
self.assertEqual(bkstmt_data["lines"][0]["partner_id"], parent_partner.id)
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_bank_statement_form" model="ir.ui.view">
|
||||
<field name="name">account.bank.statement.inherit.view.form</field>
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='button_post']" position="before">
|
||||
<button
|
||||
name="action_bank_reconcile_bank_statements"
|
||||
string="Reconcile"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible':['|','|',('all_lines_reconciled','=',True),('line_ids','=',[]),('state', '!=', 'posted')]}"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date']" position="after">
|
||||
<field name="accounting_date" groups="base.group_no_one" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="account_journal_dashboard_kanban_view" model="ir.ui.view">
|
||||
<field name="name">account.journal.inherit.dashboard.kanban</field>
|
||||
<field name="model">account.journal</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="account.account_journal_dashboard_kanban_view"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath
|
||||
expr="//kanban/templates//div[@id='dashboard_bank_cash_left']/t[1]"
|
||||
position="before"
|
||||
>
|
||||
<t t-if="dashboard.number_to_reconcile > 0">
|
||||
<button
|
||||
type="object"
|
||||
name="action_open_reconcile"
|
||||
class="btn btn-primary"
|
||||
> Reconcile <t
|
||||
t-esc="dashboard.number_to_reconcile"
|
||||
/> Items</button>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//kanban/templates//div[@id='dashboard_bank_cash_right']"
|
||||
position="inside"
|
||||
>
|
||||
<t t-if="dashboard.number_to_check > 0">
|
||||
<div class="row">
|
||||
<div class="col overflow-hidden text-left">
|
||||
<a type="object" name="action_open_reconcile_to_check">
|
||||
<t t-esc="dashboard.number_to_check" /> to check</a>
|
||||
</div>
|
||||
<div class="col-auto text-right">
|
||||
<span>
|
||||
<t t-esc="dashboard.to_check_balance" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
30
account_reconciliation_widget/views/account_view.xml
Normal file
30
account_reconciliation_widget/views/account_view.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="action_bank_reconcile" model="ir.actions.client">
|
||||
<field name="name">Reconciliation on Bank Statements</field>
|
||||
<field name="res_model">account.bank.statement.line</field>
|
||||
<field name="tag">bank_statement_reconciliation_view</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_account_move_line_reconcile" model="ir.actions.client">
|
||||
<field name="name">Reconcile</field>
|
||||
<field name="tag">manual_reconciliation_view</field>
|
||||
<field name="binding_model_id" ref="account.model_account_move_line" />
|
||||
<field name="binding_type">action</field>
|
||||
<field name="binding_view_types">list</field>
|
||||
</record>
|
||||
|
||||
<record id="action_manual_reconciliation" model="ir.actions.client">
|
||||
<field name="name">Reconciliation</field>
|
||||
<field name="tag">manual_reconciliation_view</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_action_manual_reconciliation"
|
||||
parent="account.menu_finance_entries_actions"
|
||||
action="action_manual_reconciliation"
|
||||
sequence="25"
|
||||
/>
|
||||
</data>
|
||||
</odoo>
|
||||
42
account_reconciliation_widget/views/assets.xml
Normal file
42
account_reconciliation_widget/views/assets.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<template
|
||||
id="assets_backend"
|
||||
name="account_reconciliation_widget assets"
|
||||
inherit_id="web.assets_backend"
|
||||
>
|
||||
<xpath expr="." position="inside">
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/scss"
|
||||
href="/account_reconciliation_widget/static/src/scss/account_reconciliation.scss"
|
||||
/>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/account_reconciliation_widget/static/src/js/reconciliation/reconciliation_action.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/account_reconciliation_widget/static/src/js/reconciliation/reconciliation_model.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/account_reconciliation_widget/static/src/js/reconciliation/reconciliation_renderer.js"
|
||||
/>
|
||||
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" name="account tests" inherit_id="web.qunit_suite_tests">
|
||||
<xpath expr="." position="inside">
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/account_reconciliation_widget/static/tests/account_reconciliation_tests.js"
|
||||
/>
|
||||
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
../../../../account_reconciliation_widget
|
||||
6
setup/account_reconciliation_widget/setup.py
Normal file
6
setup/account_reconciliation_widget/setup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|
||||
Reference in New Issue
Block a user