Merge PR #984 into 13.0

Signed-off-by kittiu
This commit is contained in:
OCA-git-bot
2021-10-01 10:00:58 +00:00
47 changed files with 9131 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
===================
Cost-Revenue Spread
===================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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--financial--tools-lightgray.png?logo=github
:target: https://github.com/OCA/account-financial-tools/tree/12.0/account_spread_cost_revenue
:alt: OCA/account-financial-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-financial-tools-12-0/account-financial-tools-12-0-account_spread_cost_revenue
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/92/12.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
Allows to spread costs or revenues over a customizable periods, to even out cost or invoice spikes.
**Table of contents**
.. contents::
:local:
Configuration
=============
To be able to access the full spreading features, the user must belong to *Show Full Accounting Features* group.
On the form view of the company, in the *Account Spread* tab, you can configure
the journals in which the spread journal items will be generated by default:
* the *Default Spread Journal for Revenues*,
* the *Default Spread Journal for Expenses*.
In the same *Account Spread* tab, you can also configure the Spread Balance Sheet Accounts used by default:
* the *Default Spread Account for Revenues*,
* the *Default Spread Account for Expenses*.
This module by default allows the spreading even before the receipt of the invoice or when the invoice is still draft,
so that it is possible to work on the plan of the cost/revenue spreading. To disable this feature, on the form view of
the company disable the *Allow Spread Planning* option.
On the form view of the company, the *Auto-post spread lines* option forces the account moves created
during the cost/revenue spreading to be automatically posted. When this option is false, the user can
enable/disable the automatic posting by the flag *Auto-post lines* present in the spread board.
On the form view of the company, enable the *Auto-archive spread* option if you want the
cron job to automatically archive the spreads when all lines are posted.
Usage
=====
Define Spread Costs/Revenues Board
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Under Invoicing -> Adviser -> Accounting Entries -> Spread Costs/Revenues, create a new spread board.
Complete the definition of the spreading criteria, by setting the the fields:
* *Debit Account*
* *Credit Account*
* *Estimated Amount* (The total amount to spread)
* *Number of Repetitions*
* *Period Type* (Duration of each period)
* *Start date*
* *Journal*
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/spread.png
:alt: Create a new spread board
Click on the button on the top-left to calculate the spread lines.
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/create_spread.png
:alt: The spreading board is defined
A cron job will automatically create the accounting moves for all the lines having date previous that the current day (today).
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/update_spread.png
:alt: The spreading board is updated by the cron job
By default, the status of the created accounting moves is posted.
To disable the automatic posting of the accounting moves, set the flag *Auto-post lines* to False.
This flag is only available when the *Auto-post spread lines* option, present on the form view of the company, is disabled.
Click on button *Recalculate entire spread* button in the spread board to force the recalculation of the spread lines:
this will also reset all the journal entries previously created.
Link Invoice to Spread Costs/Revenues Board
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Create an invoice or vendor bill in draft. On its lines, the spreading right-arrow icon are displayed in dark-grey color.
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/invoice_line_1.png
:alt: On the invoice line the spreading icon is displayed
Click on the spreading right-arrow icon. A wizard prompts to enter a *Spread Action Type*:
- *Link to existing spread board*
- *Create from spread template*
- *Create new spread board*
Select *Link to existing spread board* and enter the previously generated Spread Board. Click on Confirm button:
the selected Spread Board will be automatically displayed.
Go back to the draft invoice/bill. The spreading functionality is now enabled on the invoice line:
the spreading right-arrow icon is now displayed in green color.
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/invoice_line_2.png
:alt: On the invoice line the spreading icon is displayed in green color
Validate the invoice/bill. Click on the spreading (green) right-arrow icon to open the spread board, then click
on the smart button *Reconciled entries*: the moves of the spread lines are reconciled with the move of the invoice line.
In case the Subtotal Price of the invoice line is different than the *Estimated Amount* of the spread board, the spread
lines (not yet posted) will be recalculated when validating the invoice/bill.
Define Spread Costs/Revenues Template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Under Invoicing -> Configuration -> Accounting -> Spread Templates, create a new spread template.
* *Spread Type*
* *Spread Balance Sheet Account*
* *Expense/Revenue Account* This option visible if invoice line account is balance sheet account, user need to specify this too.
* *Journal*
When creating a new Spread Costs/Revenues Board, select the right template.
This way the above fields will be copied to the Spread Board.
Changelog
=========
12.0.1.1.0
~~~~~~~~~~
* [ENH] Add optional Expense/Revenue Account in Chart Template, which can be used
in place of account from invoice line to set Expense/Revenue account in the spread
12.0.1.0.0
~~~~~~~~~~
* [MIG] Port account_spread_cost_revenue to V12.
11.0.1.0.0
~~~~~~~~~~
* [ADD] Module account_spread_cost_revenue.
(`#715 <https://github.com/OCA/account-financial-tools/pull/715>`_)
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-financial-tools/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-financial-tools/issues/new?body=module:%20account_spread_cost_revenue%0Aversion:%2012.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
~~~~~~~
* Onestein
Contributors
~~~~~~~~~~~~
* Andrea Stirpe <a.stirpe@onestein.nl>
* Kitti U. <kittiu@ecosoft.co.th>
Other credits
~~~~~~~~~~~~~
Part of the code in this module (in particular the computation of the spread lines)
is highly inspired by the Assets Management module from the standard
Odoo 11.0 Community developed by Odoo SA.
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.
.. |maintainer-astirpe| image:: https://github.com/astirpe.png?size=40px
:target: https://github.com/astirpe
:alt: astirpe
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-astirpe|
This module is part of the `OCA/account-financial-tools <https://github.com/OCA/account-financial-tools/tree/12.0/account_spread_cost_revenue>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import models
from . import wizards

View File

@@ -0,0 +1,27 @@
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Cost-Revenue Spread",
"summary": "Spread costs and revenues over a custom period",
"version": "13.0.1.0.0",
"development_status": "Beta",
"author": "Onestein,Odoo Community Association (OCA)",
"maintainers": ["astirpe"],
"license": "AGPL-3",
"website": "https://github.com/OCA/account-financial-tools/",
"category": "Accounting & Finance",
"depends": ["account"],
"data": [
"security/ir.model.access.csv",
"security/account_spread_security.xml",
"views/account_spread.xml",
"views/account_move.xml",
"views/res_company.xml",
"views/account_spread_template.xml",
"templates/assets.xml",
"wizards/account_spread_invoice_line_link_wizard.xml",
"data/spread_cron.xml",
],
"installable": True,
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record id="ir_cron_spread_create_entries" forcecreate="True" model="ir.cron">
<field name="name">Cost/revenue Spread: Create Entries</field>
<field name="active" eval="True" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="model_id" ref="model_account_spread_line" />
<field name="state">code</field>
<field name="code">model._create_entries()</field>
</record>
</odoo>

View File

@@ -0,0 +1,933 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_spread_cost_revenue
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "<span class=\"help-block text-danger\" colspan=\"2\" attrs=\"{'invisible':[('is_credit_account_deprecated','!=',True)]}\">\n"
" This account in deprecated! The reconciliation will be NOT possible.\n"
" </span>"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "<span class=\"help-block text-danger\" colspan=\"2\" attrs=\"{'invisible':[('is_debit_account_deprecated','!=',True)]}\">\n"
" This account in deprecated! The reconciliation will be NOT possible.\n"
" </span>"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "<span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}\">\n"
" The Balance Sheet account used for the spreading.<br/>This account is the counterpart of the account in the invoice line.\n"
" </span>\n"
" <span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}\">\n"
" The Expense account in the vendor bill line.<br/>Usually the same account of the vendor bill line.\n"
" </span>"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "<span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}\">\n"
" The Balance Sheet account.<br/>This is the account in the invoice line.\n"
" </span>\n"
" <span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}\">\n"
" The Expense account used for the spreading.<br/>This account is the counterpart of the account of the vendor bill line.\n"
" </span>"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "<span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}\">\n"
" The Revenue account in the invoice line.<br/>Usually the same account of the invoice line.\n"
" </span>\n"
" <span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}\">\n"
" The Balance Sheet account used for the spreading.<br/>This account is the counterpart of the account in the vendor bill line.\n"
" </span>"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "<span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}\">\n"
" The Revenue account used for the spreading.<br/>This account is the counterpart of the account of the invoice line.\n"
" </span>\n"
" <span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}\">\n"
" The Balance Sheet account.<br/>This is the account in the vendor bill line.\n"
" </span>"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_company_form
msgid "Account Spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread_invoice_line_link_wizard
msgid "Account Spread Invoice Line Link Wizard"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread_line
msgid "Account Spread Lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread_template
msgid "Account Spread Template"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_needaction
msgid "Action Needed"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__active
msgid "Active"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__all_posted
msgid "All Posted"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__allow_spread_planning
msgid "Allow Spread Planning"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__amount
msgid "Amount"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__account_analytic_id
msgid "Analytic Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__analytic_tag_ids
msgid "Analytic Tags"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_search
msgid "Archived"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_attachment_count
msgid "Attachment Count"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__auto_archive
msgid "Auto-archive spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__move_line_auto_post
msgid "Auto-post lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__force_move_auto_post
msgid "Auto-post spread lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__spread_account_id
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Balance sheet account / Spread account"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_invoice_line_link_wizard
msgid "Cancel"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:467
#, python-format
msgid "Cannot delete spread(s) that are linked to an invoice line."
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:472
#, python-format
msgid "Cannot delete spread(s): there are posted Journal Entries."
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:439
#, python-format
msgid "Cannot unlink invoice lines if the invoice is validated"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_res_company
msgid "Companies"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__company_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__company_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__company_id
msgid "Company"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_invoice_line_link_wizard
msgid "Confirm"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.actions.server,name:account_spread_cost_revenue.ir_cron_spread_create_entries_ir_actions_server
#: model:ir.cron,cron_name:account_spread_cost_revenue.ir_cron_spread_create_entries
#: model:ir.cron,name:account_spread_cost_revenue.ir_cron_spread_create_entries
msgid "Cost/revenue Spread: Create Entries"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Create All Moves"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Create Move"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:13
#, python-format
msgid "Create from spread template"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:14
#, python-format
msgid "Create new spread board"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__create_uid
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__create_uid
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__create_uid
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__create_uid
msgid "Created by"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:36
#, python-format
msgid "Created move(s) "
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__create_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__create_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__create_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__create_date
msgid "Created on"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__credit_account_id
msgid "Credit Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__currency_id
msgid "Currency"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,spread_type:0
#: selection:account.spread.invoice.line.link.wizard,spread_type:0
#: selection:account.spread.template,spread_type:0
msgid "Customer"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,invoice_type:0
#: selection:account.spread.invoice.line.link.wizard,invoice_type:0
msgid "Customer Credit Note"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,invoice_type:0
#: selection:account.spread.invoice.line.link.wizard,invoice_type:0
msgid "Customer Invoice"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__date
msgid "Date"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__debit_account_id
msgid "Debit Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_company_form
msgid "Default Spread Accounts"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_company_form
msgid "Default Spread Journals"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__period_number
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread_template__period_number
msgid "Define the number of spread lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Delete Move"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:149
#, python-format
msgid "Deleted move %s"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__name
msgid "Description"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Details"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_res_company__allow_spread_planning
msgid "Disable this option if you do not want to allow the spreading before the invoice is validated."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__display_create_all_moves
msgid "Display Button All Moves"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__display_move_line_auto_post
msgid "Display Button Auto-post lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__display_recompute_buttons
msgid "Display Buttons Recompute"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__display_name
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__display_name
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__display_name
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__display_name
msgid "Display Name"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_res_company__auto_archive
msgid "Enable this option if you want the cron job to automatically archive the spreads when all lines are posted."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_res_company__force_move_auto_post
msgid "Enable this option if you want to post automatically the accounting moves of all the spreads."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__estimated_amount
msgid "Estimated Amount"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__default_spread_expense_account_id
msgid "Expense Spread Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__default_spread_expense_journal_id
msgid "Expense Spread Journal"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Expense account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__exp_rev_account_id
msgid "Expense/Revenue Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__exp_rev_account_id
msgid "Expense/revenue account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_follower_ids
msgid "Followers"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_channel_ids
msgid "Followers (Channels)"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_partner_ids
msgid "Followers (Partners)"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__id
msgid "ID"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__message_unread
msgid "If checked new messages require your attention."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__message_needaction
msgid "If checked, new messages require your attention."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__message_has_error
msgid "If checked, some messages have a delivery error."
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:453
#: model:ir.model,name:account_spread_cost_revenue.model_account_invoice
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__invoice_id
#, python-format
msgid "Invoice"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_invoice_line
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__invoice_line_id
msgid "Invoice Line"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__invoice_line_ids
msgid "Invoice Lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__invoice_id
msgid "Invoice Reference"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__invoice_type
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__invoice_type
msgid "Invoice Type"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__use_invoice_line_account
msgid "Invoice account as spread account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__invoice_line_id
msgid "Invoice line"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__is_credit_account_deprecated
msgid "Is Credit Account Deprecated"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__is_debit_account_deprecated
msgid "Is Debit Account Deprecated"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_is_follower
msgid "Is Follower"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__journal_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__spread_journal_id
msgid "Journal"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:130
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__move_id
#, python-format
msgid "Journal Entry"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread____last_update
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard____last_update
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line____last_update
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template____last_update
msgid "Last Modified on"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__write_uid
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__write_uid
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__write_uid
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__write_uid
msgid "Last Updated by"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__write_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__write_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__write_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__write_date
msgid "Last Updated on"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_invoice_line.py:60
#, python-format
msgid "Link Invoice Line with Spread Board"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:20
#, python-format
msgid "Link to existing spread board"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.invoice.line,spread_check:0
msgid "Linked"
msgstr ""
#. module: account_spread_cost_revenue
#. openerp-web
#: code:addons/account_spread_cost_revenue/static/src/js/account_spread.js:33
#, python-format
msgid "Linked to spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_main_attachment_id
msgid "Main Attachment"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_has_error
msgid "Message Delivery error"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_ids
msgid "Messages"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,period_type:0
#: selection:account.spread.template,period_type:0
msgid "Month"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__name
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__name
msgid "Name"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:157
#, python-format
msgid "New Spread Board"
msgstr ""
#. module: account_spread_cost_revenue
#. openerp-web
#: code:addons/account_spread_cost_revenue/static/src/js/account_spread.js:35
#, python-format
msgid "Not linked to spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_needaction_counter
msgid "Number of Actions"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__period_number
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__period_number
msgid "Number of Repetitions"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_has_error_counter
msgid "Number of error"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__message_needaction_counter
msgid "Number of messages which requires an action"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__message_has_error_counter
msgid "Number of messages with delivery error"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__message_unread_counter
msgid "Number of unread messages"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__exp_rev_account_id
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread_template__exp_rev_account_id
msgid "Optional account to overwrite the existing expense/revenue account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__period_type
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__period_type
msgid "Period Type"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__period_type
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread_template__period_type
msgid "Period length for the entries"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__posted_amount
msgid "Posted Amount"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,period_type:0
#: selection:account.spread.template,period_type:0
msgid "Quarter"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Recalculate entire spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Recalculate unposted lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Reconciled entries"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__default_spread_revenue_account_id
msgid "Revenue Spread Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_res_company__default_spread_revenue_journal_id
msgid "Revenue Spread Journal"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Revenue account"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:458
#, python-format
msgid "Spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__spread_action_type
msgid "Spread Action Type"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__spread_account_id
msgid "Spread Balance Sheet Account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_invoice_line__spread_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__spread_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line__spread_id
msgid "Spread Board"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Spread Board Name"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_invoice_line__spread_check
msgid "Spread Check"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.actions.act_window,name:account_spread_cost_revenue.action_account_spread_form
#: model:ir.ui.menu,name:account_spread_cost_revenue.menu_action_account_spread_form
msgid "Spread Costs/Revenues"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_invoice_line.py:40
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:134
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:213
#, python-format
msgid "Spread Details"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__spread_journal_id
msgid "Spread Journal"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__line_ids
msgid "Spread Lines"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__template_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__template_id
msgid "Spread Template"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_template
msgid "Spread Template Name"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.actions.act_window,name:account_spread_cost_revenue.action_account_spread_template_form
#: model:ir.ui.menu,name:account_spread_cost_revenue.menu_action_account_spread_template_form
msgid "Spread Templates"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__spread_type
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__spread_type
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__spread_type
msgid "Spread Type"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Spread lines"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:378
#, python-format
msgid "Spread table '%s' created."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__spread_date
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template__start_date
msgid "Start Date"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,spread_type:0
#: selection:account.spread.invoice.line.link.wizard,spread_type:0
#: selection:account.spread.template,spread_type:0
msgid "Supplier"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:264
#, python-format
msgid "The Invoice Type does not correspond to the Invoice"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:272
#, python-format
msgid "The Journal is not consistent with the account moves."
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:285
#, python-format
msgid "The Spread Template (Purchases) is not compatible with selected invoice type"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:280
#, python-format
msgid "The Spread Template (Sales) is not compatible with selected invoice type"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:65
#, python-format
msgid "This spread line is already linked to a journal entry! Please post or delete it."
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "This will delete the move. Are you sure ?"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Total"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__total_amount
msgid "Total Amount"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.invoice.line,spread_check:0
msgid "Unavailable"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Undo spread"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Unlink Invoice Line"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.invoice.line,spread_check:0
msgid "Unlinked"
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:459
#, python-format
msgid "Unlinked '%s' (invoice line %s)."
msgstr ""
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:454
#, python-format
msgid "Unlinked invoice line '%s' (view %s)."
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__unposted_amount
msgid "Unposted Amount"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_unread
msgid "Unread Messages"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__message_unread_counter
msgid "Unread Messages Counter"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__unspread_amount
msgid "Unspread Amount"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__use_invoice_line_account
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__use_invoice_line_account
msgid "Use invoice line's account"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard__use_invoice_line_account
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread_template__use_invoice_line_account
msgid "Use invoice line's account as Balance sheet / spread account.\n"
"In this case, user need to select expense/revenue account too."
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,invoice_type:0
#: selection:account.spread.invoice.line.link.wizard,invoice_type:0
msgid "Vendor Bill"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,invoice_type:0
#: selection:account.spread.invoice.line.link.wizard,invoice_type:0
msgid "Vendor Credit Note"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "View Move"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread__website_message_ids
msgid "Website Messages"
msgstr ""
#. module: account_spread_cost_revenue
#: model:ir.model.fields,help:account_spread_cost_revenue.field_account_spread__website_message_ids
msgid "Website communication history"
msgstr ""
#. module: account_spread_cost_revenue
#: selection:account.spread,period_type:0
#: selection:account.spread.template,period_type:0
msgid "Year"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "e.g. One year offices cleaning contract"
msgstr ""
#. module: account_spread_cost_revenue
#: model_terms:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_template
msgid "e.g. Template cleaning contract"
msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import SUPERUSER_ID, api
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
rule_name = "account_spread_cost_revenue.account_spread_multi_company_rule"
rule = env.ref(rule_name, raise_if_not_found=False)
if rule:
domain = "['|',('company_id','=',False),('company_id','in',company_ids)]"
rule.write({"domain_force": domain})
rule_name = "account_spread_cost_revenue.account_spread_template_multi_company_rule"
rule = env.ref(rule_name, raise_if_not_found=False)
if rule:
domain = "['|',('company_id','=',False),('company_id','in',company_ids)]"
rule.write({"domain_force": domain})
rule_name = (
"account_spread_cost_revenue.account_spread_template_auto_multi_company_rule"
)
rule = env.ref(rule_name, raise_if_not_found=False)
if rule:
domain = "['|',('company_id','=',False),('company_id','in',company_ids)]"
rule.write({"domain_force": domain})

View File

@@ -0,0 +1,7 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tools import sql
def migrate(cr, version):
sql.rename_column(cr, "res_partner", "auto_archive", "auto_archive_spread")

View File

@@ -0,0 +1,8 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import account_move
from . import account_move_line
from . import account_spread_line
from . import account_spread
from . import account_spread_template
from . import res_company

View File

@@ -0,0 +1,34 @@
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models
class AccountMove(models.Model):
_inherit = "account.move"
def action_post(self):
"""Invoked when validating the invoices."""
self.mapped("invoice_line_ids").create_auto_spread()
res = super().action_post()
spreads = self.mapped("invoice_line_ids.spread_id")
spreads.compute_spread_board()
spreads.reconcile_spread_moves()
return res
def button_cancel(self):
"""Cancel the spread lines and their related moves when
the invoice is canceled."""
spread_lines = self.mapped("invoice_line_ids.spread_id.line_ids")
moves = spread_lines.mapped("move_id")
moves.line_ids.remove_move_reconcile()
moves.filtered(lambda move: move.state == "posted").button_draft()
moves.with_context(force_delete=True).unlink()
spread_lines.unlink()
res = super().button_cancel()
return res
@api.constrains("name", "journal_id", "state")
def _check_unique_sequence_number(self):
if not self.env.context.get("skip_unique_sequence_number"):
return super()._check_unique_sequence_number()

View File

@@ -0,0 +1,163 @@
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
spread_id = fields.Many2one("account.spread", string="Spread Board", copy=False)
spread_check = fields.Selection(
[
("linked", "Linked"),
("unlinked", "Unlinked"),
("unavailable", "Unavailable"),
],
compute="_compute_spread_check",
)
@api.depends("spread_id", "move_id.state")
def _compute_spread_check(self):
for line in self:
if line.spread_id:
line.spread_check = "linked"
elif line.move_id.state == "draft":
line.spread_check = "unlinked"
else:
line.spread_check = "unavailable"
def spread_details(self):
"""Button on the invoice lines tree view of the invoice
form to show the spread form view."""
if not self:
# In case the widget clicked before the creation of the line
return
if self.spread_id:
return {
"name": _("Spread Details"),
"view_mode": "form",
"res_model": "account.spread",
"type": "ir.actions.act_window",
"target": "current",
"readonly": False,
"res_id": self.spread_id.id,
}
# In case no spread board is linked to the invoice line
# open the wizard to link them
ctx = dict(
self.env.context,
default_invoice_line_id=self.id,
default_company_id=self.move_id.company_id.id,
allow_spread_planning=self.move_id.company_id.allow_spread_planning,
)
return {
"name": _("Link Invoice Line with Spread Board"),
"view_mode": "form",
"res_model": "account.spread.invoice.line.link.wizard",
"type": "ir.actions.act_window",
"target": "new",
"context": ctx,
}
@api.constrains("spread_id", "account_id")
def _check_spread_account_balance_sheet(self):
for line in self:
if not line.spread_id:
pass
elif line.move_id.type in ("out_invoice", "in_refund"):
if line.account_id != line.spread_id.debit_account_id:
raise ValidationError(
_(
"The account of the invoice line does not correspond "
"to the Balance Sheet (debit account) of the spread"
)
)
elif line.move_id.type in ("in_invoice", "out_refund"):
if line.account_id != line.spread_id.credit_account_id:
raise ValidationError(
_(
"The account of the invoice line does not correspond "
"to the Balance Sheet (credit account) of the spread"
)
)
def write(self, vals):
if vals.get("spread_id"):
spread = self.env["account.spread"].browse(vals.get("spread_id"))
if spread.invoice_type in ["out_invoice", "in_refund"]:
vals["account_id"] = spread.debit_account_id.id
else:
vals["account_id"] = spread.credit_account_id.id
return super().write(vals)
def _check_spread_reconcile_validity(self):
# Improve error messages of standard Odoo
reconciled_lines = self.filtered(lambda l: l.reconciled)
msg_line = _("Move line: %s (%s), account code: %s\n")
if reconciled_lines:
msg = _("Cannot reconcile entries that are already reconciled:\n")
for line in reconciled_lines:
msg += msg_line % (line.id, line.name, line.account_id.code)
raise ValidationError(msg)
if len(self.mapped("account_id").ids) > 1:
msg = _("Some entries are not from the same account:\n")
for line in self:
msg += msg_line % (line.id, line.name, line.account_id.code)
raise ValidationError(msg)
def create_auto_spread(self):
""" Create auto spread table for each invoice line, when needed """
def _filter_line(aline, iline):
""" Find matching template auto line with invoice line """
if aline.product_id and iline.product_id != aline.product_id:
return False
if aline.account_id and iline.account_id != aline.account_id:
return False
if (
aline.analytic_account_id
and iline.account_analytic_id != aline.analytic_account_id
):
return False
return True
for line in self:
if line.spread_check == "linked":
continue
spread_type = (
"sale"
if line.move_id.type in ["out_invoice", "out_refund"]
else "purchase"
)
spread_auto = self.env["account.spread.template.auto"].search(
[
("template_id.auto_spread", "=", True),
("template_id.spread_type", "=", spread_type),
]
)
matched = spread_auto.filtered(lambda a, i=line: _filter_line(a, i))
template = matched.mapped("template_id")
if not template:
continue
elif len(template) > 1:
raise UserError(
_(
"Too many auto spread templates (%s) matched with the "
"invoice line, %s"
)
% (len(template), line.display_name)
)
# Found auto spread template for this invoice line, create it
wizard = self.env["account.spread.invoice.line.link.wizard"].new(
{
"invoice_line_id": line.id,
"company_id": line.company_id.id,
"spread_action_type": "template",
"template_id": template.id,
}
)
wizard.confirm()

View File

@@ -0,0 +1,517 @@
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import calendar
import time
from dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_is_zero
class AccountSpread(models.Model):
_name = "account.spread"
_description = "Account Spread"
_inherit = ["mail.thread"]
name = fields.Char(required=True)
template_id = fields.Many2one("account.spread.template", string="Spread Template")
invoice_type = fields.Selection(
[
("out_invoice", "Customer Invoice"),
("in_invoice", "Vendor Bill"),
("out_refund", "Customer Credit Note"),
("in_refund", "Vendor Credit Note"),
],
required=True,
)
spread_type = fields.Selection(
[("sale", "Customer"), ("purchase", "Supplier")],
compute="_compute_spread_type",
required=True,
)
period_number = fields.Integer(
string="Number of Repetitions",
default=12,
help="Define the number of spread lines",
required=True,
)
period_type = fields.Selection(
[("month", "Month"), ("quarter", "Quarter"), ("year", "Year")],
default="month",
help="Period length for the entries",
required=True,
)
use_invoice_line_account = fields.Boolean()
credit_account_id = fields.Many2one(
"account.account",
compute="_compute_credit_account_id",
readonly=False,
store=True,
required=True,
)
debit_account_id = fields.Many2one(
"account.account",
compute="_compute_debit_account_id",
readonly=False,
store=True,
required=True,
)
is_credit_account_deprecated = fields.Boolean(
compute="_compute_deprecated_accounts"
)
is_debit_account_deprecated = fields.Boolean(compute="_compute_deprecated_accounts")
unspread_amount = fields.Float(digits="Account", compute="_compute_amounts",)
unposted_amount = fields.Float(digits="Account", compute="_compute_amounts",)
posted_amount = fields.Float(digits="Account", compute="_compute_amounts",)
total_amount = fields.Float(digits="Account", compute="_compute_amounts",)
all_posted = fields.Boolean(compute="_compute_all_posted", store=True)
line_ids = fields.One2many(
"account.spread.line", "spread_id", string="Spread Lines"
)
spread_date = fields.Date(
string="Start Date", default=time.strftime("%Y-01-01"), required=True
)
journal_id = fields.Many2one(
"account.journal",
compute="_compute_journal_id",
readonly=False,
store=True,
required=True,
)
invoice_line_ids = fields.One2many(
"account.move.line", "spread_id", copy=False, string="Invoice Lines"
)
invoice_line_id = fields.Many2one(
"account.move.line",
string="Invoice line",
compute="_compute_invoice_line",
inverse="_inverse_invoice_line",
store=True,
)
invoice_id = fields.Many2one(
related="invoice_line_id.move_id", readonly=True, store=True,
)
estimated_amount = fields.Float(digits="Account")
company_id = fields.Many2one(
"res.company", default=lambda self: self.env.company, required=True
)
currency_id = fields.Many2one(
"res.currency",
required=True,
default=lambda self: self.env.company.currency_id.id,
)
account_analytic_id = fields.Many2one(
"account.analytic.account", string="Analytic Account"
)
analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")
move_line_auto_post = fields.Boolean("Auto-post lines", default=True)
display_create_all_moves = fields.Boolean(
compute="_compute_display_create_all_moves",
)
display_recompute_buttons = fields.Boolean(
compute="_compute_display_recompute_buttons",
)
display_move_line_auto_post = fields.Boolean(
compute="_compute_display_move_line_auto_post",
string="Display Button Auto-post lines",
)
active = fields.Boolean(default=True)
@api.model
def default_journal(self, company_id):
domain = [("type", "=", "general"), ("company_id", "=", company_id)]
return self.env["account.journal"].search(domain, limit=1)
@api.model
def default_get(self, fields):
res = super().default_get(fields)
if "journal_id" not in res:
company_id = res.get("company_id", self.env.company.id)
default_journal = self.default_journal(company_id)
if default_journal:
res["journal_id"] = default_journal.id
return res
@api.depends("invoice_type")
def _compute_spread_type(self):
for spread in self:
if spread.invoice_type in ["out_invoice", "out_refund"]:
spread.spread_type = "sale"
else:
spread.spread_type = "purchase"
@api.depends("invoice_line_ids", "invoice_line_ids.move_id")
def _compute_invoice_line(self):
for spread in self:
invoice_lines = spread.invoice_line_ids
spread.invoice_line_id = invoice_lines and invoice_lines[0] or False
def _inverse_invoice_line(self):
for spread in self:
invoice_line = spread.invoice_line_id
spread.write({"invoice_line_ids": [(6, 0, [invoice_line.id])]})
@api.depends(
"estimated_amount",
"currency_id",
"company_id",
"invoice_line_id.price_subtotal",
"invoice_line_id.currency_id",
"line_ids.amount",
"line_ids.move_id.state",
)
def _compute_amounts(self):
for spread in self:
lines_move = spread.line_ids.filtered(lambda l: l.move_id)
moves_amount = sum(spread_line.amount for spread_line in lines_move)
lines_posted = lines_move.filtered(lambda l: l.move_id.state == "posted")
posted_amount = sum(spread_line.amount for spread_line in lines_posted)
total_amount = spread.estimated_amount
if spread.invoice_line_id:
total_amount = spread.invoice_line_id.currency_id._convert(
spread.invoice_line_id.balance,
spread.currency_id,
spread.company_id,
spread.invoice_id.date,
)
spread.unspread_amount = total_amount - moves_amount
spread.unposted_amount = total_amount - posted_amount
spread.posted_amount = posted_amount
spread.total_amount = total_amount
@api.depends("unposted_amount")
def _compute_all_posted(self):
for spread in self:
rounding = self.currency_id.rounding
unposted = spread.unposted_amount
spread.all_posted = float_is_zero(unposted, precision_rounding=rounding)
def _compute_display_create_all_moves(self):
for spread in self:
any_not_move = any(not line.move_id for line in spread.line_ids)
spread.display_create_all_moves = any_not_move
def _compute_display_recompute_buttons(self):
for spread in self:
spread.display_recompute_buttons = True
if not spread.company_id.allow_spread_planning:
if spread.invoice_id.state == "draft":
spread.display_recompute_buttons = False
@api.depends("company_id.force_move_auto_post")
def _compute_display_move_line_auto_post(self):
for spread in self:
auto_post = spread.company_id.force_move_auto_post
spread.display_move_line_auto_post = not auto_post
def _get_spread_entry_name(self, seq):
"""Use this method to customise the name of the accounting entry."""
self.ensure_one()
return (self.name or "") + "/" + str(seq)
@api.onchange("template_id")
def onchange_template(self):
if self.template_id:
if self.template_id.spread_type == "sale":
if self.invoice_type in ["in_invoice", "in_refund"]:
self.invoice_type = "out_invoice"
else:
if self.invoice_type in ["out_invoice", "out_refund"]:
self.invoice_type = "in_invoice"
if self.template_id.period_number:
self.period_number = self.template_id.period_number
if self.template_id.period_type:
self.period_type = self.template_id.period_type
if self.template_id.start_date:
self.spread_date = self.template_id.start_date
@api.depends("invoice_type", "company_id")
def _compute_journal_id(self):
if not self.env.context.get("default_journal_id"):
for spread in self:
journal = spread.company_id.default_spread_expense_journal_id
if spread.invoice_type in ("out_invoice", "in_refund"):
journal = spread.company_id.default_spread_revenue_journal_id
if not journal:
journal = self.default_journal(spread.company_id.id)
spread.journal_id = journal
@api.depends("invoice_type", "company_id")
def _compute_debit_account_id(self):
if not self.env.context.get("default_debit_account_id"):
invoice_types = ("out_invoice", "in_refund")
for spread in self.filtered(lambda s: s.invoice_type in invoice_types):
debit_account = spread.company_id.default_spread_revenue_account_id
spread.debit_account_id = debit_account
@api.depends("invoice_type", "company_id")
def _compute_credit_account_id(self):
if not self.env.context.get("default_credit_account_id"):
invoice_types = ("in_invoice", "out_refund")
for spread in self.filtered(lambda s: s.invoice_type in invoice_types):
credit_account = spread.company_id.default_spread_expense_account_id
spread.credit_account_id = credit_account
@api.constrains("invoice_id", "invoice_type")
def _check_invoice_type(self):
if self.filtered(
lambda s: s.invoice_id and s.invoice_type != s.invoice_id.type
):
raise ValidationError(
_("The Invoice Type does not correspond to the Invoice")
)
@api.constrains("journal_id")
def _check_journal(self):
for spread in self:
moves = spread.mapped("line_ids.move_id").filtered("journal_id")
if any(move.journal_id != spread.journal_id for move in moves):
err_msg = _("The Journal is not consistent with the account moves.")
raise ValidationError(err_msg)
@api.constrains("template_id", "invoice_type")
def _check_template_invoice_type(self):
for spread in self.filtered(lambda s: s.template_id.spread_type == "sale"):
if spread.invoice_type in ["in_invoice", "in_refund"]:
err_msg = _(
"The Spread Template (Sales) is not compatible "
"with selected invoice type"
)
raise ValidationError(err_msg)
for spread in self.filtered(lambda s: s.template_id.spread_type == "purchase"):
if spread.invoice_type in ["out_invoice", "out_refund"]:
err_msg = _(
"The Spread Template (Purchases) is not compatible "
"with selected invoice type"
)
raise ValidationError(err_msg)
def _get_spread_period_duration(self):
"""Converts the selected period_type to number of months."""
self.ensure_one()
if self.period_type == "year":
return 12
elif self.period_type == "quarter":
return 3
return 1
def _init_line_date(self, posted_line_ids):
"""Calculates the initial spread date. This method
is used by "def _compute_spread_board()" method.
"""
self.ensure_one()
if posted_line_ids:
# if we already have some previous validated entries,
# starting date is last entry + method period
last_date = posted_line_ids[-1].date
months = self._get_spread_period_duration()
spread_date = last_date + relativedelta(months=months)
else:
spread_date = self.spread_date
return spread_date
def _next_line_date(self, month_day, date):
"""Calculates the next spread date. This method
is used by "def _compute_spread_board()" method.
"""
self.ensure_one()
months = self._get_spread_period_duration()
date = date + relativedelta(months=months)
# get the last day of the month
if month_day > 28:
max_day_in_month = calendar.monthrange(date.year, date.month)[1]
date = date.replace(day=min(max_day_in_month, month_day))
return date
def _compute_spread_board(self):
"""Creates the spread lines. This method is highly inspired
from method compute_depreciation_board() present in standard
Odoo 11.0 "account_asset" module, developed by Odoo SA.
"""
self.ensure_one()
posted_line_ids = self.line_ids.filtered(
lambda x: x.move_id.state == "posted"
).sorted(key=lambda l: l.date)
unposted_line_ids = self.line_ids.filtered(
lambda x: not x.move_id.state == "posted"
)
# Remove old unposted spread lines.
commands = [(2, line_id.id, False) for line_id in unposted_line_ids]
if self.unposted_amount != 0.0:
unposted_amount = self.unposted_amount
spread_date = self._init_line_date(posted_line_ids)
month_day = spread_date.day
number_of_periods = self._get_number_of_periods(month_day)
for x in range(len(posted_line_ids), number_of_periods):
sequence = x + 1
amount = self._compute_board_amount(
sequence, unposted_amount, number_of_periods
)
amount = self.currency_id.round(amount)
rounding = self.currency_id.rounding
if float_is_zero(amount, precision_rounding=rounding):
continue
unposted_amount -= amount
vals = {
"amount": amount,
"spread_id": self.id,
"name": self._get_spread_entry_name(sequence),
"date": self._get_last_day_of_month(spread_date),
}
commands.append((0, False, vals))
spread_date = self._next_line_date(month_day, spread_date)
self.write({"line_ids": commands})
invoice_type_selection = dict(
self.fields_get(allfields=["invoice_type"])["invoice_type"]["selection"]
)[self.invoice_type]
msg_body = _("Spread table '%s' created.") % invoice_type_selection
self.message_post(body=msg_body)
def _get_number_of_periods(self, month_day):
"""Calculates the number of spread lines."""
self.ensure_one()
return self.period_number + 1 if month_day != 1 else self.period_number
@staticmethod
def _get_last_day_of_month(spread_date):
return spread_date + relativedelta(day=31)
def _compute_board_amount(self, sequence, amount, number_of_periods):
"""Calculates the amount for the spread lines."""
self.ensure_one()
amount_to_spread = self.total_amount
if sequence != number_of_periods:
amount = amount_to_spread / self.period_number
if sequence == 1:
date = self.spread_date
month_days = calendar.monthrange(date.year, date.month)[1]
days = month_days - date.day + 1
period = self.period_number
amount = (amount_to_spread / period) / month_days * days
return amount
def compute_spread_board(self):
"""Checks whether the spread lines should be calculated.
In case checks pass, invoke "def _compute_spread_board()" method.
"""
for spread in self.filtered(lambda s: s.total_amount):
spread._compute_spread_board()
def action_recalculate_spread(self):
"""Recalculate spread"""
self.ensure_one()
spread_lines = self.mapped("line_ids").filtered("move_id")
spread_lines.unlink_move()
self.compute_spread_board()
self.env["account.spread.line"]._create_entries()
def action_undo_spread(self):
"""Undo spreading: Remove all created moves"""
self.ensure_one()
self.mapped("line_ids").filtered("move_id").unlink_move()
self.mapped("line_ids").unlink()
def action_unlink_invoice_line(self):
"""Unlink the invoice line from the spread board"""
self.ensure_one()
if self.invoice_id.state != "draft":
msg = _("Cannot unlink invoice lines if the invoice is validated")
raise UserError(msg)
self._action_unlink_invoice_line()
def _action_unlink_invoice_line(self):
self.mapped("line_ids.move_id.line_ids").remove_move_reconcile()
self._message_post_unlink_invoice_line()
self.write({"invoice_line_ids": [(5, 0, 0)]})
def _message_post_unlink_invoice_line(self):
for spread in self:
inv_link = (
"<a href=# data-oe-model=account.move "
"data-oe-id=%d>%s</a>" % (spread.invoice_id.id, _("Invoice"))
)
msg_body = _("Unlinked invoice line '%s' (view %s).") % (
spread.invoice_line_id.name,
inv_link,
)
spread.message_post(body=msg_body)
spread_link = (
"<a href=# data-oe-model=account.spread "
"data-oe-id=%d>%s</a>" % (spread.id, _("Spread"))
)
msg_body = _("Unlinked '%s' (invoice line %s).") % (
spread_link,
spread.invoice_line_id.name,
)
spread.invoice_id.message_post(body=msg_body)
def unlink(self):
if self.filtered(lambda s: s.invoice_line_id):
err_msg = _("Cannot delete spread(s) that are linked to an invoice line.")
raise UserError(err_msg)
if self.mapped("line_ids.move_id").filtered(lambda m: m.state == "posted"):
err_msg = _("Cannot delete spread(s): there are posted Journal Entries.")
raise ValidationError(err_msg)
return super().unlink()
def reconcile_spread_moves(self):
for spread in self:
spread._reconcile_spread_moves()
def _reconcile_spread_moves(self, created_moves=False):
"""Reconcile spread moves if possible"""
self.ensure_one()
spread_mls = self.line_ids.mapped("move_id.line_ids")
if created_moves:
spread_mls |= created_moves.mapped("line_ids")
account = self.invoice_line_id.account_id
mls_to_reconcile = spread_mls.filtered(lambda l: l.account_id == account)
if mls_to_reconcile:
do_reconcile = mls_to_reconcile + self.invoice_line_id
do_reconcile.remove_move_reconcile()
do_reconcile._check_spread_reconcile_validity()
do_reconcile.reconcile()
def create_all_moves(self):
for line in self.mapped("line_ids").filtered(lambda l: not l.move_id):
line.create_move()
def _post_spread_moves(self, moves):
self.ensure_one()
if not moves:
return
ctx = dict(self.env.context, skip_unique_sequence_number=True)
if self.company_id.force_move_auto_post or self.move_line_auto_post:
moves.with_context(ctx).post()
@api.depends("debit_account_id.deprecated", "credit_account_id.deprecated")
def _compute_deprecated_accounts(self):
for spread in self:
spread.is_debit_account_deprecated = spread.debit_account_id.deprecated
spread.is_credit_account_deprecated = spread.credit_account_id.deprecated
def open_reconcile_view(self):
action_name = "account_spread_cost_revenue.action_account_moves_all_spread"
[action] = self.env.ref(action_name).read()
action["domain"] = [("id", "in", [])]
spread_mls = self.line_ids.mapped("move_id.line_ids")
spread_mls = spread_mls.filtered(lambda m: m.reconciled)
if spread_mls:
domain = [("id", "in", spread_mls.ids + [self.invoice_line_id.id])]
action["domain"] = domain
return action

View File

@@ -0,0 +1,178 @@
# Copyright 2016-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountInvoiceSpreadLine(models.Model):
_name = "account.spread.line"
_description = "Account Spread Lines"
_order = "date"
name = fields.Char("Description", readonly=True)
amount = fields.Float(digits="Account", required=True)
date = fields.Date(required=True)
spread_id = fields.Many2one("account.spread", ondelete="cascade")
move_id = fields.Many2one("account.move", string="Journal Entry", readonly=True)
def create_and_reconcile_moves(self):
grouped_lines = {}
for spread_line in self:
spread = spread_line.spread_id
spread_line_list = grouped_lines.get(
spread, self.env["account.spread.line"]
)
grouped_lines.update({spread: spread_line_list + spread_line})
for spread in grouped_lines:
created_moves = grouped_lines[spread]._create_moves()
if created_moves:
post_msg = _("Created move(s) ")
post_msg += ", ".join(
"<a href=# data-oe-model=account.move data-oe-id=%d"
">%s</a>" % (move.id, move.name)
for move in created_moves
)
spread.message_post(body=post_msg)
if spread.invoice_id.state == "posted":
spread._reconcile_spread_moves(created_moves)
spread._post_spread_moves(created_moves)
def create_move(self):
"""Button to manually create a move from a spread line entry."""
self.ensure_one()
self.create_and_reconcile_moves()
def _create_moves(self):
if self.filtered(lambda l: l.move_id):
raise UserError(
_(
"This spread line is already linked to a "
"journal entry! Please post or delete it."
)
)
created_moves = self.env["account.move"]
for line in self:
move_vals = line._prepare_move()
move = self.env["account.move"].create(move_vals)
line.move_id = move
created_moves += move
return created_moves
def _prepare_move(self):
self.ensure_one()
spread_date = self.env.context.get("spread_date") or self.date
spread = self.spread_id
analytic = spread.account_analytic_id
analytic_tags = []
if self.env["account.analytic.tag"].check_access_rights(
"read", raise_exception=False
):
analytic_tags = [(6, 0, spread.analytic_tag_ids.ids)]
company_currency = spread.company_id.currency_id
current_currency = spread.currency_id
not_same_curr = company_currency != current_currency
amount = current_currency._convert(
self.amount, company_currency, spread.company_id, spread_date
)
debit_credit = spread.invoice_type in ["in_invoice", "out_refund"]
line_ids = [
(
0,
0,
{
"name": spread.name,
"account_id": spread.debit_account_id.id
if debit_credit
else spread.credit_account_id.id,
"debit": amount if amount > 0.0 else 0.0,
"credit": -amount if amount < 0.0 else 0.0,
"partner_id": self.spread_id.invoice_id.partner_id.id,
"analytic_account_id": analytic.id,
"analytic_tag_ids": analytic_tags,
"currency_id": not_same_curr and current_currency.id or False,
"amount_currency": not_same_curr and -1.0 * self.amount or 0.0,
},
),
(
0,
0,
{
"name": spread.name,
"account_id": spread.credit_account_id.id
if debit_credit
else spread.debit_account_id.id,
"credit": amount if amount > 0.0 else 0.0,
"debit": -amount if amount < 0.0 else 0.0,
"partner_id": self.spread_id.invoice_id.partner_id.id,
"analytic_account_id": analytic.id,
"analytic_tag_ids": analytic_tags,
"currency_id": not_same_curr and current_currency.id or False,
"amount_currency": not_same_curr and self.amount or 0.0,
},
),
]
return {
"name": self.name or "/",
"ref": self.name,
"date": spread_date,
"journal_id": spread.journal_id.id,
"line_ids": line_ids,
"company_id": spread.company_id.id,
"partner_id": spread.invoice_id.partner_id.id,
}
def open_move(self):
"""Used by a button to manually view a move from a spread line entry."""
self.ensure_one()
return {
"name": _("Journal Entry"),
"view_mode": "form",
"res_model": "account.move",
"view_id": False,
"type": "ir.actions.act_window",
"res_id": self.move_id.id,
}
def unlink_move(self):
"""Used by a button to manually unlink a move from a spread line entry."""
for line in self:
move = line.move_id
if move.state == "posted":
move.button_cancel()
move.line_ids.remove_move_reconcile()
post_msg = _("Deleted move %s") % line.move_id.id
move.with_context(force_delete=True).unlink()
line.move_id = False
line.spread_id.message_post(body=post_msg)
@api.model
def _create_entries(self):
"""Find spread line entries where date is in the past and
create moves for them. Method also called by the cron job.
"""
lines = self.search(
[("date", "<=", fields.Date.today()), ("move_id", "=", False)]
)
lines.create_and_reconcile_moves()
unposted_moves = (
self.search([("move_id", "!=", False)])
.mapped("move_id")
.filtered(lambda m: m.state != "posted")
)
unposted_moves.filtered(lambda m: m.company_id.force_move_auto_post).post()
spreads_to_archive = (
self.env["account.spread"]
.search([("all_posted", "=", True)])
.filtered(lambda s: s.company_id.auto_archive_spread)
)
spreads_to_archive.write({"active": False})

View File

@@ -0,0 +1,187 @@
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountSpreadTemplate(models.Model):
_name = "account.spread.template"
_description = "Account Spread Template"
name = fields.Char(required=True)
spread_type = fields.Selection(
[("sale", "Customer"), ("purchase", "Supplier")], default="sale", required=True
)
company_id = fields.Many2one(
"res.company", default=lambda self: self.env.company, required=True
)
spread_journal_id = fields.Many2one(
"account.journal",
string="Journal",
compute="_compute_spread_journal",
readonly=False,
store=True,
required=True,
)
use_invoice_line_account = fields.Boolean(
string="Invoice account as spread account",
help="Use invoice line's account as Balance sheet / spread account.\n"
"In this case, user need to select expense/revenue account too.",
)
spread_account_id = fields.Many2one(
"account.account",
string="Spread Balance Sheet Account",
compute="_compute_spread_account",
readonly=False,
store=True,
required=False,
)
exp_rev_account_id = fields.Many2one(
"account.account",
string="Expense/Revenue Account",
help="Optional account to overwrite the existing expense/revenue account",
)
period_number = fields.Integer(
string="Number of Repetitions", help="Define the number of spread lines"
)
period_type = fields.Selection(
[("month", "Month"), ("quarter", "Quarter"), ("year", "Year")],
help="Period length for the entries",
)
start_date = fields.Date()
auto_spread = fields.Boolean(
string="Auto assign template on invoice validate",
help="If checked, provide option to auto create spread during "
"invoice validation, based on product/account/analytic in invoice line.",
)
auto_spread_ids = fields.One2many(
comodel_name="account.spread.template.auto",
string="Auto Spread On",
inverse_name="template_id",
)
@api.model
def default_get(self, fields):
res = super().default_get(fields)
if not res.get("company_id"):
res["company_id"] = self.env.company.id
if "spread_journal_id" not in res:
default_journal = self.env["account.spread"].default_journal(
res["company_id"]
)
if default_journal:
res["spread_journal_id"] = default_journal.id
return res
@api.constrains("auto_spread", "auto_spread_ids")
def _check_product_account(self):
for rec in self.filtered("auto_spread"):
for line in rec.auto_spread_ids:
if not line.product_id and not line.account_id:
raise UserError(
_(
"Please select product and/or account "
"on auto spread options"
)
)
@api.depends("spread_type", "company_id")
def _compute_spread_journal(self):
for spread in self:
company = spread.company_id
if spread.spread_type == "sale":
journal = company.default_spread_revenue_journal_id
else:
journal = company.default_spread_expense_journal_id
if journal:
spread.spread_journal_id = journal
@api.depends("spread_type", "company_id")
def _compute_spread_account(self):
for spread in self:
company = spread.company_id
if spread.spread_type == "sale":
account = company.default_spread_revenue_account_id
else:
account = company.default_spread_expense_account_id
if account:
spread.spread_account_id = account
@api.onchange("use_invoice_line_account")
def _onchange_user_invoice_line_account(self):
self.exp_rev_account_id = False
def _prepare_spread_from_template(self, spread_account_id=False):
self.ensure_one()
company = self.company_id
spread_vals = {
"name": self.name,
"template_id": self.id,
"journal_id": self.spread_journal_id.id,
"use_invoice_line_account": self.use_invoice_line_account,
"company_id": company.id,
}
account_id = spread_account_id or self.spread_account_id.id
if self.spread_type == "sale":
invoice_type = "out_invoice"
spread_vals["debit_account_id"] = account_id
else:
invoice_type = "in_invoice"
spread_vals["credit_account_id"] = account_id
if self.period_number:
spread_vals["period_number"] = self.period_number
if self.period_type:
spread_vals["period_type"] = self.period_type
if self.start_date:
spread_vals["spread_date"] = self.start_date
spread_vals["invoice_type"] = invoice_type
return spread_vals
@api.constrains("auto_spread_ids", "auto_spread")
def _check_auto_spread_ids_unique(self):
query = """
select product_id, account_id, analytic_account_id
from (
select product_id, account_id, analytic_account_id, count(*)
from account_spread_template_auto a
join account_spread_template b on a.template_id = b.id
where b.auto_spread = true and b.id in %s
group by product_id, account_id, analytic_account_id
) x where x.count > 1 """
self._cr.execute(query, [self._ids])
results = []
for res in self._cr.fetchall():
product = self.env["product.product"].browse(res[0])
account = self.env["account.account"].browse(res[1])
analytic = self.env["account.analytic.account"].browse(res[2])
results.append(
"{} / {} / {}".format(product.name, account.name, analytic.name)
)
if results:
raise UserError(
_("Followings are duplicated combinations,\n\n%s" % "\n".join(results))
)
class AccountSpreadTemplateAuto(models.Model):
_name = "account.spread.template.auto"
_description = "Auto create spread, based on product/account/analytic"
template_id = fields.Many2one(
comodel_name="account.spread.template",
string="Spread Template",
required=True,
ondelete="cascade",
index=True,
)
company_id = fields.Many2one(related="template_id.company_id", store=True,)
name = fields.Char(required=True, default="/",)
product_id = fields.Many2one(comodel_name="product.product", string="Product",)
account_id = fields.Many2one(comodel_name="account.account", string="Account",)
analytic_account_id = fields.Many2one(
comodel_name="account.analytic.account", string="Analytic",
)

View File

@@ -0,0 +1,40 @@
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
default_spread_revenue_account_id = fields.Many2one(
"account.account", string="Revenue Spread Account"
)
default_spread_expense_account_id = fields.Many2one(
"account.account", string="Expense Spread Account"
)
default_spread_revenue_journal_id = fields.Many2one(
"account.journal", string="Revenue Spread Journal"
)
default_spread_expense_journal_id = fields.Many2one(
"account.journal", string="Expense Spread Journal"
)
allow_spread_planning = fields.Boolean(
default=True,
help="Disable this option if you do not want to allow the "
"spreading before the invoice is validated.",
)
force_move_auto_post = fields.Boolean(
"Auto-post spread lines",
help="Enable this option if you want to post automatically the "
"accounting moves of all the spreads.",
)
auto_archive_spread = fields.Boolean(
"Auto-archive spread",
help="Enable this option if you want the cron job to automatically "
"archive the spreads when all lines are posted.",
)

View File

@@ -0,0 +1,25 @@
To be able to access the full spreading features, the user must belong to *Show Full Accounting Features* group.
On the form view of the company, in the *Account Spread* tab, you can configure
the journals in which the spread journal items will be generated by default:
* the *Default Spread Journal for Revenues*,
* the *Default Spread Journal for Expenses*.
In the same *Account Spread* tab, you can also configure the Spread Balance Sheet Accounts used by default:
* the *Default Spread Account for Revenues*,
* the *Default Spread Account for Expenses*.
This module by default allows the spreading even before the receipt of the invoice or when the invoice is still draft,
so that it is possible to work on the plan of the cost/revenue spreading. To disable this feature, on the form view of
the company disable the *Allow Spread Planning* option.
In Spread Template, there is also option to *Auto assign template on invoice validate*, based on the preset invoice line criteria.
On the form view of the company, the *Auto-post spread lines* option forces the account moves created
during the cost/revenue spreading to be automatically posted. When this option is false, the user can
enable/disable the automatic posting by the flag *Auto-post lines* present in the spread board.
On the form view of the company, enable the *Auto-archive spread* option if you want the
cron job to automatically archive the spreads when all lines are posted.

View File

@@ -0,0 +1,2 @@
* Andrea Stirpe <a.stirpe@onestein.nl>
* Kitti U. <kittiu@ecosoft.co.th>

View File

@@ -0,0 +1,3 @@
Part of the code in this module (in particular the computation of the spread lines)
is highly inspired by the Assets Management module from the standard
Odoo 11.0 Community developed by Odoo SA.

View File

@@ -0,0 +1 @@
Allows to spread costs or revenues over a customizable periods, to even out cost or invoice spikes.

View File

@@ -0,0 +1,28 @@
13.0.1.0.0
~~~~~~~~~~
* [MIG] Port account_spread_cost_revenue to V13.
12.0.2.0.0
~~~~~~~~~~
* [ENH] In spread template, add option to auto create spread on invoice validation
12.0.1.1.0
~~~~~~~~~~
* [ENH] Add optional Expense/Revenue Account in Chart Template, which can be used
in place of account from invoice line to set Expense/Revenue account in the spread
12.0.1.0.0
~~~~~~~~~~
* [MIG] Port account_spread_cost_revenue to V12.
11.0.1.0.0
~~~~~~~~~~
* [ADD] Module account_spread_cost_revenue.
(`#715 <https://github.com/OCA/account-financial-tools/pull/715>`_)

View File

@@ -0,0 +1,79 @@
Define Spread Costs/Revenues Board
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Under Invoicing -> Adviser -> Accounting Entries -> Spread Costs/Revenues, create a new spread board.
Complete the definition of the spreading criteria, by setting the the fields:
* *Debit Account*
* *Credit Account*
* *Estimated Amount* (The total amount to spread)
* *Number of Repetitions*
* *Period Type* (Duration of each period)
* *Start date*
* *Journal*
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/spread.png
:alt: Create a new spread board
Click on the button on the top-left to calculate the spread lines.
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/create_spread.png
:alt: The spreading board is defined
A cron job will automatically create the accounting moves for all the lines having date previous that the current day (today).
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/update_spread.png
:alt: The spreading board is updated by the cron job
By default, the status of the created accounting moves is posted.
To disable the automatic posting of the accounting moves, set the flag *Auto-post lines* to False.
This flag is only available when the *Auto-post spread lines* option, present on the form view of the company, is disabled.
Click on button *Recalculate entire spread* button in the spread board to force the recalculation of the spread lines:
this will also reset all the journal entries previously created.
Link Invoice to Spread Costs/Revenues Board
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Create an invoice or vendor bill in draft. On its lines, the spreading right-arrow icon are displayed in dark-grey color.
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/invoice_line_1.png
:alt: On the invoice line the spreading icon is displayed
Click on the spreading right-arrow icon. A wizard prompts to enter a *Spread Action Type*:
- *Link to existing spread board*
- *Create from spread template*
- *Create new spread board*
Select *Link to existing spread board* and enter the previously generated Spread Board. Click on Confirm button:
the selected Spread Board will be automatically displayed.
Go back to the draft invoice/bill. The spreading functionality is now enabled on the invoice line:
the spreading right-arrow icon is now displayed in green color.
.. figure:: https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/invoice_line_2.png
:alt: On the invoice line the spreading icon is displayed in green color
Validate the invoice/bill. Click on the spreading (green) right-arrow icon to open the spread board, then click
on the smart button *Reconciled entries*: the moves of the spread lines are reconciled with the move of the invoice line.
In case the Subtotal Price of the invoice line is different than the *Estimated Amount* of the spread board, the spread
lines (not yet posted) will be recalculated when validating the invoice/bill.
Define Spread Costs/Revenues Template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Under Invoicing -> Configuration -> Accounting -> Spread Templates, create a new spread template.
* *Spread Type*
* *Spread Balance Sheet Account*
* *Expense/Revenue Account* This option visible if invoice line account is balance sheet account, user need to specify this too.
* *Journal*
* *Auto assign template on invoice validate*
When creating a new Spread Costs/Revenues Board, select the right template.
This way the above fields will be copied to the Spread Board.
If *Auto assign template on invoice validate* is checked, this template will be used to auto create spread, if the underlining invoice match the preset product/account/analytic criteria.

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data noupdate="1">
<record id="account_spread_multi_company_rule" model="ir.rule">
<field name="name">Account Spread multi-company</field>
<field ref="model_account_spread" name="model_id" />
<field eval="True" name="global" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="account_spread_template_multi_company_rule" model="ir.rule">
<field name="name">Account Spread Template multi-company</field>
<field ref="model_account_spread_template" name="model_id" />
<field eval="True" name="global" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
<record id="account_spread_template_auto_multi_company_rule" model="ir.rule">
<field name="name">Account Spread Tempalte Auto multi-company</field>
<field ref="model_account_spread_template_auto" name="model_id" />
<field eval="True" name="global" />
<field
name="domain_force"
>['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_spread_cost_revenue_full,Full access on account.spread,model_account_spread,account.group_account_manager,1,1,1,1
access_account_spread_cost_revenue_read,Read access on account.spread,model_account_spread,account.group_account_user,1,0,0,0
access_account_spread_cost_revenue_line_full,Full access on account.spread.line,model_account_spread_line,account.group_account_manager,1,1,1,1
access_account_spread_cost_revenue_line_read,Read access on account.spread.line,model_account_spread_line,account.group_account_user,1,0,0,0
access_account_spread_cost_revenue_template_full,Full access on account.spread.template,model_account_spread_template,account.group_account_manager,1,1,1,1
access_account_spread_cost_revenue_template_read,Read access on account.spread.template,model_account_spread_template,account.group_account_user,1,0,0,0
access_account_spread_cost_revenue_template_auto_full,Full access on account.spread.template.auto,model_account_spread_template_auto,account.group_account_manager,1,1,1,1
access_account_spread_cost_revenue_template_auto_read,Read access on account.spread.template.auto,model_account_spread_template_auto,account.group_account_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_spread_cost_revenue_full Full access on account.spread model_account_spread account.group_account_manager 1 1 1 1
3 access_account_spread_cost_revenue_read Read access on account.spread model_account_spread account.group_account_user 1 0 0 0
4 access_account_spread_cost_revenue_line_full Full access on account.spread.line model_account_spread_line account.group_account_manager 1 1 1 1
5 access_account_spread_cost_revenue_line_read Read access on account.spread.line model_account_spread_line account.group_account_user 1 0 0 0
6 access_account_spread_cost_revenue_template_full Full access on account.spread.template model_account_spread_template account.group_account_manager 1 1 1 1
7 access_account_spread_cost_revenue_template_read Read access on account.spread.template model_account_spread_template account.group_account_user 1 0 0 0
8 access_account_spread_cost_revenue_template_auto_full Full access on account.spread.template.auto model_account_spread_template_auto account.group_account_manager 1 1 1 1
9 access_account_spread_cost_revenue_template_auto_read Read access on account.spread.template.auto model_account_spread_template_auto account.group_account_user 1 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1,557 @@
<?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 0.15.1: http://docutils.sourceforge.net/" />
<title>Cost-Revenue Spread</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="cost-revenue-spread">
<h1 class="title">Cost-Revenue Spread</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-financial-tools/tree/12.0/account_spread_cost_revenue"><img alt="OCA/account-financial-tools" src="https://img.shields.io/badge/github-OCA%2Faccount--financial--tools-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/account-financial-tools-12-0/account-financial-tools-12-0-account_spread_cost_revenue"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/92/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>Allows to spread costs or revenues over a customizable periods, to even out cost or invoice spikes.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id5">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id6">Usage</a><ul>
<li><a class="reference internal" href="#define-spread-costs-revenues-board" id="id7">Define Spread Costs/Revenues Board</a></li>
<li><a class="reference internal" href="#link-invoice-to-spread-costs-revenues-board" id="id8">Link Invoice to Spread Costs/Revenues Board</a></li>
<li><a class="reference internal" href="#define-spread-costs-revenues-template" id="id9">Define Spread Costs/Revenues Template</a></li>
</ul>
</li>
<li><a class="reference internal" href="#changelog" id="id10">Changelog</a><ul>
<li><a class="reference internal" href="#id1" id="id11">12.0.1.1.0</a></li>
<li><a class="reference internal" href="#id2" id="id12">12.0.1.0.0</a></li>
<li><a class="reference internal" href="#id3" id="id13">11.0.1.0.0</a></li>
</ul>
</li>
<li><a class="reference internal" href="#bug-tracker" id="id14">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id15">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id16">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id17">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id18">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="id19">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id5">Configuration</a></h1>
<p>To be able to access the full spreading features, the user must belong to <em>Show Full Accounting Features</em> group.</p>
<p>On the form view of the company, in the <em>Account Spread</em> tab, you can configure
the journals in which the spread journal items will be generated by default:</p>
<ul class="simple">
<li>the <em>Default Spread Journal for Revenues</em>,</li>
<li>the <em>Default Spread Journal for Expenses</em>.</li>
</ul>
<p>In the same <em>Account Spread</em> tab, you can also configure the Spread Balance Sheet Accounts used by default:</p>
<ul class="simple">
<li>the <em>Default Spread Account for Revenues</em>,</li>
<li>the <em>Default Spread Account for Expenses</em>.</li>
</ul>
<p>This module by default allows the spreading even before the receipt of the invoice or when the invoice is still draft,
so that it is possible to work on the plan of the cost/revenue spreading. To disable this feature, on the form view of
the company disable the <em>Allow Spread Planning</em> option.</p>
<p>On the form view of the company, the <em>Auto-post spread lines</em> option forces the account moves created
during the cost/revenue spreading to be automatically posted. When this option is false, the user can
enable/disable the automatic posting by the flag <em>Auto-post lines</em> present in the spread board.</p>
<p>On the form view of the company, enable the <em>Auto-archive spread</em> option if you want the
cron job to automatically archive the spreads when all lines are posted.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id6">Usage</a></h1>
<div class="section" id="define-spread-costs-revenues-board">
<h2><a class="toc-backref" href="#id7">Define Spread Costs/Revenues Board</a></h2>
<p>Under Invoicing -&gt; Adviser -&gt; Accounting Entries -&gt; Spread Costs/Revenues, create a new spread board.</p>
<p>Complete the definition of the spreading criteria, by setting the the fields:</p>
<ul class="simple">
<li><em>Debit Account</em></li>
<li><em>Credit Account</em></li>
<li><em>Estimated Amount</em> (The total amount to spread)</li>
<li><em>Number of Repetitions</em></li>
<li><em>Period Type</em> (Duration of each period)</li>
<li><em>Start date</em></li>
<li><em>Journal</em></li>
</ul>
<div class="figure">
<img alt="Create a new spread board" src="https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/spread.png" />
</div>
<p>Click on the button on the top-left to calculate the spread lines.</p>
<div class="figure">
<img alt="The spreading board is defined" src="https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/create_spread.png" />
</div>
<p>A cron job will automatically create the accounting moves for all the lines having date previous that the current day (today).</p>
<div class="figure">
<img alt="The spreading board is updated by the cron job" src="https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/update_spread.png" />
</div>
<p>By default, the status of the created accounting moves is posted.
To disable the automatic posting of the accounting moves, set the flag <em>Auto-post lines</em> to False.
This flag is only available when the <em>Auto-post spread lines</em> option, present on the form view of the company, is disabled.</p>
<p>Click on button <em>Recalculate entire spread</em> button in the spread board to force the recalculation of the spread lines:
this will also reset all the journal entries previously created.</p>
</div>
<div class="section" id="link-invoice-to-spread-costs-revenues-board">
<h2><a class="toc-backref" href="#id8">Link Invoice to Spread Costs/Revenues Board</a></h2>
<p>Create an invoice or vendor bill in draft. On its lines, the spreading right-arrow icon are displayed in dark-grey color.</p>
<div class="figure">
<img alt="On the invoice line the spreading icon is displayed" src="https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/invoice_line_1.png" />
</div>
<p>Click on the spreading right-arrow icon. A wizard prompts to enter a <em>Spread Action Type</em>:</p>
<ul class="simple">
<li><em>Link to existing spread board</em></li>
<li><em>Create from spread template</em></li>
<li><em>Create new spread board</em></li>
</ul>
<p>Select <em>Link to existing spread board</em> and enter the previously generated Spread Board. Click on Confirm button:
the selected Spread Board will be automatically displayed.</p>
<p>Go back to the draft invoice/bill. The spreading functionality is now enabled on the invoice line:
the spreading right-arrow icon is now displayed in green color.</p>
<div class="figure">
<img alt="On the invoice line the spreading icon is displayed in green color" src="https://raw.githubusercontent.com/OCA/account-financial-tools/12.0/account_spread_cost_revenue/static/description/invoice_line_2.png" />
</div>
<p>Validate the invoice/bill. Click on the spreading (green) right-arrow icon to open the spread board, then click
on the smart button <em>Reconciled entries</em>: the moves of the spread lines are reconciled with the move of the invoice line.</p>
<p>In case the Subtotal Price of the invoice line is different than the <em>Estimated Amount</em> of the spread board, the spread
lines (not yet posted) will be recalculated when validating the invoice/bill.</p>
</div>
<div class="section" id="define-spread-costs-revenues-template">
<h2><a class="toc-backref" href="#id9">Define Spread Costs/Revenues Template</a></h2>
<p>Under Invoicing -&gt; Configuration -&gt; Accounting -&gt; Spread Templates, create a new spread template.</p>
<ul class="simple">
<li><em>Spread Type</em></li>
<li><em>Spread Balance Sheet Account</em></li>
<li><em>Expense/Revenue Account</em> This option visible if invoice line account is balance sheet account, user need to specify this too.</li>
<li><em>Journal</em></li>
</ul>
<p>When creating a new Spread Costs/Revenues Board, select the right template.
This way the above fields will be copied to the Spread Board.</p>
</div>
</div>
<div class="section" id="changelog">
<h1><a class="toc-backref" href="#id10">Changelog</a></h1>
<div class="section" id="id1">
<h2><a class="toc-backref" href="#id11">12.0.1.1.0</a></h2>
<ul class="simple">
<li>[ENH] Add optional Expense/Revenue Account in Chart Template, which can be used
in place of account from invoice line to set Expense/Revenue account in the spread</li>
</ul>
</div>
<div class="section" id="id2">
<h2><a class="toc-backref" href="#id12">12.0.1.0.0</a></h2>
<ul class="simple">
<li>[MIG] Port account_spread_cost_revenue to V12.</li>
</ul>
</div>
<div class="section" id="id3">
<h2><a class="toc-backref" href="#id13">11.0.1.0.0</a></h2>
<ul class="simple">
<li>[ADD] Module account_spread_cost_revenue.
(<a class="reference external" href="https://github.com/OCA/account-financial-tools/pull/715">#715</a>)</li>
</ul>
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id14">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account-financial-tools/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-financial-tools/issues/new?body=module:%20account_spread_cost_revenue%0Aversion:%2012.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="#id15">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id16">Authors</a></h2>
<ul class="simple">
<li>Onestein</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id17">Contributors</a></h2>
<ul class="simple">
<li>Andrea Stirpe &lt;<a class="reference external" href="mailto:a.stirpe&#64;onestein.nl">a.stirpe&#64;onestein.nl</a>&gt;</li>
<li>Kitti U. &lt;<a class="reference external" href="mailto:kittiu&#64;ecosoft.co.th">kittiu&#64;ecosoft.co.th</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id18">Other credits</a></h2>
<p>Part of the code in this module (in particular the computation of the spread lines)
is highly inspired by the Assets Management module from the standard
Odoo 11.0 Community developed by Odoo SA.</p>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id19">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>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external" href="https://github.com/astirpe"><img alt="astirpe" src="https://github.com/astirpe.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-financial-tools/tree/12.0/account_spread_cost_revenue">OCA/account-financial-tools</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -0,0 +1,61 @@
odoo.define("account_spread_cost_revenue.widget", function(require) {
"use strict";
var AbstractField = require("web.AbstractField");
var core = require("web.core");
var registry = require("web.field_registry");
var _t = core._t;
var AccountSpreadWidget = AbstractField.extend({
events: _.extend({}, AbstractField.prototype.events, {
click: "_onClick",
}),
noLabel: true,
/**
* @override
*/
isSet: function() {
return this.value !== "unavailable";
},
/**
* @override
* @private
*/
_render: function() {
var className = "";
var style = "btn fa fa-arrow-circle-right o_spread_line ";
var title = "";
if (this.recordData.spread_check === "linked") {
className = "o_is_linked";
title = _t("Linked to spread");
} else {
title = _t("Not linked to spread");
}
var $button = $("<button/>", {
type: "button",
title: title,
}).addClass(style + className);
this.$el.html($button);
},
/**
* @private
* @param {MouseEvent} event
*/
_onClick: function(event) {
event.stopPropagation();
this.trigger_up("button_clicked", {
attrs: {
name: "spread_details",
type: "object",
},
record: this.record,
});
},
});
registry.add("spread_line_widget", AccountSpreadWidget);
});

View File

@@ -0,0 +1,5 @@
.o_web_client .o_spread_line {
&.o_is_linked {
color: theme-color("success");
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="assets_backend" name="account spread" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link
rel="stylesheet"
href="/account_spread_cost_revenue/static/src/scss/account_spread.scss"
/>
<script
type="text/javascript"
src="/account_spread_cost_revenue/static/src/js/account_spread.js"
/>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,6 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import test_account_spread_cost_revenue
from . import test_compute_spread_board
from . import test_account_invoice_spread
from . import test_account_invoice_auto_spread

View File

@@ -0,0 +1,114 @@
# Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.exceptions import UserError
from odoo.addons.account_spread_cost_revenue.tests.test_account_invoice_spread import (
TestAccountInvoiceSpread,
)
class TestAccountInvoiceAutoSpread(TestAccountInvoiceSpread):
def test_01_no_auto_spread_sheet(self):
self.env["account.spread.template"].create(
{
"name": "test",
"spread_type": "purchase",
"period_number": 5,
"period_type": "month",
"spread_account_id": self.account_payable.id,
"spread_journal_id": self.ref(
"account_spread_cost_revenue.expenses_journal"
),
"auto_spread": False, # Auto Spread = False
"auto_spread_ids": [
(0, 0, {"account_id": self.vendor_bill_line.account_id.id})
],
}
)
self.assertFalse(self.vendor_bill_line.spread_id)
self.vendor_bill.action_post()
self.assertFalse(self.vendor_bill_line.spread_id)
def test_02_new_auto_spread_sheet_purchase(self):
self.env["account.spread.template"].create(
{
"name": "test 1",
"spread_type": "purchase",
"period_number": 5,
"period_type": "month",
"spread_account_id": self.account_payable.id,
"spread_journal_id": self.ref(
"account_spread_cost_revenue.expenses_journal"
),
"auto_spread": True, # Auto Spread
"auto_spread_ids": [
(0, 0, {"account_id": self.vendor_bill_line.account_id.id})
],
}
)
template2 = self.env["account.spread.template"].create(
{
"name": "test 2",
"spread_type": "purchase",
"period_number": 5,
"period_type": "month",
"spread_account_id": self.account_payable.id,
"spread_journal_id": self.ref(
"account_spread_cost_revenue.expenses_journal"
),
"auto_spread": True, # Auto Spread
"auto_spread_ids": [
(0, 0, {"account_id": self.vendor_bill_line.account_id.id})
],
}
)
template2._check_auto_spread_ids_unique()
self.assertFalse(self.vendor_bill_line.spread_id)
with self.assertRaises(UserError): # too many auto_spread_ids matched
self.vendor_bill.action_post()
template2.auto_spread = False # Do not use this template
self.vendor_bill.action_post()
self.assertTrue(self.vendor_bill_line.spread_id)
spread_lines = self.vendor_bill_line.spread_id.line_ids
self.assertTrue(spread_lines)
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
def test_03_new_auto_spread_sheet_sale(self):
self.env["account.spread.template"].create(
{
"name": "test",
"spread_type": "sale",
"period_number": 5,
"period_type": "month",
"spread_account_id": self.account_receivable.id,
"spread_journal_id": self.ref(
"account_spread_cost_revenue.sales_journal"
),
"auto_spread": True, # Auto Spread
"auto_spread_ids": [
(0, 0, {"account_id": self.invoice_line.account_id.id})
],
}
)
self.assertFalse(self.invoice_line.spread_id)
self.sale_invoice.action_post()
self.assertTrue(self.invoice_line.spread_id)
spread_lines = self.invoice_line.spread_id.line_ids
self.assertTrue(spread_lines)
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)

View File

@@ -0,0 +1,787 @@
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import datetime
from odoo import fields
from odoo.exceptions import UserError, ValidationError
from odoo.modules.module import get_resource_path
from odoo.tests import Form, common
from odoo.tools import convert_file
class TestAccountInvoiceSpread(common.TransactionCase):
def _load(self, module, *args):
convert_file(
self.cr,
"account_spread_cost_revenue",
get_resource_path(module, *args),
{},
"init",
False,
"test",
self.registry._assertion_report,
)
def create_account_invoice(self, invoice_type, quantity=1.0, price_unit=1000.0):
""" Create an invoice as in a view by triggering its onchange methods"""
invoice_form = Form(
self.env["account.move"].with_context(default_type=invoice_type)
)
invoice_form.partner_id = self.env["res.partner"].create(
{"name": "Partner Name"}
)
with invoice_form.invoice_line_ids.new() as line:
line.name = "product that costs " + str(price_unit)
line.quantity = quantity
line.price_unit = price_unit
return invoice_form.save()
def setUp(self):
super().setUp()
self._load("account", "test", "account_minimal_test.xml")
self.account_payable = self.env["account.account"].create(
{
"name": "Test account payable",
"code": "321spread",
"user_type_id": self.env.ref(
"account.data_account_type_other_income"
).id,
"reconcile": True,
}
)
self.account_receivable = self.env["account.account"].create(
{
"name": "Test account receivable",
"code": "322spread",
"user_type_id": self.env.ref(
"account.data_account_type_other_income"
).id,
"reconcile": True,
}
)
spread_account_payable = self.env["account.account"].create(
{
"name": "test spread account_payable",
"code": "765spread",
"user_type_id": self.env.ref(
"account.data_account_type_other_income"
).id,
"reconcile": True,
}
)
spread_account_receivable = self.env["account.account"].create(
{
"name": "test spread account_receivable",
"code": "766spread",
"user_type_id": self.env.ref(
"account.data_account_type_other_income"
).id,
"reconcile": True,
}
)
# Invoices
self.vendor_bill = self.create_account_invoice("in_invoice")
self.sale_invoice = self.create_account_invoice("out_invoice")
self.vendor_bill_line = self.vendor_bill.invoice_line_ids[0]
self.invoice_line = self.sale_invoice.invoice_line_ids[0]
# Set accounts to reconcilable
self.vendor_bill_line.account_id.reconcile = True
self.invoice_line.account_id.reconcile = True
analytic_tags = [(6, 0, self.env.ref("analytic.tag_contract").ids)]
self.analytic_account = self.env["account.analytic.account"].create(
{"name": "test account"}
)
self.spread = (
self.env["account.spread"]
.with_context(mail_create_nosubscribe=True)
.create(
[
{
"name": "test",
"debit_account_id": spread_account_payable.id,
"credit_account_id": self.account_payable.id,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 2, 1),
"estimated_amount": 1000.0,
"journal_id": self.vendor_bill.journal_id.id,
"invoice_type": "in_invoice",
"account_analytic_id": self.analytic_account.id,
"analytic_tag_ids": analytic_tags,
}
]
)
)
self.spread2 = self.env["account.spread"].create(
[
{
"name": "test2",
"debit_account_id": spread_account_receivable.id,
"credit_account_id": self.account_receivable.id,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 2, 1),
"estimated_amount": 1000.0,
"journal_id": self.sale_invoice.journal_id.id,
"invoice_type": "out_invoice",
}
]
)
def test_01_wizard_defaults(self):
Wizard = self.env["account.spread.invoice.line.link.wizard"]
wizard1 = Wizard.with_context(
default_invoice_line_id=self.vendor_bill_line.id,
default_company_id=self.env.company.id,
allow_spread_planning=True,
).create({})
self.assertEqual(wizard1.invoice_line_id, self.vendor_bill_line)
self.assertEqual(wizard1.invoice_line_id.move_id, self.vendor_bill)
self.assertEqual(wizard1.invoice_type, "in_invoice")
self.assertFalse(wizard1.spread_id)
self.assertEqual(wizard1.company_id, self.env.company)
self.assertEqual(wizard1.spread_action_type, "link")
self.assertFalse(wizard1.spread_account_id)
self.assertFalse(wizard1.spread_journal_id)
wizard2 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=self.env.company.id,
).create({})
self.assertEqual(wizard2.invoice_line_id, self.invoice_line)
self.assertEqual(wizard2.invoice_line_id.move_id, self.sale_invoice)
self.assertEqual(wizard2.invoice_type, "out_invoice")
self.assertFalse(wizard2.spread_id)
self.assertEqual(wizard2.company_id, self.env.company)
self.assertEqual(wizard2.spread_action_type, "template")
self.assertFalse(wizard2.spread_account_id)
self.assertFalse(wizard2.spread_journal_id)
def test_02_wizard_defaults(self):
Wizard = self.env["account.spread.invoice.line.link.wizard"]
exp_journal = self.ref("account_spread_cost_revenue.expenses_journal")
sales_journal = self.ref("account_spread_cost_revenue.sales_journal")
self.env.company.default_spread_revenue_account_id = self.account_receivable
self.env.company.default_spread_expense_account_id = self.account_payable
self.env.company.default_spread_revenue_journal_id = sales_journal
self.env.company.default_spread_expense_journal_id = exp_journal
self.assertTrue(self.env.company.default_spread_revenue_account_id)
self.assertTrue(self.env.company.default_spread_expense_account_id)
self.assertTrue(self.env.company.default_spread_revenue_journal_id)
self.assertTrue(self.env.company.default_spread_expense_journal_id)
wizard1 = Wizard.with_context(
default_invoice_line_id=self.vendor_bill_line.id,
default_company_id=self.env.company.id,
allow_spread_planning=True,
).create({})
self.assertEqual(wizard1.invoice_line_id, self.vendor_bill_line)
self.assertEqual(wizard1.invoice_line_id.move_id, self.vendor_bill)
self.assertEqual(wizard1.invoice_type, "in_invoice")
self.assertFalse(wizard1.spread_id)
self.assertEqual(wizard1.company_id, self.env.company)
self.assertEqual(wizard1.spread_action_type, "link")
self.assertTrue(wizard1.spread_account_id)
self.assertTrue(wizard1.spread_journal_id)
self.assertEqual(wizard1.spread_account_id, self.account_payable)
self.assertEqual(wizard1.spread_journal_id.id, exp_journal)
self.assertTrue(wizard1.spread_invoice_type_domain_ids)
wizard2 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=self.env.company.id,
).create({})
self.assertEqual(wizard2.invoice_line_id, self.invoice_line)
self.assertEqual(wizard2.invoice_line_id.move_id, self.sale_invoice)
self.assertEqual(wizard2.invoice_type, "out_invoice")
self.assertFalse(wizard2.spread_id)
self.assertEqual(wizard2.company_id, self.env.company)
self.assertEqual(wizard2.spread_action_type, "template")
self.assertTrue(wizard2.spread_account_id)
self.assertTrue(wizard2.spread_journal_id)
self.assertEqual(wizard2.spread_account_id, self.account_receivable)
self.assertEqual(wizard2.spread_journal_id.id, sales_journal)
self.assertTrue(wizard2.spread_invoice_type_domain_ids)
def test_03_link_invoice_line_with_spread_sheet(self):
self.env.user.write(
{
"groups_id": [
(4, self.env.ref("analytic.group_analytic_accounting").id),
(4, self.env.ref("analytic.group_analytic_tags").id),
],
}
)
Wizard = self.env["account.spread.invoice.line.link.wizard"]
wizard1 = Wizard.with_context(
default_invoice_line_id=self.vendor_bill_line.id,
default_company_id=self.env.company.id,
allow_spread_planning=True,
).create({})
self.assertEqual(wizard1.spread_action_type, "link")
wizard1.spread_account_id = self.account_receivable
wizard1.spread_journal_id = self.ref(
"account_spread_cost_revenue.expenses_journal"
)
wizard1.spread_id = self.spread
res_action = wizard1.confirm()
self.assertTrue(isinstance(res_action, dict))
self.assertTrue(res_action.get("res_id"))
self.assertEqual(res_action.get("res_id"), self.spread.id)
self.assertTrue(self.spread.invoice_line_id)
self.assertEqual(self.spread.invoice_line_id, self.vendor_bill_line)
self.assertFalse(any(line.move_id for line in self.spread.line_ids))
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
for ml in line.move_id.line_ids:
analytic_tag = self.env.ref("analytic.tag_contract")
self.assertEqual(ml.analytic_account_id, self.analytic_account)
self.assertEqual(ml.analytic_tag_ids, analytic_tag)
self.spread.invoice_id.button_cancel()
self.assertTrue(self.spread.invoice_line_id)
with self.assertRaises(UserError):
self.spread.unlink()
with self.assertRaises(UserError):
self.spread.action_unlink_invoice_line()
self.assertTrue(self.spread.invoice_line_id)
def test_04_new_spread_sheet(self):
Wizard = self.env["account.spread.invoice.line.link.wizard"]
spread_journal_id = self.ref("account_spread_cost_revenue.expenses_journal")
wizard1 = Wizard.with_context(
default_invoice_line_id=self.vendor_bill_line.id,
default_company_id=self.env.company.id,
).create({"spread_action_type": "new"})
self.assertEqual(wizard1.spread_action_type, "new")
wizard1.write(
{
"spread_account_id": self.account_receivable.id,
"spread_journal_id": spread_journal_id,
}
)
res_action = wizard1.confirm()
self.assertTrue(isinstance(res_action, dict))
self.assertFalse(res_action.get("res_id"))
self.assertTrue(res_action.get("context"))
res_context = res_action.get("context")
self.assertTrue(res_context.get("default_name"))
self.assertTrue(res_context.get("default_invoice_type"))
self.assertTrue(res_context.get("default_invoice_line_id"))
self.assertTrue(res_context.get("default_debit_account_id"))
self.assertTrue(res_context.get("default_credit_account_id"))
self.assertFalse(any(line.move_id for line in self.spread.line_ids))
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
wizard2 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=self.env.company.id,
).create({"spread_action_type": "new"})
self.assertEqual(wizard2.spread_action_type, "new")
wizard2.write(
{
"spread_account_id": self.account_receivable.id,
"spread_journal_id": spread_journal_id,
}
)
res_action = wizard2.confirm()
self.assertTrue(isinstance(res_action, dict))
self.assertFalse(res_action.get("res_id"))
self.assertTrue(res_action.get("context"))
res_context = res_action.get("context")
self.assertTrue(res_context.get("default_name"))
self.assertTrue(res_context.get("default_invoice_type"))
self.assertTrue(res_context.get("default_invoice_line_id"))
self.assertTrue(res_context.get("default_debit_account_id"))
self.assertTrue(res_context.get("default_credit_account_id"))
self.assertFalse(any(line.move_id for line in self.spread2.line_ids))
self.spread2.compute_spread_board()
for line in self.spread2.line_ids:
line.create_move()
self.assertTrue(line.move_id)
def test_05_new_spread_sheet_from_template(self):
Wizard = self.env["account.spread.invoice.line.link.wizard"]
spread_account = self.account_payable
self.assertTrue(spread_account)
spread_journal_id = self.ref("account_spread_cost_revenue.expenses_journal")
template = self.env["account.spread.template"].create(
{
"name": "test",
"spread_type": "purchase",
"period_number": 5,
"period_type": "month",
"spread_account_id": spread_account.id,
"spread_journal_id": spread_journal_id,
}
)
wizard1 = Wizard.with_context(
default_invoice_line_id=self.vendor_bill_line.id,
default_company_id=self.env.company.id,
).create({"spread_action_type": "template", "template_id": template.id})
self.assertEqual(wizard1.spread_action_type, "template")
res_action = wizard1.confirm()
self.assertTrue(isinstance(res_action, dict))
self.assertTrue(res_action.get("res_id"))
new_spread = self.env["account.spread"].browse(res_action["res_id"])
new_spread.compute_spread_board()
self.assertFalse(any(line.move_id for line in new_spread.line_ids))
for line in new_spread.line_ids:
line.create_move()
self.assertTrue(line.move_id)
wizard2 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=self.env.company.id,
).create({"spread_action_type": "new"})
self.assertEqual(wizard2.spread_action_type, "new")
wizard2.write(
{
"spread_account_id": spread_account.id,
"spread_journal_id": spread_journal_id,
}
)
res_action = wizard2.confirm()
self.assertTrue(isinstance(res_action, dict))
self.assertFalse(res_action.get("res_id"))
self.assertTrue(res_action.get("context"))
res_context = res_action.get("context")
self.assertTrue(res_context.get("default_name"))
self.assertTrue(res_context.get("default_invoice_type"))
self.assertTrue(res_context.get("default_invoice_line_id"))
self.assertTrue(res_context.get("default_debit_account_id"))
self.assertTrue(res_context.get("default_credit_account_id"))
self.assertFalse(any(line.move_id for line in self.spread2.line_ids))
self.spread2.compute_spread_board()
for line in self.spread2.line_ids:
line.create_move()
self.assertTrue(line.move_id)
def test_06_open_wizard(self):
res_action = self.vendor_bill_line.spread_details()
self.assertTrue(isinstance(res_action, dict))
self.assertFalse(res_action.get("res_id"))
self.assertTrue(res_action.get("context"))
def test_07_unlink_invoice_line_and_spread_sheet(self):
self.assertFalse(self.spread.invoice_line_id)
self.vendor_bill_line.spread_id = self.spread
self.assertTrue(self.spread.invoice_line_id)
self.spread.action_unlink_invoice_line()
self.assertFalse(self.spread.invoice_line_id)
self.assertFalse(self.spread2.invoice_line_id)
self.invoice_line.spread_id = self.spread2
self.assertTrue(self.spread2.invoice_line_id)
self.spread2.action_unlink_invoice_line()
self.assertFalse(self.spread2.invoice_line_id)
def test_08_invoice_multi_line(self):
invoice_form = Form(self.vendor_bill)
with invoice_form.invoice_line_ids.new() as line:
line.name = "new test line"
line.quantity = 1.0
line.price_unit = 1000.0
self.invoice = invoice_form.save()
self.assertEqual(len(self.vendor_bill.invoice_line_ids), 2)
self.vendor_bill_line.spread_id = self.spread
self.assertTrue(self.spread.invoice_id.invoice_line_ids[0])
self.assertEqual(
self.spread.invoice_id.invoice_line_ids[0], self.vendor_bill_line
)
self.assertTrue(self.vendor_bill_line.spread_id)
self.assertEqual(self.vendor_bill_line.spread_check, "linked")
self.assertFalse(self.vendor_bill.invoice_line_ids[1].spread_id)
self.assertEqual(self.vendor_bill.invoice_line_ids[1].spread_check, "unlinked")
self.assertFalse(any(line.move_id for line in self.spread.line_ids))
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
# Validate invoice
self.vendor_bill.action_post()
self.assertTrue(self.vendor_bill_line.spread_id)
self.assertEqual(self.vendor_bill_line.spread_check, "linked")
self.assertFalse(self.vendor_bill.invoice_line_ids[1].spread_id)
self.assertEqual(
self.vendor_bill.invoice_line_ids[1].spread_check, "unavailable"
)
def test_09_no_link_invoice(self):
balance_sheet = self.spread.credit_account_id
# Validate invoice
self.vendor_bill.action_post()
invoice_mls = self.vendor_bill.invoice_line_ids
self.assertTrue(invoice_mls)
for invoice_ml in invoice_mls:
self.assertNotEqual(invoice_ml.account_id, balance_sheet)
def test_10_link_vendor_bill_line_with_spread_sheet(self):
invoice_form = Form(self.vendor_bill)
with invoice_form.invoice_line_ids.new() as line:
line.name = "new test line"
line.quantity = 1.0
line.price_unit = 1000.0
self.invoice = invoice_form.save()
self.spread.write(
{
"estimated_amount": 1000.0,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
"invoice_line_id": self.vendor_bill_line.id,
"move_line_auto_post": False,
}
)
self.assertFalse(any(line.move_id for line in self.spread.line_ids))
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
expense_account = self.spread.debit_account_id
balance_sheet = self.spread.credit_account_id
self.assertTrue(balance_sheet.reconcile)
spread_mls = self.spread.line_ids.mapped("move_id.line_ids")
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.debit:
self.assertEqual(spread_ml.account_id, expense_account)
if spread_ml.credit:
self.assertEqual(spread_ml.account_id, balance_sheet)
# Validate invoice
self.vendor_bill.action_post()
count_balance_sheet = len(
self.vendor_bill.line_ids.filtered(lambda x: x.account_id == balance_sheet)
)
self.assertEqual(count_balance_sheet, 1)
self.spread.line_ids.create_and_reconcile_moves()
spread_mls = self.spread.line_ids.mapped("move_id.line_ids")
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.debit:
self.assertFalse(spread_ml.full_reconcile_id)
if spread_ml.credit:
self.assertTrue(spread_ml.full_reconcile_id)
action_reconcile_view = self.spread2.open_reconcile_view()
self.assertTrue(isinstance(action_reconcile_view, dict))
self.assertFalse(action_reconcile_view.get("domain")[0][2])
self.assertTrue(action_reconcile_view.get("context"))
def test_11_link_vendor_bill_line_with_spread_sheet(self):
invoice_form = Form(self.vendor_bill)
with invoice_form.invoice_line_ids.new() as line:
line.name = "new test line"
line.quantity = 1.0
line.price_unit = 1000.0
self.invoice = invoice_form.save()
self.spread.write(
{
"estimated_amount": 1000.0,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
"invoice_line_id": self.vendor_bill_line.id,
"move_line_auto_post": False,
}
)
self.assertFalse(any(line.move_id for line in self.spread.line_ids))
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
expense_account = self.spread.debit_account_id
balance_sheet = self.spread.credit_account_id
self.assertTrue(balance_sheet.reconcile)
spread_mls = self.spread.line_ids.mapped("move_id.line_ids")
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.debit:
self.assertEqual(spread_ml.account_id, expense_account)
if spread_ml.credit:
self.assertEqual(spread_ml.account_id, balance_sheet)
# Validate invoice
self.vendor_bill.action_post()
invoice_mls = self.vendor_bill.line_ids
self.assertTrue(invoice_mls)
count_balance_sheet = len(
invoice_mls.filtered(lambda x: x.account_id == balance_sheet)
)
self.assertEqual(count_balance_sheet, 1)
self.spread.company_id.force_move_auto_post = True
self.spread.line_ids.create_and_reconcile_moves()
spread_mls = self.spread.line_ids.mapped("move_id.line_ids")
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.credit:
self.assertEqual(spread_ml.account_id, balance_sheet)
self.assertTrue(spread_ml.full_reconcile_id)
if spread_ml.debit:
self.assertEqual(spread_ml.account_id, expense_account)
self.assertFalse(spread_ml.full_reconcile_id)
action_reconcile_view = self.spread.open_reconcile_view()
self.assertTrue(isinstance(action_reconcile_view, dict))
self.assertTrue(action_reconcile_view.get("domain")[0][2])
self.assertTrue(action_reconcile_view.get("context"))
action_spread_details = self.vendor_bill_line.spread_details()
self.assertTrue(isinstance(action_spread_details, dict))
self.assertTrue(action_spread_details.get("res_id"))
def test_12_link_invoice_line_with_spread_sheet_full_reconcile(self):
# Validate invoice
self.sale_invoice.action_post()
self.spread2.write(
{
"estimated_amount": 1000.0,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
"invoice_line_id": self.invoice_line.id,
"move_line_auto_post": False,
}
)
self.assertFalse(any(line.move_id for line in self.spread2.line_ids))
self.spread2.compute_spread_board()
spread_lines = self.spread2.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
payable_account = self.spread2.credit_account_id
balance_sheet = self.spread2.debit_account_id
self.assertTrue(balance_sheet.reconcile)
spread_mls = self.spread2.line_ids.mapped("move_id.line_ids")
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.debit:
self.assertEqual(spread_ml.account_id, balance_sheet)
self.assertTrue(spread_ml.reconciled)
self.assertTrue(spread_ml.full_reconcile_id)
if spread_ml.credit:
self.assertEqual(spread_ml.account_id, payable_account)
self.assertFalse(spread_ml.reconciled)
self.assertFalse(spread_ml.full_reconcile_id)
invoice_mls = self.sale_invoice.invoice_line_ids
self.assertTrue(invoice_mls)
for invoice_ml in invoice_mls:
self.assertEqual(invoice_ml.account_id, balance_sheet)
action_reconcile_view = self.spread2.open_reconcile_view()
self.assertTrue(isinstance(action_reconcile_view, dict))
self.assertTrue(action_reconcile_view.get("domain")[0][2])
self.assertFalse(action_reconcile_view.get("res_id"))
self.assertTrue(action_reconcile_view.get("context"))
action_spread_details = self.invoice_line.spread_details()
self.assertTrue(isinstance(action_spread_details, dict))
self.assertTrue(action_spread_details.get("res_id"))
def test_13_link_invoice_line_with_spread_sheet_partial_reconcile(self):
self.spread2.write(
{
"estimated_amount": 1000.0,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
}
)
self.spread2.compute_spread_board()
spread_lines = self.spread2.line_ids
self.assertEqual(len(spread_lines), 13)
self.assertFalse(any(line.move_id for line in spread_lines))
spread_lines[0].create_and_reconcile_moves()
spread_lines[1].create_and_reconcile_moves()
spread_lines[2].create_and_reconcile_moves()
spread_lines[3].create_and_reconcile_moves()
self.assertEqual(spread_lines[0].move_id.state, "posted")
self.assertEqual(spread_lines[1].move_id.state, "posted")
self.assertEqual(spread_lines[2].move_id.state, "posted")
self.assertEqual(spread_lines[3].move_id.state, "posted")
self.assertNotEqual(spread_lines[4].move_id.state, "posted")
spread_mls = spread_lines[0].move_id.line_ids
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.debit:
self.assertFalse(spread_ml.matched_debit_ids)
self.assertFalse(spread_ml.matched_credit_ids)
self.assertFalse(spread_ml.full_reconcile_id)
if spread_ml.credit:
self.assertFalse(spread_ml.matched_debit_ids)
self.assertFalse(spread_ml.matched_credit_ids)
self.assertFalse(spread_ml.full_reconcile_id)
balance_sheet = self.spread2.debit_account_id
self.assertTrue(balance_sheet.reconcile)
# Assing invoice line to spread
self.spread2.invoice_line_id = self.invoice_line
self.assertEqual(self.invoice_line.spread_id, self.spread2)
# Validate invoice
self.sale_invoice.action_post()
invoice_mls = self.sale_invoice.invoice_line_ids
self.assertTrue(invoice_mls)
for invoice_ml in invoice_mls:
self.assertEqual(invoice_ml.account_id, balance_sheet)
spread_lines = self.spread2.line_ids
spread_lines[4].create_and_reconcile_moves()
self.assertEqual(spread_lines[4].move_id.state, "posted")
spread_mls = spread_lines[4].move_id.line_ids
self.assertTrue(spread_mls)
for spread_ml in spread_mls:
if spread_ml.debit:
self.assertFalse(spread_ml.matched_debit_ids)
self.assertTrue(spread_ml.matched_credit_ids)
self.assertFalse(spread_ml.full_reconcile_id)
if spread_ml.credit:
self.assertFalse(spread_ml.matched_debit_ids)
self.assertFalse(spread_ml.matched_credit_ids)
self.assertFalse(spread_ml.full_reconcile_id)
other_journal = self.env["account.journal"].create(
{"name": "Other Journal", "type": "general", "code": "test2"}
)
with self.assertRaises(ValidationError):
self.spread2.journal_id = other_journal
with self.assertRaises(UserError):
self.spread2.unlink()
def test_14_create_all_moves(self):
self.spread.compute_spread_board()
self.assertEqual(len(self.spread.line_ids), 12)
self.assertFalse(any(line.move_id for line in self.spread.line_ids))
# create moves for all the spread lines
self.spread.create_all_moves()
self.assertTrue(all(line.move_id for line in self.spread.line_ids))
with self.assertRaises(ValidationError):
self.spread.unlink()
def test_15_invoice_refund(self):
self.vendor_bill_line.spread_id = self.spread
# Validate invoice
self.vendor_bill.action_post()
self.assertTrue(self.vendor_bill.invoice_line_ids.mapped("spread_id"))
# Create a refund for invoice.
move_reversal = (
self.env["account.move.reversal"]
.with_context(active_model="account.move", active_ids=self.vendor_bill.ids)
.create(
{
"date": fields.Date.today(),
"reason": "no reason",
"refund_method": "refund",
}
)
)
reversal = move_reversal.reverse_moves()
refund = self.env["account.move"].browse(reversal["res_id"])
self.assertFalse(refund.invoice_line_ids.mapped("spread_id"))

View File

@@ -0,0 +1,341 @@
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import datetime
from psycopg2 import IntegrityError
from odoo.exceptions import ValidationError
from odoo.tests import Form, common
from odoo.tools import mute_logger
class TestAccountSpreadCostRevenue(common.TransactionCase):
def setUp(self):
super().setUp()
self.sales_journal = self.env["account.journal"].create(
{"name": "Customer Invoices - Test", "code": "TEST1", "type": "sale"}
)
self.expenses_journal = self.env["account.journal"].create(
{"name": "Vendor Bills - Test", "code": "TEST2", "type": "purchase"}
)
type_receivable = self.env.ref("account.data_account_type_receivable")
type_expenses = self.env.ref("account.data_account_type_expenses")
type_payable = self.env.ref("account.data_account_type_payable")
type_revenue = self.env.ref("account.data_account_type_revenue")
self.credit_account = self.env["account.account"].create(
{
"name": "test_account_receivable",
"code": "123",
"user_type_id": type_receivable.id,
"reconcile": True,
}
)
self.debit_account = self.env["account.account"].create(
{
"name": "test account_expenses",
"code": "765",
"user_type_id": type_expenses.id,
"reconcile": True,
}
)
self.account_payable = self.env["account.account"].create(
{
"name": "test_account_payable",
"code": "321",
"user_type_id": type_payable.id,
"reconcile": True,
}
)
self.account_revenue = self.env["account.account"].create(
{
"name": "test_account_revenue",
"code": "864",
"user_type_id": type_revenue.id,
"reconcile": True,
}
)
def test_01_account_spread_defaults(self):
this_year = datetime.date.today().year
spread_template = self.env["account.spread.template"].create(
{"name": "test", "spread_account_id": self.debit_account.id}
)
self.assertEqual(spread_template.spread_type, "sale")
self.assertTrue(spread_template.spread_journal_id)
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
self.assertTrue(spread)
self.assertFalse(spread.line_ids)
self.assertFalse(spread.invoice_line_ids)
self.assertFalse(spread.invoice_line_id)
self.assertFalse(spread.invoice_id)
self.assertFalse(spread.account_analytic_id)
self.assertFalse(spread.analytic_tag_ids)
self.assertTrue(spread.move_line_auto_post)
self.assertEqual(spread.name, "test")
self.assertEqual(spread.invoice_type, "out_invoice")
self.assertEqual(spread.company_id, self.env.company)
self.assertEqual(spread.currency_id, self.env.company.currency_id)
self.assertEqual(spread.period_number, 12)
self.assertEqual(spread.period_type, "month")
self.assertEqual(spread.debit_account_id, self.debit_account)
self.assertEqual(spread.credit_account_id, self.credit_account)
self.assertEqual(spread.unspread_amount, 0.0)
self.assertEqual(spread.unposted_amount, 0.0)
self.assertEqual(spread.total_amount, 0.0)
self.assertEqual(spread.estimated_amount, 0.0)
self.assertEqual(spread.spread_date, datetime.date(this_year, 1, 1))
self.assertTrue(spread.journal_id)
self.assertEqual(spread.journal_id.type, "general")
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
def test_02_config_defaults(self):
self.assertFalse(self.env.company.default_spread_revenue_account_id)
self.assertFalse(self.env.company.default_spread_expense_account_id)
self.assertFalse(self.env.company.default_spread_revenue_journal_id)
self.assertFalse(self.env.company.default_spread_expense_journal_id)
@mute_logger("odoo.sql_db")
def test_03_no_defaults(self):
with self.assertRaises(IntegrityError):
self.env["account.spread"].create({"name": "test"})
@mute_logger("odoo.sql_db")
def test_04_no_defaults(self):
with self.assertRaises(IntegrityError):
self.env["account.spread"].create(
{"name": "test", "invoice_type": "out_invoice"}
)
def test_05_config_settings(self):
self.env.company.default_spread_revenue_account_id = self.account_revenue
self.env.company.default_spread_expense_account_id = self.account_payable
self.env.company.default_spread_revenue_journal_id = self.sales_journal
self.env.company.default_spread_expense_journal_id = self.expenses_journal
self.assertTrue(self.env.company.default_spread_revenue_account_id)
self.assertTrue(self.env.company.default_spread_expense_account_id)
self.assertTrue(self.env.company.default_spread_revenue_journal_id)
self.assertTrue(self.env.company.default_spread_expense_journal_id)
spread_form = Form(self.env["account.spread"])
spread_form.name = "test"
spread_form.invoice_type = "in_invoice"
spread_form.debit_account_id = self.debit_account
spread_form.credit_account_id = self.credit_account
spread = spread_form.save()
self.assertTrue(spread)
self.assertFalse(spread.line_ids)
self.assertFalse(spread.invoice_line_ids)
self.assertFalse(spread.invoice_line_id)
self.assertFalse(spread.invoice_id)
self.assertFalse(spread.account_analytic_id)
self.assertFalse(spread.analytic_tag_ids)
self.assertTrue(spread.move_line_auto_post)
defaults = self.env["account.spread"].default_get(["company_id", "currency_id"])
self.assertEqual(defaults["company_id"], self.env.company.id)
self.assertEqual(defaults["currency_id"], self.env.company.currency_id.id)
spread_form = Form(spread)
spread_form.invoice_type = "out_invoice"
spread_form.company_id = self.env.company
spread = spread_form.save()
self.assertEqual(spread.debit_account_id, self.account_revenue)
self.assertFalse(spread.is_debit_account_deprecated)
self.assertEqual(spread.journal_id, self.sales_journal)
self.assertEqual(spread.spread_type, "sale")
spread_form = Form(spread)
spread_form.invoice_type = "in_invoice"
spread = spread_form.save()
self.assertEqual(spread.credit_account_id, self.account_payable)
self.assertFalse(spread.is_credit_account_deprecated)
self.assertEqual(spread.journal_id, self.expenses_journal)
self.assertEqual(spread.spread_type, "purchase")
def test_07_create_spread_template(self):
spread_template = self.env["account.spread.template"].create(
{
"name": "test",
"spread_type": "sale",
"spread_account_id": self.account_revenue.id,
}
)
self.assertEqual(spread_template.company_id, self.env.company)
self.assertTrue(spread_template.spread_journal_id)
self.env.company.default_spread_revenue_account_id = self.account_revenue
self.env.company.default_spread_expense_account_id = self.account_payable
self.env.company.default_spread_revenue_journal_id = self.sales_journal
self.env.company.default_spread_expense_journal_id = self.expenses_journal
spread_template.spread_type = "purchase"
self.assertTrue(spread_template.spread_journal_id)
self.assertTrue(spread_template.spread_account_id)
self.assertEqual(spread_template.spread_account_id, self.account_payable)
self.assertEqual(spread_template.spread_journal_id, self.expenses_journal)
spread_vals = spread_template._prepare_spread_from_template()
self.assertTrue(spread_vals["name"])
self.assertTrue(spread_vals["template_id"])
self.assertTrue(spread_vals["journal_id"])
self.assertTrue(spread_vals["company_id"])
self.assertTrue(spread_vals["invoice_type"])
self.assertTrue(spread_vals["credit_account_id"])
spread_template.spread_type = "sale"
self.assertTrue(spread_template.spread_journal_id)
self.assertTrue(spread_template.spread_account_id)
self.assertEqual(spread_template.spread_account_id, self.account_revenue)
self.assertEqual(spread_template.spread_journal_id, self.sales_journal)
spread_vals = spread_template._prepare_spread_from_template()
self.assertTrue(spread_vals["name"])
self.assertTrue(spread_vals["template_id"])
self.assertTrue(spread_vals["journal_id"])
self.assertTrue(spread_vals["company_id"])
self.assertTrue(spread_vals["invoice_type"])
self.assertTrue(spread_vals["debit_account_id"])
def test_08_check_template_invoice_type(self):
template_sale = self.env["account.spread.template"].create(
{
"name": "test",
"spread_type": "sale",
"spread_account_id": self.account_revenue.id,
}
)
template_purchase = self.env["account.spread.template"].create(
{
"name": "test",
"spread_type": "purchase",
"spread_account_id": self.account_payable.id,
}
)
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
with self.assertRaises(ValidationError):
spread.template_id = template_purchase
spread.template_id = template_sale
self.assertEqual(spread.template_id, template_sale)
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "in_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
with self.assertRaises(ValidationError):
spread.template_id = template_sale
spread.template_id = template_purchase
self.assertEqual(spread.template_id, template_purchase)
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
def test_10_account_spread_unlink(self):
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
spread.unlink()
def test_11_compute_display_fields(self):
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
spread.company_id.allow_spread_planning = True
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
def test_12_compute_display_fields(self):
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
spread.company_id.allow_spread_planning = False
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
def test_13_compute_display_fields(self):
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
spread.company_id.force_move_auto_post = True
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertFalse(spread.display_move_line_auto_post)
def test_14_compute_display_fields(self):
spread = self.env["account.spread"].create(
{
"name": "test",
"invoice_type": "out_invoice",
"debit_account_id": self.debit_account.id,
"credit_account_id": self.credit_account.id,
}
)
spread.company_id.force_move_auto_post = False
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)

View File

@@ -0,0 +1,619 @@
# Copyright 2017-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import datetime
from odoo.exceptions import UserError
from odoo.tests import common
class TestComputeSpreadBoard(common.TransactionCase):
def setUp(self):
super().setUp()
type_receivable = self.env.ref("account.data_account_type_receivable")
type_expenses = self.env.ref("account.data_account_type_expenses")
journal = self.env["account.journal"].create(
{"name": "Test", "type": "general", "code": "test"}
)
self.receivable_account = self.env["account.account"].create(
{
"name": "test_account_receivable",
"code": "123",
"user_type_id": type_receivable.id,
"reconcile": True,
}
)
self.expense_account = self.env["account.account"].create(
{
"name": "test account_expenses",
"code": "765",
"user_type_id": type_expenses.id,
"reconcile": True,
}
)
self.spread_account = self.env["account.account"].create(
{
"name": "test spread account_expenses",
"code": "321",
"user_type_id": type_expenses.id,
"reconcile": True,
}
)
self.spread = self.env["account.spread"].create(
{
"name": "test",
"debit_account_id": self.spread_account.id,
"credit_account_id": self.expense_account.id,
"period_number": 12,
"period_type": "month",
"spread_date": "2017-02-01",
"estimated_amount": 1000.0,
"journal_id": journal.id,
"invoice_type": "in_invoice",
}
)
self.spread2 = self.env["account.spread"].create(
{
"name": "test2",
"debit_account_id": self.spread_account.id,
"credit_account_id": self.expense_account.id,
"period_number": 12,
"period_type": "month",
"spread_date": "2017-02-01",
"estimated_amount": 1000.0,
"journal_id": journal.id,
"invoice_type": "out_invoice",
}
)
def test_01_supplier_invoice(self):
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 12)
self.assertEqual(83.33, spread_lines[0].amount)
self.assertEqual(83.33, spread_lines[1].amount)
self.assertEqual(83.33, spread_lines[2].amount)
self.assertEqual(83.33, spread_lines[3].amount)
self.assertEqual(83.33, spread_lines[4].amount)
self.assertEqual(83.33, spread_lines[5].amount)
self.assertEqual(83.33, spread_lines[6].amount)
self.assertEqual(83.33, spread_lines[7].amount)
self.assertEqual(83.33, spread_lines[8].amount)
self.assertEqual(83.33, spread_lines[9].amount)
self.assertEqual(83.33, spread_lines[10].amount)
self.assertEqual(83.37, spread_lines[11].amount)
self.assertEqual(datetime.date(2017, 2, 28), spread_lines[0].date)
self.assertEqual(datetime.date(2017, 3, 31), spread_lines[1].date)
self.assertEqual(datetime.date(2017, 4, 30), spread_lines[2].date)
self.assertEqual(datetime.date(2017, 5, 31), spread_lines[3].date)
self.assertEqual(datetime.date(2017, 6, 30), spread_lines[4].date)
self.assertEqual(datetime.date(2017, 7, 31), spread_lines[5].date)
self.assertEqual(datetime.date(2017, 8, 31), spread_lines[6].date)
self.assertEqual(datetime.date(2017, 9, 30), spread_lines[7].date)
self.assertEqual(datetime.date(2017, 10, 31), spread_lines[8].date)
self.assertEqual(datetime.date(2017, 11, 30), spread_lines[9].date)
self.assertEqual(datetime.date(2017, 12, 31), spread_lines[10].date)
self.assertEqual(datetime.date(2018, 1, 31), spread_lines[11].date)
for line in spread_lines:
self.assertFalse(line.move_id)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
self.spread.action_recalculate_spread()
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertTrue(line.move_id)
def test_02_supplier_invoice(self):
# spread date set
self.spread.write(
{
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
}
)
self.spread_account.reconcile = True
self.assertTrue(self.spread_account.reconcile)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 13)
self.assertEqual(67.20, spread_lines[0].amount)
self.assertEqual(83.33, spread_lines[1].amount)
self.assertEqual(83.33, spread_lines[2].amount)
self.assertEqual(83.33, spread_lines[3].amount)
self.assertEqual(83.33, spread_lines[4].amount)
self.assertEqual(83.33, spread_lines[5].amount)
self.assertEqual(83.33, spread_lines[6].amount)
self.assertEqual(83.33, spread_lines[7].amount)
self.assertEqual(83.33, spread_lines[8].amount)
self.assertEqual(83.33, spread_lines[9].amount)
self.assertEqual(83.33, spread_lines[10].amount)
self.assertEqual(83.33, spread_lines[11].amount)
self.assertEqual(16.17, spread_lines[12].amount)
self.assertEqual(datetime.date(2017, 1, 31), spread_lines[0].date)
self.assertEqual(datetime.date(2017, 2, 28), spread_lines[1].date)
self.assertEqual(datetime.date(2017, 3, 31), spread_lines[2].date)
self.assertEqual(datetime.date(2017, 4, 30), spread_lines[3].date)
self.assertEqual(datetime.date(2017, 5, 31), spread_lines[4].date)
self.assertEqual(datetime.date(2017, 6, 30), spread_lines[5].date)
self.assertEqual(datetime.date(2017, 7, 31), spread_lines[6].date)
self.assertEqual(datetime.date(2017, 8, 31), spread_lines[7].date)
self.assertEqual(datetime.date(2017, 9, 30), spread_lines[8].date)
self.assertEqual(datetime.date(2017, 10, 31), spread_lines[9].date)
self.assertEqual(datetime.date(2017, 11, 30), spread_lines[10].date)
self.assertEqual(datetime.date(2017, 12, 31), spread_lines[11].date)
self.assertEqual(datetime.date(2018, 1, 31), spread_lines[12].date)
for line in spread_lines:
self.assertFalse(line.move_id)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
self.spread.line_ids.create_and_reconcile_moves()
for line in self.spread.line_ids:
self.assertTrue(line.move_id)
def test_03_supplier_invoice(self):
# spread date set
self.spread.write(
{
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 31),
"move_line_auto_post": False,
}
)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 13)
self.assertEqual(2.69, spread_lines[0].amount)
self.assertEqual(83.33, spread_lines[1].amount)
self.assertEqual(83.33, spread_lines[2].amount)
self.assertEqual(83.33, spread_lines[3].amount)
self.assertEqual(83.33, spread_lines[4].amount)
self.assertEqual(83.33, spread_lines[5].amount)
self.assertEqual(83.33, spread_lines[6].amount)
self.assertEqual(83.33, spread_lines[7].amount)
self.assertEqual(83.33, spread_lines[8].amount)
self.assertEqual(83.33, spread_lines[9].amount)
self.assertEqual(83.33, spread_lines[10].amount)
self.assertEqual(83.33, spread_lines[11].amount)
self.assertEqual(80.68, spread_lines[12].amount)
self.assertEqual(datetime.date(2017, 1, 31), spread_lines[0].date)
self.assertEqual(datetime.date(2017, 2, 28), spread_lines[1].date)
self.assertEqual(datetime.date(2017, 3, 31), spread_lines[2].date)
self.assertEqual(datetime.date(2017, 4, 30), spread_lines[3].date)
self.assertEqual(datetime.date(2017, 5, 31), spread_lines[4].date)
self.assertEqual(datetime.date(2017, 6, 30), spread_lines[5].date)
self.assertEqual(datetime.date(2017, 7, 31), spread_lines[6].date)
self.assertEqual(datetime.date(2017, 8, 31), spread_lines[7].date)
self.assertEqual(datetime.date(2017, 9, 30), spread_lines[8].date)
self.assertEqual(datetime.date(2017, 10, 31), spread_lines[9].date)
self.assertEqual(datetime.date(2017, 11, 30), spread_lines[10].date)
self.assertEqual(datetime.date(2017, 12, 31), spread_lines[11].date)
self.assertEqual(datetime.date(2018, 1, 31), spread_lines[12].date)
for line in spread_lines:
self.assertFalse(line.move_id)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
spread_lines[0].create_move()
spread_lines[1].create_move()
spread_lines[2].create_move()
self.assertTrue(any(line.move_id for line in spread_lines))
self.assertTrue(any(not line.move_id for line in spread_lines))
self.spread._compute_amounts()
self.assertEqual(self.spread.unspread_amount, 830.65)
self.assertEqual(self.spread.unposted_amount, 1000.0)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 13)
self.assertEqual(2.69, spread_lines[0].amount)
self.assertEqual(83.33, spread_lines[1].amount)
self.assertEqual(83.33, spread_lines[2].amount)
self.assertEqual(83.33, spread_lines[3].amount)
self.assertEqual(83.33, spread_lines[4].amount)
self.assertEqual(83.33, spread_lines[5].amount)
self.assertEqual(83.33, spread_lines[6].amount)
self.assertEqual(83.33, spread_lines[7].amount)
self.assertEqual(83.33, spread_lines[8].amount)
self.assertEqual(83.33, spread_lines[9].amount)
self.assertEqual(83.33, spread_lines[10].amount)
self.assertEqual(83.33, spread_lines[11].amount)
self.assertEqual(80.68, spread_lines[12].amount)
self.assertEqual(datetime.date(2017, 1, 31), spread_lines[0].date)
self.assertEqual(datetime.date(2017, 2, 28), spread_lines[1].date)
self.assertEqual(datetime.date(2017, 3, 31), spread_lines[2].date)
self.assertEqual(datetime.date(2017, 4, 30), spread_lines[3].date)
self.assertEqual(datetime.date(2017, 5, 31), spread_lines[4].date)
self.assertEqual(datetime.date(2017, 6, 30), spread_lines[5].date)
self.assertEqual(datetime.date(2017, 7, 31), spread_lines[6].date)
self.assertEqual(datetime.date(2017, 8, 31), spread_lines[7].date)
self.assertEqual(datetime.date(2017, 9, 30), spread_lines[8].date)
self.assertEqual(datetime.date(2017, 10, 31), spread_lines[9].date)
self.assertEqual(datetime.date(2017, 11, 30), spread_lines[10].date)
self.assertEqual(datetime.date(2017, 12, 31), spread_lines[11].date)
self.assertEqual(datetime.date(2018, 1, 31), spread_lines[12].date)
def test_04_supplier_invoice(self):
self.spread.write(
{
"credit_account_id": self.expense_account.id,
"debit_account_id": self.spread_account.id,
"period_number": 3,
"period_type": "year",
"spread_date": datetime.date(2018, 10, 24),
}
)
# change the state of invoice to open by clicking Validate button
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 4)
self.assertEqual(333.33, spread_lines[1].amount)
self.assertEqual(333.33, spread_lines[2].amount)
first_amount = spread_lines[0].amount
last_amount = spread_lines[3].amount
remaining_amount = first_amount + last_amount
self.assertAlmostEqual(remaining_amount, 333.34, places=2)
total_line_amount = 0.0
for line in spread_lines:
total_line_amount += line.amount
self.assertAlmostEqual(total_line_amount, 1000.0, places=2)
for line in spread_lines:
self.assertFalse(line.move_id)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_05_supplier_invoice(self):
# spread date set
self.spread.write(
{
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 2, 1),
}
)
self.spread.compute_spread_board()
# create moves for all the spread lines and open them
self.spread.line_ids.create_and_reconcile_moves()
for spread_line in self.spread.line_ids:
attrs = spread_line.open_move()
self.assertTrue(isinstance(attrs, dict))
# unlink all created moves
self.spread.line_ids.unlink_move()
for spread_line in self.spread.line_ids:
self.assertFalse(spread_line.move_id)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_06_supplier_invoice(self):
# spread date set
self.spread.write(
{"period_number": 3, "period_type": "quarter", "move_line_auto_post": False}
)
self.spread.compute_spread_board()
# create moves for all the spread lines and open them
self.spread.line_ids.create_and_reconcile_moves()
# check move lines
for spread_line in self.spread.line_ids:
for move_line in spread_line.move_id.line_ids:
spread_account = self.spread.debit_account_id
if move_line.account_id == spread_account:
debit = move_line.debit
self.assertAlmostEqual(debit, spread_line.amount)
for line in self.spread.line_ids:
self.assertTrue(line.move_id)
self.assertFalse(line.move_id.state == "posted")
self.assertEqual(self.spread.unspread_amount, 0.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
# try to create move lines again: an error is raised
for line in self.spread.line_ids:
with self.assertRaises(UserError):
line.create_move()
self.spread.write({"move_line_auto_post": True})
self.spread.action_recalculate_spread()
for line in self.spread.line_ids:
self.assertTrue(line.move_id)
self.assertTrue(line.move_id.state == "posted")
self.assertEqual(self.spread.unspread_amount, 0.0)
self.assertEqual(self.spread.unposted_amount, 0.0)
def test_07_supplier_invoice(self):
self.spread.write(
{
"period_number": 3,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 1),
"estimated_amount": 345.96,
}
)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 3)
self.assertAlmostEquals(115.32, spread_lines[0].amount)
self.assertAlmostEquals(115.32, spread_lines[1].amount)
self.assertAlmostEquals(115.32, spread_lines[2].amount)
self.assertEqual(datetime.date(2017, 1, 31), spread_lines[0].date)
self.assertEqual(datetime.date(2017, 2, 28), spread_lines[1].date)
self.assertEqual(datetime.date(2017, 3, 31), spread_lines[2].date)
for line in spread_lines:
self.assertFalse(line.move_id)
self.assertEqual(self.spread.unspread_amount, 345.96)
self.assertEqual(self.spread.unposted_amount, 345.96)
def test_08_supplier_invoice(self):
# spread date set
self.spread.write(
{
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 2, 1),
}
)
self.spread.compute_spread_board()
self.assertTrue(self.spread.line_ids)
self.spread.action_undo_spread()
self.assertFalse(self.spread.line_ids)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_09_supplier_invoice(self):
# spread date set
self.spread.write(
{
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 2, 1),
}
)
self.spread.compute_spread_board()
for line in self.spread.line_ids:
line.create_move()
self.assertTrue(line.move_id)
action = line.open_move()
self.assertTrue(action)
self.spread.line_ids.unlink_move()
for line in self.spread.line_ids:
self.assertFalse(line.move_id)
self.assertTrue(self.spread.line_ids)
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_10_create_entries(self):
self.env["account.spread.line"]._create_entries()
self.assertFalse(self.spread.line_ids)
self.spread.compute_spread_board()
self.env["account.spread.line"]._create_entries()
self.assertTrue(self.spread.line_ids)
for line in self.spread.line_ids:
self.assertTrue(line.move_id)
def test_11_create_move_sale_invoice(self):
self.spread2.move_line_auto_post = False
self.spread2.compute_spread_board()
for line in self.spread2.line_ids:
self.assertFalse(line.move_id)
line.create_move()
self.assertTrue(line.move_id)
self.assertFalse(line.move_id.state == "posted")
self.spread2.action_undo_spread()
for line in self.spread2.line_ids:
self.assertFalse(line.move_id)
self.spread2.action_recalculate_spread()
for line in self.spread2.line_ids:
self.assertTrue(line.move_id)
self.assertTrue(line.move_id)
self.assertFalse(line.move_id.state == "posted")
# try to create move lines again: an error is raised
with self.assertRaises(UserError):
line.create_move()
def test_12_supplier_invoice_auto_post(self):
# spread date set
self.spread.write(
{"period_number": 8, "period_type": "month", "move_line_auto_post": True}
)
self.spread.compute_spread_board()
# create moves for all the spread lines and open them
self.spread.line_ids.create_and_reconcile_moves()
# check move lines
for spread_line in self.spread.line_ids:
for move_line in spread_line.move_id.line_ids:
spread_account = self.spread.debit_account_id
if move_line.account_id == spread_account:
debit = move_line.debit
self.assertAlmostEqual(debit, spread_line.amount)
self.assertTrue(self.spread.move_line_auto_post)
for line in self.spread.line_ids:
self.assertTrue(line.move_id)
self.assertTrue(line.move_id.state == "posted")
self.assertEqual(self.spread.unspread_amount, 0.0)
self.assertEqual(self.spread.unposted_amount, 0.0)
def test_13_create_move_in_invoice_auto_post(self):
self.spread2.write({"period_number": 4, "move_line_auto_post": True})
self.spread_account.reconcile = True
self.assertTrue(self.spread_account.reconcile)
self.spread2.compute_spread_board()
for line in self.spread2.line_ids:
self.assertFalse(line.move_id)
line.create_move()
self.assertTrue(line.move_id)
self.assertTrue(line.move_id.state == "posted")
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_14_negative_amount(self):
# spread date set
self.spread.write(
{
"estimated_amount": -1000.0,
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
}
)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertTrue(spread_lines)
def test_15_compute_spread_board_line_account_deprecated(self):
self.spread.debit_account_id.deprecated = True
self.assertTrue(self.spread.debit_account_id.deprecated)
self.assertTrue(self.spread.is_debit_account_deprecated)
self.spread.compute_spread_board()
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_16_compute_spread_board_line_account_deprecated(self):
self.spread.credit_account_id.deprecated = True
self.assertTrue(self.spread.credit_account_id.deprecated)
self.assertTrue(self.spread.is_credit_account_deprecated)
self.spread.compute_spread_board()
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_17_compute_spread_board_line_account_deprecated(self):
self.spread.compute_spread_board()
self.spread.debit_account_id.deprecated = True
self.assertTrue(self.spread.debit_account_id.deprecated)
for line in self.spread.line_ids:
self.assertFalse(line.move_id)
with self.assertRaises(UserError):
line.create_move()
self.assertEqual(self.spread.unspread_amount, 1000.0)
self.assertEqual(self.spread.unposted_amount, 1000.0)
def test_18_supplier_invoice(self):
# spread date set
self.spread.write(
{
"period_number": 12,
"period_type": "month",
"spread_date": datetime.date(2017, 1, 7),
}
)
self.spread_account.reconcile = True
self.assertTrue(self.spread_account.reconcile)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 13)
for line in spread_lines:
self.assertFalse(line.move_id)
spread_lines[0]._create_moves().post()
spread_lines[1]._create_moves().post()
spread_lines[2]._create_moves().post()
spread_lines[3]._create_moves().post()
self.assertEqual(spread_lines[0].move_id.state, "posted")
self.assertEqual(spread_lines[1].move_id.state, "posted")
self.assertEqual(spread_lines[2].move_id.state, "posted")
self.assertEqual(spread_lines[3].move_id.state, "posted")
self.assertAlmostEqual(self.spread.unspread_amount, 682.81)
self.assertAlmostEqual(self.spread.unposted_amount, 682.81)
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 13)
self.assertEqual(67.20, spread_lines[0].amount)
self.assertEqual(83.33, spread_lines[1].amount)
self.assertEqual(83.33, spread_lines[2].amount)
self.assertEqual(83.33, spread_lines[3].amount)
self.assertEqual(83.33, spread_lines[4].amount)
self.assertEqual(83.33, spread_lines[5].amount)
self.assertEqual(83.33, spread_lines[6].amount)
self.assertEqual(83.33, spread_lines[7].amount)
self.assertEqual(83.33, spread_lines[8].amount)
self.assertEqual(83.33, spread_lines[9].amount)
self.assertEqual(83.33, spread_lines[10].amount)
self.assertEqual(83.33, spread_lines[11].amount)
self.assertEqual(16.17, spread_lines[12].amount)
self.assertEqual(datetime.date(2017, 1, 31), spread_lines[0].date)
self.assertEqual(datetime.date(2017, 2, 28), spread_lines[1].date)
self.assertEqual(datetime.date(2017, 3, 31), spread_lines[2].date)
self.assertEqual(datetime.date(2017, 4, 30), spread_lines[3].date)
self.assertEqual(datetime.date(2017, 5, 31), spread_lines[4].date)
self.assertEqual(datetime.date(2017, 6, 30), spread_lines[5].date)
self.assertEqual(datetime.date(2017, 7, 31), spread_lines[6].date)
self.assertEqual(datetime.date(2017, 8, 31), spread_lines[7].date)
self.assertEqual(datetime.date(2017, 9, 30), spread_lines[8].date)
self.assertEqual(datetime.date(2017, 10, 31), spread_lines[9].date)
self.assertEqual(datetime.date(2017, 11, 30), spread_lines[10].date)
self.assertEqual(datetime.date(2017, 12, 31), spread_lines[11].date)
self.assertEqual(datetime.date(2018, 1, 31), spread_lines[12].date)
self.assertAlmostEqual(self.spread.unspread_amount, 682.81)
self.assertAlmostEqual(self.spread.unposted_amount, 682.81)

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_invoice_spread" model="ir.ui.view">
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form" />
<field name="arch" type="xml">
<xpath
expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']"
position="before"
>
<field
name="spread_check"
widget="spread_line_widget"
groups="account.group_account_user,account.group_account_manager"
/>
</xpath>
</field>
</record>
<record id="action_account_moves_all_spread" model="ir.actions.act_window">
<field
name="context"
>{'journal_type':'general', 'search_default_group_by_move': 0, 'search_default_posted':1, 'name_groupby':1}</field>
<field name="name">Journal Items</field>
<field name="res_model">account.move.line</field>
<field
name="domain"
>[('display_type', 'not in', ('line_section', 'line_note'))]</field>
<field name="view_id" ref="account.view_move_line_tree_grouped" />
<field name="view_mode">tree,pivot,graph,form,kanban</field>
</record>
</odoo>

View File

@@ -0,0 +1,409 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_spread" model="ir.ui.view">
<field name="model">account.spread</field>
<field name="arch" type="xml">
<form>
<header>
<button
name="compute_spread_board"
type="object"
string="Recalculate unposted lines"
class="oe_highlight"
attrs="{'invisible': ['|',('debit_account_id', '=', False),('display_recompute_buttons', '=', False)]}"
/>
<button
name="action_recalculate_spread"
type="object"
string="Recalculate entire spread"
attrs="{'invisible': ['|',('debit_account_id', '=', False),('display_recompute_buttons', '=', False)]}"
groups="account.group_account_manager"
/>
<button
name="action_undo_spread"
type="object"
string="Undo spread"
attrs="{'invisible': [('line_ids', '=', [])]}"
groups="account.group_account_manager"
/>
<button
name="action_unlink_invoice_line"
type="object"
string="Unlink Invoice Line"
attrs="{'invisible': [('invoice_line_id', '=', False)]}"
groups="account.group_account_manager"
/>
</header>
<sheet>
<div name="button_box" class="oe_button_box">
<button
name="open_reconcile_view"
class="oe_stat_button"
icon="fa-bars"
type="object"
string="Reconciled entries"
>
</button>
</div>
<widget
name="web_ribbon"
title="Archived"
bg_color="bg-danger"
attrs="{'invisible': [('active', '=', True)]}"
/>
<div class="oe_title">
<label for="name" string="Spread Board Name" />
<h1>
<field
name="name"
placeholder="e.g. One year offices cleaning contract"
/>
</h1>
</div>
<group name="header_info">
<group name="spread_definitions">
<field name="template_id" />
<field
name="invoice_type"
attrs="{'readonly':[('invoice_line_id','!=',False)]}"
/>
<field name="display_recompute_buttons" invisible="1" />
<field name="display_move_line_auto_post" invisible="1" />
<field name="all_posted" invisible="1" />
<field name="active" invisible="1" />
<field name="use_invoice_line_account" invisible="1" />
</group>
</group>
<group name="accounts">
<group name="debits">
<field name="is_debit_account_deprecated" invisible="1" />
<label
for="debit_account_id"
colspan="3"
string="Balance sheet account / Spread account"
attrs="{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}"
/>
<label
for="debit_account_id"
colspan="3"
string="Expense account"
attrs="{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}"
/>
<div
attrs="{'invisible': [('use_invoice_line_account', '=', True)]}"
colspan="2"
>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}"
>
The Balance Sheet account used for the spreading.<br
/>This account is the counterpart of the account in the invoice line.
</span>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}"
>
The Expense account in the vendor bill line.<br
/>Usually the same account of the vendor bill line.
</span>
</div>
<div
attrs="{'invisible': [('use_invoice_line_account', '!=', True)]}"
colspan="2"
>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}"
>
The Balance Sheet account.<br
/>This is the account in the invoice line.
</span>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}"
>
The Expense account used for the spreading.<br
/>This account is the counterpart of the account of the vendor bill line.
</span>
</div>
<field
name="debit_account_id"
required="1"
domain="[('company_id', '=', company_id), ('deprecated', '=', False), ('internal_type','!=','liquidity'), ('internal_group', '!=', 'off_balance')]"
attrs="{'readonly':[('invoice_line_id','!=',False)]}"
/>
<span
class="help-block text-danger"
colspan="2"
attrs="{'invisible':[('is_debit_account_deprecated','!=',True)]}"
>
This account in deprecated! The reconciliation will be NOT possible.
</span>
</group>
<group name="credits">
<field name="is_credit_account_deprecated" invisible="1" />
<label
for="credit_account_id"
colspan="3"
string="Revenue account"
attrs="{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}"
/>
<label
for="credit_account_id"
colspan="3"
string="Balance sheet account / Spread account"
attrs="{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}"
/>
<div
attrs="{'invisible': [('use_invoice_line_account', '=', True)]}"
colspan="2"
>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}"
>
The Revenue account in the invoice line.<br
/>Usually the same account of the invoice line.
</span>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}"
>
The Balance Sheet account used for the spreading.<br
/>This account is the counterpart of the account in the vendor bill line.
</span>
</div>
<div
attrs="{'invisible': [('use_invoice_line_account', '!=', True)]}"
colspan="2"
>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}"
>
The Revenue account used for the spreading.<br
/>This account is the counterpart of the account of the invoice line.
</span>
<span
class="help-block"
colspan="2"
attrs="{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}"
>
The Balance Sheet account.<br
/>This is the account in the vendor bill line.
</span>
</div>
<field
name="credit_account_id"
required="1"
domain="[('company_id', '=', company_id), ('deprecated', '=', False), ('internal_type','!=','liquidity'), ('internal_group', '!=', 'off_balance')]"
attrs="{'readonly':[('invoice_line_id','!=',False)]}"
/>
<span
class="help-block text-danger"
colspan="2"
attrs="{'invisible':[('is_credit_account_deprecated','!=',True)]}"
>
This account in deprecated! The reconciliation will be NOT possible.
</span>
</group>
</group>
<group name="main_info">
<group>
<field
name="invoice_id"
attrs="{'invisible':[('invoice_id','=',False)]}"
/>
<field
name="invoice_line_id"
readonly="1"
attrs="{'invisible':[('invoice_line_id','=',False)]}"
/>
<field
name="estimated_amount"
attrs="{'readonly':[('invoice_line_id','!=',False)],'invisible':[('estimated_amount','=',0.0),('invoice_line_id','!=',False)]}"
/>
<field
name="total_amount"
attrs="{'invisible':[('invoice_line_id','=',False)]}"
/>
<field
name="move_line_auto_post"
attrs="{'invisible':[('display_move_line_auto_post','=',False)]}"
/>
</group>
<group>
<field name="period_number" />
<field name="period_type" />
<field name="spread_date" />
<field
name="journal_id"
domain="[('company_id', '=', company_id)]"
widget="selection"
/>
</group>
</group>
<notebook name="notebook">
<page name="spread_lines" string="Spread lines">
<field name="line_ids" readonly="1">
<tree>
<field name="name" readonly="1" />
<field
name="amount"
attrs="{'readonly':[('move_id','!=',False)]}"
sum="Total"
/>
<field name="date" readonly="1" />
<field name="move_id" readonly="1" />
<button
name="create_move"
icon="fa-play"
string="Create Move"
type="object"
groups="account.group_account_manager"
attrs="{'invisible':['|',('move_id','!=',False)]}"
/>
<button
name="open_move"
icon="fa-plus-square-o"
string="View Move"
type="object"
attrs="{'invisible':[('move_id','=',False)]}"
/>
<button
name="unlink_move"
icon="fa-times"
string="Delete Move"
type="object"
confirm="This will delete the move. Are you sure ?"
groups="account.group_account_manager"
attrs="{'invisible':[('move_id','=',False)]}"
/>
</tree>
</field>
<group name="extension">
<group name="extension_left">
</group>
<group name="extension_right">
<field
name="display_create_all_moves"
invisible="1"
/>
<button
name="create_all_moves"
string="Create All Moves"
type="object"
icon="fa-play"
attrs="{'invisible':[('display_create_all_moves','!=',True)]}"
/>
<field
name="unspread_amount"
attrs="{'invisible': [('unspread_amount', '=', 0)]}"
/>
<field
name="unposted_amount"
attrs="{'invisible': [('unposted_amount', '=', 0)]}"
/>
<field
name="posted_amount"
attrs="{'invisible': [('posted_amount', '=', 0)]}"
/>
</group>
</group>
</page>
<page name="details" string="Details">
<group name="extra_details">
<group>
<field
name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"
/>
<field
name="currency_id"
groups="base.group_multi_currency"
/>
</group>
<group>
<field
name="account_analytic_id"
domain="[('company_id', '=', company_id)]"
groups="analytic.group_analytic_accounting"
options="{'no_create': True}"
/>
<field
name="analytic_tag_ids"
groups="analytic.group_analytic_accounting"
widget="many2many_tags"
/>
</group>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread" />
</div>
</form>
</field>
</record>
<record id="view_account_spread_account_manager" model="ir.ui.view">
<field name="model">account.spread</field>
<field name="inherit_id" ref="view_account_spread" />
<field name="groups_id" eval="[(4, ref('account.group_account_manager'))]" />
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="editable">1</attribute>
</tree>
<field name="amount" position="attributes">
<attribute name="readonly" />
</field>
<field name="line_ids" position="attributes">
<attribute name="readonly" />
</field>
</field>
</record>
<record id="view_account_spread_tree" model="ir.ui.view">
<field name="model">account.spread</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="company_id" groups="base.group_multi_company" />
</tree>
</field>
</record>
<record id="view_account_spread_search" model="ir.ui.view">
<field name="model">account.spread</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Spread" />
<filter
string="Archived"
name="inactive"
domain="[('active','=',False)]"
/>
</search>
</field>
</record>
<record id="action_account_spread_form" model="ir.actions.act_window">
<field name="name">Spread Costs/Revenues</field>
<field name="res_model">account.spread</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_account_spread_tree" />
</record>
<menuitem
id="menu_action_account_spread_form"
parent="account.menu_finance_entries_accounting_miscellaneous"
action="action_account_spread_form"
groups="account.group_account_user,account.group_account_manager"
/>
</odoo>

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_spread_template" model="ir.ui.view">
<field name="model">account.spread.template</field>
<field name="arch" type="xml">
<form>
<header>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Spread Template Name" />
<h1>
<field
name="name"
placeholder="e.g. Template cleaning contract"
/>
</h1>
</div>
<group name="main_info">
<group>
<field name="spread_type" />
<field
name="company_id"
groups="base.group_multi_company"
/>
<field name="period_number" />
<field name="period_type" />
<field name="start_date" />
</group>
<group>
<field name="use_invoice_line_account" />
<field
name="spread_account_id"
domain="[('company_id', '=', company_id), ('deprecated', '=', False), ('internal_type','!=','liquidity'), ('internal_group', '!=', 'off_balance')]"
options="{'no_create': True}"
attrs="{'required': [('use_invoice_line_account', '!=', True)], 'invisible': [('use_invoice_line_account', '=', True)]}"
/>
<field
name="exp_rev_account_id"
attrs="{'invisible': ['|', ('use_invoice_line_account', '=', False)], 'required': [('use_invoice_line_account', '=', True)]}"
domain="[('company_id', '=', company_id), ('deprecated', '=', False)]"
options="{'no_create': True}"
/>
<field
name="spread_journal_id"
domain="[('company_id', '=', company_id)]"
widget="selection"
/>
</group>
</group>
<div>
<field name="auto_spread" />
<label for="auto_spread" />
</div>
<p attrs="{'invisible': [('auto_spread', '!=', True)]}">
Automatically use this spread template on invoice validation for invoice lines using below product and/or account and/or analytic,
</p>
<field
name="auto_spread_ids"
attrs="{'invisible': [('auto_spread', '!=', True)]}"
nolabel="1"
>
<tree editable="bottom">
<field name="name" />
<field name="product_id" />
<field name="account_id" />
<field
name="analytic_account_id"
groups="analytic.group_analytic_accounting"
/>
</tree>
</field>
</sheet>
</form>
</field>
</record>
<record id="view_account_spread_template_tree" model="ir.ui.view">
<field name="model">account.spread.template</field>
<field name="arch" type="xml">
<tree>
<field name="name" />
<field name="company_id" groups="base.group_multi_company" />
<field name="spread_account_id" />
<field name="spread_journal_id" />
</tree>
</field>
</record>
<record id="action_account_spread_template_form" model="ir.actions.act_window">
<field name="name">Spread Templates</field>
<field name="res_model">account.spread.template</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_account_spread_template_tree" />
</record>
<menuitem
id="menu_action_account_spread_template_form"
parent="account.account_account_menu"
action="action_account_spread_template_form"
groups="account.group_account_manager"
/>
</odoo>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_company_form" model="ir.ui.view">
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form" />
<field name="arch" type="xml">
<xpath expr="//notebook">
<page
name="account_spread_cost_revenue"
string="Account Spread"
groups="account.group_account_manager"
>
<group>
<group
string="Default Spread Accounts"
name="default_spread_accounts"
>
<field name="default_spread_revenue_account_id" />
<field name="default_spread_expense_account_id" />
</group>
<group
string="Default Spread Journals"
name="default_spread_journals"
>
<field name="default_spread_revenue_journal_id" />
<field name="default_spread_expense_journal_id" />
</group>
</group>
<group name="spreading_options">
<group name="spreading_options_left">
<field name="allow_spread_planning" />
</group>
<group name="spreading_options_right">
<field name="force_move_auto_post" />
<field name="auto_archive_spread" />
</group>
</group>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import account_spread_invoice_line_link_wizard

View File

@@ -0,0 +1,236 @@
# Copyright 2018-2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
class AccountSpreadInvoiceLineLinkWizard(models.TransientModel):
_name = "account.spread.invoice.line.link.wizard"
_description = "Account Spread Invoice Line Link Wizard"
def _selection_spread_action_type(self):
base_selection = [
("template", _("Create from spread template")),
("new", _("Create new spread board")),
]
if not self.env.context.get("allow_spread_planning"):
return base_selection
link_selection = [
("link", _("Link to existing spread board")),
]
return link_selection + base_selection
def _selection_default_spread_action_type(self):
if not self.env.context.get("allow_spread_planning"):
return "template"
return "link"
invoice_line_id = fields.Many2one(
"account.move.line", readonly=True, required=True, ondelete="cascade"
)
invoice_id = fields.Many2one(related="invoice_line_id.move_id", readonly=True)
invoice_type = fields.Selection(
[
("out_invoice", "Customer Invoice"),
("in_invoice", "Vendor Bill"),
("out_refund", "Customer Credit Note"),
("in_refund", "Vendor Credit Note"),
],
compute="_compute_invoice_type",
store=True,
)
spread_type = fields.Selection(
[("sale", "Customer"), ("purchase", "Supplier")],
compute="_compute_invoice_type",
store=True,
)
spread_invoice_type_domain_ids = fields.One2many(
"account.spread", compute="_compute_spread_invoice_type_domain",
)
spread_id = fields.Many2one(
"account.spread",
string="Spread Board",
domain="[('id', 'in', spread_invoice_type_domain_ids)]",
)
company_id = fields.Many2one("res.company", required=True)
spread_action_type = fields.Selection(
selection=_selection_spread_action_type,
default=_selection_default_spread_action_type,
)
template_id = fields.Many2one("account.spread.template", string="Spread Template")
use_invoice_line_account = fields.Boolean(
help="Use invoice line's account as Balance sheet / spread account.\n"
"In this case, user need to select expense/revenue account too.",
)
spread_account_id = fields.Many2one(
"account.account",
string="Balance sheet account / Spread account",
compute="_compute_spread_account",
readonly=False,
store=True,
)
exp_rev_account_id = fields.Many2one(
"account.account",
string="Expense/revenue account",
help="Optional account to overwrite the existing expense/revenue account",
)
spread_journal_id = fields.Many2one(
"account.journal",
string="Spread Journal",
compute="_compute_spread_journal",
readonly=False,
store=True,
)
@api.depends("invoice_line_id")
def _compute_invoice_type(self):
for wizard in self:
invoice = wizard.invoice_line_id.move_id
wizard.invoice_type = invoice.type
if invoice.is_sale_document(include_receipts=True):
wizard.spread_type = "sale"
else:
wizard.spread_type = "purchase"
@api.depends("invoice_type", "company_id")
def _compute_spread_journal(self):
for wizard in self:
journal_revenue = wizard.company_id.default_spread_revenue_journal_id
journal_expense = wizard.company_id.default_spread_expense_journal_id
if wizard.invoice_type in ("out_invoice", "in_refund"):
wizard.spread_journal_id = journal_revenue
else:
wizard.spread_journal_id = journal_expense
@api.depends("invoice_type", "company_id")
def _compute_spread_account(self):
for wizard in self:
acc_revenue = wizard.company_id.default_spread_revenue_account_id
acc_expense = wizard.company_id.default_spread_expense_account_id
if wizard.invoice_type in ("out_invoice", "in_refund"):
wizard.spread_account_id = acc_revenue
else:
wizard.spread_account_id = acc_expense
def _inverse_spread_journal_account(self):
"""Keep this for making the fields editable"""
pass
@api.depends("company_id", "invoice_type")
def _compute_spread_invoice_type_domain(self):
for wizard in self:
spreads = self.env["account.spread"].search(
[
("invoice_id", "=", False),
("invoice_type", "=", wizard.invoice_type),
("company_id", "=", wizard.company_id.id),
]
)
wizard.spread_invoice_type_domain_ids = spreads
@api.onchange("use_invoice_line_account")
def _onchange_user_invoice_line_account(self):
self.spread_account_id = (
self.use_invoice_line_account and self.invoice_line_id.account_id or False
)
self.exp_rev_account_id = False
def confirm(self):
self.ensure_one()
if self.spread_action_type == "link":
if not self.invoice_line_id.spread_id:
self.invoice_line_id.spread_id = self.spread_id
return {
"name": _("Spread Details"),
"view_mode": "form",
"res_model": "account.spread",
"type": "ir.actions.act_window",
"target": "current",
"readonly": False,
"res_id": self.invoice_line_id.spread_id.id,
}
elif self.spread_action_type == "new":
debit_account = credit_account = self.spread_account_id
if self.invoice_type in ("out_invoice", "in_refund"):
credit_account = (
self.exp_rev_account_id or self.invoice_line_id.account_id
)
else:
debit_account = (
self.exp_rev_account_id or self.invoice_line_id.account_id
)
analytic_account = self.invoice_line_id.analytic_account_id
analytic_tags = self.invoice_line_id.analytic_tag_ids
date_invoice = self.invoice_id.invoice_date or fields.Date.today()
return {
"name": _("New Spread Board"),
"view_type": "form",
"view_mode": "form",
"res_model": "account.spread",
"type": "ir.actions.act_window",
"target": "current",
"readonly": False,
"context": {
"default_name": self.invoice_line_id.name,
"default_invoice_type": self.invoice_type,
"default_invoice_line_id": self.invoice_line_id.id,
"default_use_invoice_line_account": self.use_invoice_line_account,
"default_debit_account_id": debit_account.id,
"default_credit_account_id": credit_account.id,
"default_journal_id": self.spread_journal_id.id,
"default_account_analytic_id": analytic_account.id,
"default_analytic_tag_ids": analytic_tags.ids,
"default_spread_date": date_invoice,
},
}
elif self.spread_action_type == "template":
if not self.invoice_line_id.spread_id:
account = self.invoice_line_id.account_id
spread_account_id = False
if self.template_id.use_invoice_line_account:
account = self.template_id.exp_rev_account_id
spread_account_id = self.invoice_line_id.account_id.id
spread_vals = self.template_id._prepare_spread_from_template(
spread_account_id=spread_account_id
)
date_invoice = self.invoice_id.invoice_date
date_invoice = date_invoice or self.template_id.start_date
date_invoice = date_invoice or fields.Date.today()
spread_vals["spread_date"] = date_invoice
spread_vals["name"] = ("%s %s") % (
spread_vals["name"],
self.invoice_line_id.name,
)
if spread_vals["invoice_type"] == "out_invoice":
spread_vals["credit_account_id"] = account.id
else:
spread_vals["debit_account_id"] = account.id
analytic_account = self.invoice_line_id.analytic_account_id
spread_vals["account_analytic_id"] = analytic_account.id
spread_vals["currency_id"] = self.invoice_id.currency_id.id
spread = self.env["account.spread"].create(spread_vals)
analytic_tags = self.invoice_line_id.analytic_tag_ids
spread.analytic_tag_ids = analytic_tags
self.invoice_line_id.spread_id = spread
return {
"name": _("Spread Details"),
"view_mode": "form",
"res_model": "account.spread",
"type": "ir.actions.act_window",
"target": "current",
"readonly": False,
"res_id": self.invoice_line_id.spread_id.id,
}

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_account_spread_invoice_line_link_wizard" model="ir.ui.view">
<field name="model">account.spread.invoice.line.link.wizard</field>
<field name="arch" type="xml">
<form>
<group name="main_info">
<group>
<field
name="company_id"
readonly="1"
groups="base.group_multi_company"
/>
<field name="invoice_type" readonly="1" />
<field name="spread_type" invisible="1" />
<field name="invoice_id" readonly="1" />
<field name="invoice_line_id" readonly="1" />
</group>
<group>
<field name="spread_action_type" widget="radio" />
<field
name="spread_id"
attrs="{'invisible': [('spread_action_type', '!=', 'link')],'required': [('spread_action_type', '=', 'link')]}"
domain="[('invoice_type', '=', invoice_type)]"
options="{'no_create': True}"
/>
<field
name="template_id"
attrs="{'invisible': [('spread_action_type', '!=', 'template')],'required': [('spread_action_type', '=', 'template')]}"
domain="[('spread_type', '=', spread_type)]"
options="{'no_create': True}"
/>
<field
name="use_invoice_line_account"
attrs="{'invisible': [('spread_action_type', '!=', 'new')]}"
/>
<field
name="spread_account_id"
attrs="{'invisible': [('spread_action_type', '!=', 'new')],'required': [('spread_action_type', '=', 'new')]}"
domain="[('company_id', '=', company_id), ('deprecated', '=', False), ('internal_type','!=','liquidity'), ('internal_group', '!=', 'off_balance')]"
options="{'no_create': True}"
/>
<field
name="exp_rev_account_id"
attrs="{'invisible': ['|', ('use_invoice_line_account', '=', False), ('spread_action_type', '!=', 'new')], 'required': [('use_invoice_line_account', '=', True)]}"
domain="[('company_id', '=', company_id), ('deprecated', '=', False)]"
options="{'no_create': True}"
/>
<field
name="spread_journal_id"
attrs="{'invisible': [('spread_action_type', '!=', 'new')],'required': [('spread_action_type', '=', 'new')]}"
domain="[('company_id', '=', company_id)]"
options="{'no_create': True}"
/>
</group>
</group>
<footer>
<button
string="Confirm"
type="object"
name="confirm"
class="btn-primary"
/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
</odoo>