Merge PR #715 into 11.0

Signed-off-by dreispt
This commit is contained in:
OCA-git-bot
2020-08-20 08:03:16 +00:00
42 changed files with 4983 additions and 0 deletions

View File

@@ -0,0 +1,170 @@
===================
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/11.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-11-0/account-financial-tools-11-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/11.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*.
Usage
=====
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:: static/description/spread.png
:alt: Create a new spread board
Click on the button on the top-left to calculate the spread lines.
.. figure:: 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:: 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 unposted, so you should post them manually one by one.
To allow the automatic posting of the accounting moves, set the flag *Auto-post lines* to True.
Create an invoice or vendor bill in draft. On its lines, the spreading right-arrow icon are displayed in dark-grey color.
.. figure:: 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:: 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.
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.
Known issues / Roadmap
======================
* Verify last day of month
* Not yet compatible with cutoff module: create module for adaptation?
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:%2011.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>
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/11.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,29 @@
# Copyright 2016-2019 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": "11.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_invoice.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,17 @@
<?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,742 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_spread_cost_revenue
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-25 13:04+0000\n"
"PO-Revision-Date: 2019-04-25 13:04+0000\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: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 "<span class=\"help-block text-danger\" colspan=\"2\" attrs=\"{'invisible':[('is_credit_account_deprecated','!=',True)]}\">\n"
" Deze grootboekrekening is verouderd! Aflettering is NIET mogelijk.\n"
" </span>"
#. module: account_spread_cost_revenue
#: model: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 "<span class=\"help-block text-danger\" colspan=\"2\" attrs=\"{'invisible':[('is_debit_account_deprecated','!=',True)]}\">\n"
" Deze grootboekrekening is verouderd! Aflettering is NIET mogelijk.\n"
" </span>"
#. module: account_spread_cost_revenue
#: model: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 "<span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}\">\n"
" De balans grootboekrekening welke gebruikt wordt voor transitorische spreiding.<br/>Dit is de tegenrekening van de grootboekrekening gekoppeld aan de factuurregel\n"
" </span>\n"
" <span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}\">\n"
" De kosten grootboekrekening van de inkoopfactuurregel.<br/>Meestal dezelfde grootboekrekning als de inkoopfactuurregel.\n"
" </span>"
#. module: account_spread_cost_revenue
#: model: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 "<span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('out_invoice','in_refund'))]}\">\n"
" De omzet grootboekrekening van de factuurregel.<br/>Meestal dezelfde grootboekrekning als de factuurregel.\n"
" </span>\n"
" <span class=\"help-block\" colspan=\"2\" attrs=\"{'invisible':[('invoice_type','not in',('in_invoice','out_refund'))]}\">\n"
" De balans grootboekrekening welke gebruikt wordt voor transitorische spreiding.<br/>Dit is de tegenrekening van de grootboekrekening gekoppeld aan de inkoopfactuurregel\n"
" </span>"
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_company_form
msgid "Account Spread"
msgstr "Grootboekrekening Spreiding"
#. 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 "Transitorisch Factuur Regel Koppel Wizard"
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread_line
msgid "Account Spread Lines"
msgstr "Grootboekrekening Spreiding Regels"
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_account_spread_template
msgid "Account Spread Template"
msgstr "Transitorische Spreiding Template"
#. 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 "Allow Spread Planning"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line_amount
msgid "Amount"
msgstr "Bedrag"
#. 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 "Kostenplaats"
#. 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 "Labels kostenplaats"
#. 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 "Automatisch boeken regels"
#. 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 "Auto-post spread lines"
#. 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:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Balance sheet account / Spread account"
msgstr "Balance grootboekrekening / Grootboekrekening Spreiding"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_invoice_line_link_wizard
msgid "Cancel"
msgstr "Annuleer"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:461
#, python-format
msgid "Cannot delete spread(s) that are linked to an invoice line."
msgstr "Cannot delete spread(s) that are linked to an invoice line."
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:467
#, python-format
msgid "Cannot delete spread(s): there are posted Journal Entries."
msgstr "Cannot delete spread(s): there are posted Journal Entries."
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:432
#, python-format
msgid "Cannot unlink invoice lines if the invoice is validated"
msgstr "Ontkoppelen van factuurregels is niet mogelijk als de factuur bevestigd is"
#. module: account_spread_cost_revenue
#: model:ir.model,name:account_spread_cost_revenue.model_res_company
msgid "Companies"
msgstr "Bedrijven"
#. 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 "Bedrijf"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_invoice_line_link_wizard
msgid "Confirm"
msgstr "Bevestigen"
#. 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 "Kosten/omzet spreiding: Aanmaken regels"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Create All Moves"
msgstr "Create All Moves"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Create Move"
msgstr "Maak mutatie"
#. 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 "Aangemaakt door"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:36
#, python-format
msgid "Created move(s) "
msgstr "Aangemaakte regel(s) "
#. 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 "Aangemaakt op"
#. 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 "Credit Grootboekrekening"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_currency_id
msgid "Currency"
msgstr "Valuta"
#. 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 "Klant"
#. 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 "Klant Credit Nota"
#. 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 "Klant Factuur"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line_date
msgid "Date"
msgstr "Datum"
#. 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 "Debet Grootboekrekening"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_company_form
msgid "Default Spread Accounts"
msgstr "Standaard Spreiding Grootboekrekeningen"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_company_form
msgid "Default Spread Journals"
msgstr "Standaard Spreiding Dagboeken"
#. 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 "Definieer het aantal spreiding regels"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Delete Move"
msgstr "Verwijder Boeking"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:147
#, python-format
msgid "Deleted move %s"
msgstr "Verwijder boeking %s"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line_name
msgid "Description"
msgstr "Omschrijving"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Details"
msgstr "Details"
#. 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 "Disable this option if you do not want to allow the spreading before the invoice is validated."
#. 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 "Display Button All Moves"
#. 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 "Display Button Auto-post lines"
#. 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 "Display Buttons Recompute"
#. 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 "Schermnaam"
#. 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 "Enable this option if you want to post automatically the accounting moves of all the spreads."
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_estimated_amount
msgid "Estimated Amount"
msgstr "Geschat Bedrag"
#. 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 "Kosten spreiding Grootboekrekening"
#. 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 "Kosten spreiding Dagboek"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Expense account"
msgstr "Kosten grootboekrekening"
#. 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_3843
msgid "ID"
msgstr "ID"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:446
#: 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 "Factuur"
#. 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 "Factuurregel"
#. 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 "Factuurregels"
#. 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 "Factuurreferentie"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard_invoice_type
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_type
msgid "Invoice Type"
msgstr "Factuur Type"
#. 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 "Factuur regel"
#. 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 "Is Credit Grootboekrekening Verouderd"
#. 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 "Is Debet Grootboekrekening Verouderd"
#. 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 "Dagboek"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:128
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line_move_id
#, python-format
msgid "Journal Entry"
msgstr "Boeking"
#. 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 "Laatst gewijzigd op"
#. module: account_spread_cost_revenue
#: 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
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_write_uid
msgid "Last Updated by"
msgstr "Laatst bijgewerkt door"
#. module: account_spread_cost_revenue
#: 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
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_write_date
msgid "Last Updated on"
msgstr "Laatst bijgewerkt op"
#. 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 "Koppel Factuur Regel met Spreiding Tabel"
#. module: account_spread_cost_revenue
#: selection:account.invoice.line,spread_check:0
msgid "Linked"
msgstr "Gekoppeld"
#. 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 "Gekoppeld met spreiding"
#. module: account_spread_cost_revenue
#: selection:account.spread,period_type:0
#: selection:account.spread.template,period_type:0
msgid "Month"
msgstr "Maand"
#. 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 "Naam"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:141
#, python-format
msgid "New Spread Board"
msgstr "Nieuw Spreiding Tabel"
#. 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 "Niet gekoppeld met spreiding"
#. 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 "Aantal Herhalingen"
#. 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 "Periode Type"
#. 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 "Periode lengte voor boekingen"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_posted_amount
msgid "Posted Amount"
msgstr "Geboekte Bedrag"
#. module: account_spread_cost_revenue
#: selection:account.spread,period_type:0
#: selection:account.spread.template,period_type:0
msgid "Quarter"
msgstr "Kwartaal"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Recalculate entire spread"
msgstr "Herbereken volledige spreiding"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Recalculate unposted lines"
msgstr "Herbereken ongeboekte regels"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Reconciled entries"
msgstr "Afgeletterde boekingen"
#. 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 "Omzet Spreiding Grootboekrekening"
#. 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 "Omzet Spreiding Dagboek"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Revenue account"
msgstr "Omzet grootboekrekening"
#. 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 "Spreiding Actie Type"
#. 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 "Spreiding Balans Grootboekrekening"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:451
#: 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
#, python-format
msgid "Spread Board"
msgstr "Spreiding Tabel"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Spread Board Name"
msgstr "Spreiding Tabel Naam"
#. 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 "Spreiding Controle"
#. 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 "Spreiding Kosten/Omzet"
#. 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:120
#: code:addons/account_spread_cost_revenue/wizards/account_spread_invoice_line_link_wizard.py:190
#, python-format
msgid "Spread Details"
msgstr "Spreiding Details"
#. 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 "Spreiding Dagboek"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_line_ids
msgid "Spread Lines"
msgstr "Boekingsregels Spreiding"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_invoice_line_link_wizard_template_id
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template_id
msgid "Spread Template"
msgstr "Spreiding Template"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_template
msgid "Spread Template Name"
msgstr "Spreiding Template Naam"
#. 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 "Spreiding Templates"
#. module: account_spread_cost_revenue
#: 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_spread_type
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_template_spread_type
msgid "Spread Type"
msgstr "Spreiding Type"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Spread lines"
msgstr "Spreiding Regels"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:370
#, python-format
msgid "Spread table '%s' created."
msgstr "Spreiding tabel '%s' aangemaakt."
#. 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 "Startdatum"
#. 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 "Leverancier"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:256
#, python-format
msgid "The Invoice Type does not correspond to the Invoice"
msgstr "De Factuur Type komt niet overeen met de Factuur"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:264
#, python-format
msgid "The Journal is not consistent with the account moves."
msgstr "De dagboek komt niet overeen met de boekingsregels."
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:277
#, python-format
msgid "The Spread Template (Purchases) is not compatible with selected invoice type"
msgstr "De Spreiding Template (Inkoop) is not compatibel met geselecteerde factuur type"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:272
#, python-format
msgid "The Spread Template (Sales) is not compatible with selected invoice type"
msgstr "De Spreiding Template (Verkoop) is not compatibel met geselecteerde factuur type"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread_line.py:67
#, python-format
msgid "This spread line is already linked to a journal entry! Please post or delete it."
msgstr "Deze spreidingsregel is al gekoppeld aan een boekingsregel! Graag boeken of verwijderen."
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "This will delete the move. Are you sure ?"
msgstr "Hiermee wordt de boekingsregel verwijderd. Weet u het zeker?"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Total"
msgstr "Totaal"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_total_amount
msgid "Total Amount"
msgstr "Totaal Bedrag"
#. module: account_spread_cost_revenue
#: selection:account.invoice.line,spread_check:0
msgid "Unavailable"
msgstr "Niet beschikbaar"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Undo spread"
msgstr "Spreiding ongedaan maken"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "Unlink Invoice Line"
msgstr "Ontkoppel Facuur Regel"
#. module: account_spread_cost_revenue
#: selection:account.invoice.line,spread_check:0
msgid "Unlinked"
msgstr "Ontkoppeld"
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:452
#, python-format
msgid "Unlinked '%s' (invoice line %s)."
msgstr "Unlinked '%s' (invoice line %s)."
#. module: account_spread_cost_revenue
#: code:addons/account_spread_cost_revenue/models/account_spread.py:447
#, python-format
msgid "Unlinked invoice line '%s' (view %s)."
msgstr "Unlinked invoice line '%s' (view %s)."
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_unposted_amount
msgid "Unposted Amount"
msgstr "Ongeboekte Bedrag"
#. module: account_spread_cost_revenue
#: model:ir.model.fields,field_description:account_spread_cost_revenue.field_account_spread_unspread_amount
msgid "Unspread Amount"
msgstr "Niet gespeid Bedrag"
#. 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 "Leverancier Facuur"
#. 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 "Leverancier Creditnota"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "View Move"
msgstr "Zie Boeking"
#. module: account_spread_cost_revenue
#: selection:account.spread,period_type:0
#: selection:account.spread.template,period_type:0
msgid "Year"
msgstr "Jaar"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread
msgid "e.g. One year offices cleaning contract"
msgstr "bijv Één jaar kantoor schoonmaak contract"
#. module: account_spread_cost_revenue
#: model:ir.ui.view,arch_db:account_spread_cost_revenue.view_account_spread_template
msgid "e.g. Template cleaning contract"
msgstr "e.g. Template schoonmaak contract"

View File

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

View File

@@ -0,0 +1,54 @@
# Copyright 2016-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, models
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
@api.multi
def action_move_create(self):
"""Invoked when validating the invoices."""
res = super().action_move_create()
spreads = self.mapped('invoice_line_ids.spread_id')
spreads.compute_spread_board()
spreads.reconcile_spread_moves()
return res
@api.multi
def invoice_line_move_line_get(self):
"""Copying expense/revenue account from spread to move lines."""
res = super().invoice_line_move_line_get()
for line in res:
invl_id = line.get('invl_id')
invl = self.env['account.invoice.line'].browse(invl_id)
if invl.spread_id:
if invl.invoice_id.type in ('out_invoice', 'in_refund'):
account = invl.spread_id.debit_account_id
else:
account = invl.spread_id.credit_account_id
line['account_id'] = account.id
return res
@api.multi
def action_cancel(self):
"""Cancel the spread lines and their related moves when
the invoice is canceled."""
res = super().action_cancel()
spread_lines = self.mapped('invoice_line_ids.spread_id.line_ids')
moves = spread_lines.mapped('move_id')
moves.button_cancel()
moves.unlink()
spread_lines.unlink()
return res
@api.model
def _refund_cleanup_lines(self, lines):
result = super()._refund_cleanup_lines(lines)
for i, line in enumerate(lines):
for name in line._fields.keys():
if name == 'spread_id':
result[i][2][name] = False
break
return result

View File

@@ -0,0 +1,67 @@
# Copyright 2016-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.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', 'invoice_id.state')
def _compute_spread_check(self):
for line in self:
if line.spread_id:
line.spread_check = 'linked'
elif line.invoice_id.state == 'draft':
line.spread_check = 'unlinked'
else:
line.spread_check = 'unavailable'
@api.multi
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_type': 'form',
'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
company = self.invoice_id.company_id
ctx = dict(
self.env.context,
default_invoice_line_id=self.id,
default_company_id=company.id,
allow_spread_planning=company.allow_spread_planning,
)
return {
'name': _('Link Invoice Line with Spread Board'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'account.spread.invoice.line.link.wizard',
'type': 'ir.actions.act_window',
'target': 'new',
'context': ctx,
}

View File

@@ -0,0 +1,549 @@
# Copyright 2018-2019 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.addons import decimal_precision as dp
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)
credit_account_id = fields.Many2one(
'account.account',
string='Credit Account',
required=True)
debit_account_id = fields.Many2one(
'account.account',
string='Debit Account',
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=dp.get_precision('Account'),
compute='_compute_amounts')
unposted_amount = fields.Float(
digits=dp.get_precision('Account'),
compute='_compute_amounts')
posted_amount = fields.Float(
digits=dp.get_precision('Account'),
compute='_compute_amounts')
total_amount = fields.Float(
digits=dp.get_precision('Account'),
compute='_compute_amounts')
all_posted = fields.Boolean(
compute='_compute_amounts',
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',
string='Journal',
required=True)
invoice_line_ids = fields.One2many(
'account.invoice.line',
'spread_id',
copy=False,
string='Invoice Lines')
invoice_line_id = fields.Many2one(
'account.invoice.line',
string='Invoice line',
compute='_compute_invoice_line',
inverse='_inverse_invoice_line',
store=True)
invoice_id = fields.Many2one(
related='invoice_line_id.invoice_id',
readonly=True,
store=True,
string='Invoice')
estimated_amount = fields.Float(digits=dp.get_precision('Account'))
company_id = fields.Many2one(
'res.company',
default=lambda self: self.env.user.company_id,
string='Company',
required=True)
currency_id = fields.Many2one(
'res.currency',
string='Currency',
required=True,
default=lambda self: self.env.user.company_id.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',
string='Display Button All Moves')
display_recompute_buttons = fields.Boolean(
compute='_compute_display_recompute_buttons',
string='Display Buttons Recompute')
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_get(self, fields):
res = super().default_get(fields)
if 'company_id' not in fields:
company_id = self.env.user.company_id.id
else:
company_id = res['company_id']
default_journal = self.env['account.journal'].search([
('type', '=', 'general'),
('company_id', '=', company_id)
], limit=1)
if 'journal_id' not in res and 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.invoice_id')
def _compute_invoice_line(self):
for spread in self:
invoice_lines = spread.invoice_line_ids
line = invoice_lines and invoice_lines[0] or False
spread.invoice_line_id = line
@api.multi
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', 'invoice_line_id.price_subtotal',
'line_ids.move_id.amount', 'line_ids.move_id.state')
def _compute_amounts(self):
for spread in self:
moves_amount = 0.0
posted_amount = 0.0
total_amount = spread.estimated_amount
if spread.invoice_line_id:
total_amount = spread.invoice_line_id.price_subtotal
for spread_line in spread.line_ids:
if spread_line.move_id:
moves_amount += spread_line.amount
if spread_line.move_id.state == 'posted':
posted_amount += spread_line.amount
spread.unspread_amount = total_amount - moves_amount
spread.unposted_amount = total_amount - posted_amount
spread.posted_amount = posted_amount
spread.total_amount = total_amount
spread.all_posted = spread.unposted_amount == 0.0
@api.multi
def _compute_display_create_all_moves(self):
for spread in self:
if any(not line.move_id for line in spread.line_ids):
spread.display_create_all_moves = True
else:
spread.display_create_all_moves = False
@api.multi
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.multi
def _compute_display_move_line_auto_post(self):
for spread in self:
spread.display_move_line_auto_post = True
if spread.company_id.force_move_auto_post:
spread.display_move_line_auto_post = False
@api.multi
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.onchange('invoice_type', 'company_id')
def onchange_invoice_type(self):
company = self.company_id
if not self.env.context.get('default_journal_id'):
journal = company.default_spread_expense_journal_id
if self.invoice_type in ('out_invoice', 'in_refund'):
journal = company.default_spread_revenue_journal_id
if journal:
self.journal_id = journal
if not self.env.context.get('default_debit_account_id'):
if self.invoice_type in ('out_invoice', 'in_refund'):
debit_account_id = company.default_spread_revenue_account_id
self.debit_account_id = debit_account_id
if not self.env.context.get('default_credit_account_id'):
if self.invoice_type in ('in_invoice', 'out_refund'):
credit_account_id = company.default_spread_expense_account_id
self.credit_account_id = credit_account_id
@api.constrains('invoice_id', 'invoice_type')
def _check_invoice_type(self):
for spread in self:
if not spread.invoice_id:
pass
elif spread.invoice_type != spread.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):
raise ValidationError(_(
'The Journal is not consistent with the account moves.'))
@api.constrains('template_id', 'invoice_type')
def _check_template_invoice_type(self):
for spread in self:
if spread.invoice_type in ['in_invoice', 'in_refund']:
if spread.template_id.spread_type == 'sale':
raise ValidationError(_(
'The Spread Template (Sales) is not compatible '
'with selected invoice type'))
elif spread.invoice_type in ['out_invoice', 'out_refund']:
if spread.template_id.spread_type == 'purchase':
raise ValidationError(_(
'The Spread Template (Purchases) is not compatible '
'with selected invoice type'))
@api.multi
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
@api.multi
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 = fields.Date.from_string(posted_line_ids[-1].date)
months = self._get_spread_period_duration()
spread_date = last_date + relativedelta(months=months)
else:
spread_date = fields.Date.from_string(self.spread_date)
return spread_date
@api.multi
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
@api.multi
def _compute_spread_board(self):
"""Creates the spread lines. This method is highly inspired
from method compute_depreciation_board() present in standard
"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)
@api.multi
def _get_number_of_periods(self, month_day):
"""Calculates the number of spread lines."""
self.ensure_one()
if month_day != 1:
return self.period_number + 1
return self.period_number
@staticmethod
def _get_last_day_of_month(spread_date):
return fields.Date.to_string(spread_date + relativedelta(day=31))
@api.multi
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 = fields.Datetime.from_string(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
@api.multi
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()
@api.multi
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()
@api.multi
def action_undo_spread(self):
"""Undo spreading: Remove all created moves,
restore original account on move line"""
self.ensure_one()
self.mapped('line_ids').filtered('move_id').unlink_move()
self.mapped('line_ids').unlink()
@api.multi
def action_unlink_invoice_line(self):
"""Unlink the invoice line from the spread board"""
self.ensure_one()
if self.invoice_id.state != 'draft':
raise UserError(
_("Cannot unlink invoice lines if the invoice is validated"))
self._action_unlink_invoice_line()
@api.multi
def _action_unlink_invoice_line(self):
spread_mls = self.mapped('line_ids.move_id.line_ids')
spread_mls.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:
invoice_id = spread.invoice_id.id
inv_link = '<a href=# data-oe-model=account.invoice ' \
'data-oe-id=%d>%s</a>' % (invoice_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)
@api.multi
def unlink(self):
if self.filtered(lambda s: s.invoice_line_id):
raise UserError(
_('Cannot delete spread(s) that are linked '
'to an invoice line.'))
if self.mapped('line_ids.move_id').filtered(
lambda m: m.state == 'posted'):
raise ValidationError(
_('Cannot delete spread(s): there are '
'posted Journal Entries.'))
return super().unlink()
@api.multi
def reconcile_spread_moves(self):
for spread in self:
spread._reconcile_spread_moves()
@api.multi
def _reconcile_spread_moves(self, created_moves=False):
"""Reconcile spread moves if possible"""
self.ensure_one()
if not self.invoice_id.number:
return
spread_mls = self.line_ids.mapped('move_id.line_ids')
if created_moves:
spread_mls |= created_moves.mapped('line_ids')
spread_sign = True if self.total_amount >= 0.0 else False
in_invoice_or_out_refund = ('in_invoice', 'out_refund')
if self.invoice_type in in_invoice_or_out_refund and spread_sign:
spread_mls = spread_mls.filtered(lambda x: x.credit != 0.)
elif self.invoice_type in in_invoice_or_out_refund:
spread_mls = spread_mls.filtered(lambda x: x.debit != 0.)
elif spread_sign:
spread_mls = spread_mls.filtered(lambda x: x.debit != 0.)
else:
spread_mls = spread_mls.filtered(lambda x: x.credit != 0.)
invoice_mls = self.invoice_id.move_id.mapped('line_ids')
if self.invoice_id.type in in_invoice_or_out_refund and spread_sign:
invoice_mls = invoice_mls.filtered(lambda x: x.debit != 0.)
elif self.invoice_id.type in in_invoice_or_out_refund:
invoice_mls = invoice_mls.filtered(lambda x: x.credit != 0.)
elif spread_sign:
invoice_mls = invoice_mls.filtered(lambda x: x.credit != 0.)
else:
invoice_mls = invoice_mls.filtered(lambda x: x.debit != 0.)
to_be_reconciled = self.env['account.move.line']
if len(invoice_mls) > 1:
# Refine selection of move line.
# The name is formatted the same way as it is done when creating
# move lines in method "def invoice_line_move_line_get()" of
# standard account module
raw_name = self.invoice_line_id.name
formatted_name = raw_name.split('\n')[0][:64]
for move_line in invoice_mls:
if move_line.name == formatted_name:
to_be_reconciled |= move_line
else:
to_be_reconciled = invoice_mls
if len(to_be_reconciled) == 1:
do_reconcile = spread_mls + to_be_reconciled
do_reconcile.remove_move_reconcile()
do_reconcile.reconcile()
@api.multi
def create_all_moves(self):
for line in self.mapped('line_ids').filtered(lambda l: not l.move_id):
line.create_move()
@api.depends(
'debit_account_id.deprecated', 'credit_account_id.deprecated')
def _compute_deprecated_accounts(self):
for spread in self:
debit_deprecated = bool(spread.debit_account_id.deprecated)
credit_deprecated = bool(spread.credit_account_id.deprecated)
spread.is_debit_account_deprecated = debit_deprecated
spread.is_credit_account_deprecated = credit_deprecated
@api.multi
def open_reconcile_view(self):
self.ensure_one()
spread_mls = self.line_ids.mapped('move_id.line_ids')
return spread_mls.open_reconcile_view()

View File

@@ -0,0 +1,173 @@
# Copyright 2016-2019 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.addons import decimal_precision as dp
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=dp.get_precision('Account'), required=True)
date = fields.Date(required=True)
spread_id = fields.Many2one(
'account.spread', string='Spread Board', ondelete='cascade')
move_id = fields.Many2one(
'account.move', string='Journal Entry', readonly=True)
@api.multi
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)
spread._reconcile_spread_moves(created_moves)
self._post_spread_moves(spread, created_moves)
@api.model
def _post_spread_moves(self, spread, moves):
if not moves:
return
if spread.company_id.force_move_auto_post:
moves.post()
elif spread.move_line_auto_post:
moves.post()
@api.multi
def create_move(self):
"""Button to manually create a move from a spread line entry.
"""
self.ensure_one()
self.create_and_reconcile_moves()
@api.multi
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.write({'move_id': move.id})
created_moves += move
return created_moves
@api.multi
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 = [(4, tag.id, None) for tag in spread.analytic_tag_ids]
company_currency = spread.company_id.currency_id
current_currency = spread.currency_id
not_same_curr = company_currency != current_currency
amount = current_currency.with_context(date=spread_date).compute(
self.amount, company_currency)
line_ids = [(0, 0, {
'name': spread.name.split('\n')[0][:64],
'account_id': spread.debit_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.split('\n')[0][:64],
'account_id': spread.credit_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,
}
@api.multi
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_type': 'form',
'view_mode': 'form',
'res_model': 'account.move',
'view_id': False,
'type': 'ir.actions.act_window',
'res_id': self.move_id.id,
}
@api.multi
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.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)
spreads_to_archive.write({'active': False})

View File

@@ -0,0 +1,94 @@
# Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
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.user.company_id,
string='Company',
required=True)
spread_journal_id = fields.Many2one(
'account.journal',
string='Journal',
required=True)
spread_account_id = fields.Many2one(
'account.account',
string='Spread Balance Sheet Account',
required=True)
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()
@api.model
def default_get(self, fields):
res = super().default_get(fields)
if 'company_id' not in fields:
company_id = self.env.user.company_id.id
else:
company_id = res['company_id']
default_journal = self.env['account.journal'].search([
('type', '=', 'general'),
('company_id', '=', company_id)],
limit=1)
if 'spread_journal_id' not in res and default_journal:
res['spread_journal_id'] = default_journal.id
return res
@api.onchange('spread_type', 'company_id')
def onchange_spread_type(self):
company = self.company_id
if self.spread_type == 'sale':
account = company.default_spread_revenue_account_id
journal = company.default_spread_revenue_journal_id
else:
account = company.default_spread_expense_account_id
journal = company.default_spread_expense_journal_id
if account:
self.spread_account_id = account
if journal:
self.spread_journal_id = journal
def _prepare_spread_from_template(self):
self.ensure_one()
company = self.company_id
spread_vals = {
'name': self.name,
'template_id': self.id,
'journal_id': self.spread_journal_id.id,
'company_id': company.id,
}
if self.spread_type == 'sale':
invoice_type = 'out_invoice'
spread_vals['debit_account_id'] = self.spread_account_id.id
else:
invoice_type = 'in_invoice'
spread_vals['credit_account_id'] = self.spread_account_id.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

View File

@@ -0,0 +1,33 @@
# Copyright 2018-2019 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 = 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,23 @@
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.

View File

@@ -0,0 +1 @@
* Andrea Stirpe <a.stirpe@onestein.nl>

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,5 @@
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,2 @@
* Verify last day of month
* Add help in fields definition

View File

@@ -0,0 +1,75 @@
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/11.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/11.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/11.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/11.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/11.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*
* *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.

View File

@@ -0,0 +1,13 @@
<?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','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,7 @@
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
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

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,503 @@
<?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.14: 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/11.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-11-0/account-financial-tools-11-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/11.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="id1">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="id8">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="id9">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">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>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<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/11.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/11.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/11.0/account_spread_cost_revenue/static/description/update_spread.png" />
</div>
<p>By default, the status of the created accounting moves is unposted, so you should post them manually one by one.
To allow the automatic posting of the accounting moves, set the flag <em>Auto-post lines</em> to True.</p>
<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/11.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/11.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>
<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="known-issues-roadmap">
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
<ul class="simple">
<li>Verify last day of month</li>
<li>Not yet compatible with cutoff module: create module for adaptation?</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">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:%2011.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="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>Onestein</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">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>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#id8">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="#id9">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/11.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,62 @@
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',
}),
description: "",
/**
* @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: @brand-success;
}
}

View File

@@ -0,0 +1,11 @@
<?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/less/account_spread.less"/>
<script type="text/javascript" src="/account_spread_cost_revenue/static/src/js/account_spread.js"></script>
</xpath>
</template>
</odoo>

View File

@@ -0,0 +1,5 @@
# 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

View File

@@ -0,0 +1,770 @@
# Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tools import convert_file
from odoo.modules.module import get_module_resource
from odoo.exceptions import UserError, ValidationError
from odoo.tests import common
class TestAccountInvoiceSpread(common.TransactionCase):
def _load(self, module, *args):
convert_file(
self.cr,
'account_spread_cost_revenue',
get_module_resource(module, *args),
{}, 'init', False, 'test', self.registry._assertion_report)
def setUp(self):
super().setUp()
self._load('account', 'test', 'account_minimal_test.xml')
type_receivable = self.env.ref('account.data_account_type_receivable')
type_payable = self.env.ref('account.data_account_type_payable')
type_revenue = self.env.ref('account.data_account_type_revenue')
self.invoice_account = self.env['account.account'].create({
'name': 'test_account_receivable',
'code': '123',
'user_type_id': type_receivable.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
})
self.invoice_line_account = self.account_payable
self.spread_account = self.env['account.account'].create({
'name': 'test spread account_payable',
'code': '765',
'user_type_id': type_payable.id,
'reconcile': True
})
partner = self.env['res.partner'].create({
'name': 'Partner Name',
'supplier': True,
})
self.invoice = self.env['account.invoice'].with_context(
default_type='in_invoice'
).create({
'partner_id': partner.id,
'account_id': self.invoice_account.id,
'type': 'in_invoice',
})
self.invoice_line = self.env['account.invoice.line'].create({
'quantity': 1.0,
'price_unit': 1000.0,
'invoice_id': self.invoice.id,
'name': 'product that cost 1000',
'account_id': self.invoice_account.id,
})
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'].create({
'name': 'test',
'debit_account_id': self.spread_account.id,
'credit_account_id': self.invoice_line_account.id,
'period_number': 12,
'period_type': 'month',
'spread_date': '2017-02-01',
'estimated_amount': 1000.0,
'journal_id': self.invoice.journal_id.id,
'invoice_type': 'in_invoice',
'account_analytic_id': self.analytic_account.id,
'analytic_tag_ids': analytic_tags,
})
self.invoice_2 = self.env['account.invoice'].with_context(
default_type='out_invoice'
).create({
'partner_id': partner.id,
'account_id': self.invoice_account.id,
'type': 'out_invoice',
})
self.invoice_line_2 = self.env['account.invoice.line'].create({
'quantity': 1.0,
'price_unit': 1000.0,
'invoice_id': self.invoice_2.id,
'name': 'product that cost 1000',
'account_id': self.invoice_line_account.id,
})
self.spread2 = self.env['account.spread'].create({
'name': 'test2',
'debit_account_id': self.spread_account.id,
'credit_account_id': self.invoice_line_account.id,
'period_number': 12,
'period_type': 'month',
'spread_date': '2017-02-01',
'estimated_amount': 1000.0,
'journal_id': self.invoice_2.journal_id.id,
'invoice_type': 'out_invoice',
})
def test_01_wizard_defaults(self):
my_company = self.env.user.company_id
Wizard = self.env['account.spread.invoice.line.link.wizard']
wizard1 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=my_company.id,
allow_spread_planning=True,
).create({})
self.assertEqual(wizard1.invoice_line_id, self.invoice_line)
self.assertEqual(wizard1.invoice_line_id.invoice_id, self.invoice)
self.assertEqual(wizard1.invoice_type, 'in_invoice')
self.assertFalse(wizard1.spread_id)
self.assertEqual(wizard1.company_id, my_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_2.id,
default_company_id=my_company.id,
).create({})
self.assertEqual(wizard2.invoice_line_id, self.invoice_line_2)
self.assertEqual(wizard2.invoice_line_id.invoice_id, self.invoice_2)
self.assertEqual(wizard2.invoice_type, 'out_invoice')
self.assertFalse(wizard2.spread_id)
self.assertEqual(wizard2.company_id, my_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):
my_company = self.env.user.company_id
Wizard = self.env['account.spread.invoice.line.link.wizard']
account_revenue = self.account_revenue
account_payable = self.account_payable
exp_journal = self.ref('account_spread_cost_revenue.expenses_journal')
sales_journal = self.ref('account_spread_cost_revenue.sales_journal')
my_company.default_spread_revenue_account_id = account_revenue
my_company.default_spread_expense_account_id = account_payable
my_company.default_spread_revenue_journal_id = sales_journal
my_company.default_spread_expense_journal_id = exp_journal
self.assertTrue(my_company.default_spread_revenue_account_id)
self.assertTrue(my_company.default_spread_expense_account_id)
self.assertTrue(my_company.default_spread_revenue_journal_id)
self.assertTrue(my_company.default_spread_expense_journal_id)
wizard1 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=my_company.id,
allow_spread_planning=True,
).create({})
self.assertEqual(wizard1.invoice_line_id, self.invoice_line)
self.assertEqual(wizard1.invoice_line_id.invoice_id, self.invoice)
self.assertEqual(wizard1.invoice_type, 'in_invoice')
self.assertFalse(wizard1.spread_id)
self.assertEqual(wizard1.company_id, my_company)
self.assertEqual(wizard1.spread_action_type, 'link')
self.assertFalse(wizard1.spread_account_id)
self.assertFalse(wizard1.spread_journal_id)
res_onchange = wizard1.onchange_invoice_type()
self.assertTrue(res_onchange)
self.assertTrue(res_onchange.get('domain'))
wizard1._onchange_spread_journal_account()
self.assertTrue(wizard1.spread_account_id)
self.assertTrue(wizard1.spread_journal_id)
self.assertEqual(wizard1.spread_account_id, account_payable)
self.assertEqual(wizard1.spread_journal_id.id, exp_journal)
wizard2 = Wizard.with_context(
default_invoice_line_id=self.invoice_line_2.id,
default_company_id=my_company.id,
).create({})
self.assertEqual(wizard2.invoice_line_id, self.invoice_line_2)
self.assertEqual(wizard2.invoice_line_id.invoice_id, self.invoice_2)
self.assertEqual(wizard2.invoice_type, 'out_invoice')
self.assertFalse(wizard2.spread_id)
self.assertEqual(wizard2.company_id, my_company)
self.assertEqual(wizard2.spread_action_type, 'template')
self.assertFalse(wizard2.spread_account_id)
self.assertFalse(wizard2.spread_journal_id)
res_onchange = wizard2.onchange_invoice_type()
self.assertTrue(res_onchange)
self.assertTrue(res_onchange.get('domain'))
wizard2._onchange_spread_journal_account()
self.assertTrue(wizard2.spread_account_id)
self.assertTrue(wizard2.spread_journal_id)
self.assertEqual(wizard2.spread_account_id, account_revenue)
self.assertEqual(wizard2.spread_journal_id.id, sales_journal)
def test_03_link_invoice_line_with_spread_sheet(self):
my_company = self.env.user.company_id
Wizard = self.env['account.spread.invoice.line.link.wizard']
wizard1 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=my_company.id,
allow_spread_planning=True,
).create({})
self.assertEqual(wizard1.spread_action_type, 'link')
wizard1.spread_account_id = self.account_revenue
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.invoice_line)
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
self.invoice.journal_id.update_posted = True
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
self.assertTrue(line.move_id.journal_id.update_posted)
for ml in line.move_id.line_ids:
ml_analytic_account = ml.analytic_account_id
analytic_tag = self.env.ref('analytic.tag_contract')
self.assertEqual(ml_analytic_account, self.analytic_account)
self.assertEqual(ml.analytic_tag_ids, analytic_tag)
self.spread.invoice_id.action_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):
my_company = self.env.user.company_id
Wizard = self.env['account.spread.invoice.line.link.wizard']
spread_account = self.account_revenue
spread_journal_id = self.ref(
'account_spread_cost_revenue.expenses_journal')
wizard1 = Wizard.with_context(
default_invoice_line_id=self.invoice_line.id,
default_company_id=my_company.id,
).create({
'spread_action_type': 'new',
})
self.assertEqual(wizard1.spread_action_type, 'new')
wizard1.write({
'spread_account_id': spread_account.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'))
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
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_2.id,
default_company_id=my_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'))
spread_lines = self.spread2.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
self.spread2.compute_spread_board()
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
def test_05_new_spread_sheet_from_template(self):
my_company = self.env.user.company_id
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.invoice_line.id,
default_company_id=my_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'))
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
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_2.id,
default_company_id=my_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'))
spread_lines = self.spread2.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
self.spread2.compute_spread_board()
for line in spread_lines:
line.create_move()
self.assertTrue(line.move_id)
def test_06_open_wizard(self):
res_action = self.invoice_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.invoice_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_2.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):
self.invoice_line.copy()
self.assertEqual(len(self.invoice.invoice_line_ids), 2)
self.invoice.invoice_line_ids[0].spread_id = self.spread
self.assertTrue(self.spread.invoice_line_id)
self.assertEqual(self.spread.invoice_line_id, self.invoice_line)
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
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.invoice.action_invoice_open()
def test_09_no_link_invoice(self):
balance_sheet = self.spread.credit_account_id
# Validate invoice
self.invoice.action_invoice_open()
invoice_mls = self.invoice.move_id.mapped('line_ids')
self.assertTrue(invoice_mls)
for invoice_ml in invoice_mls:
if invoice_ml.debit:
self.assertNotEqual(invoice_ml.account_id, balance_sheet)
def test_10_link_vendor_bill_line_with_spread_sheet(self):
copied_line = self.invoice_line.copy()
copied_line.name = 'new test line'
self.spread.write({
'estimated_amount': 1000.0,
'period_number': 12,
'period_type': 'month',
'spread_date': '2017-01-07',
'invoice_line_id': self.invoice_line.id,
'move_line_auto_post': False,
})
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
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.invoice.action_invoice_open()
invoice_mls = self.invoice.move_id.mapped('line_ids')
self.assertTrue(invoice_mls)
count_balance_sheet = 0
for invoice_ml in invoice_mls:
if invoice_ml.account_id == balance_sheet:
count_balance_sheet += 1
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):
self.invoice_line.copy()
self.spread.write({
'estimated_amount': 1000.0,
'period_number': 12,
'period_type': 'month',
'spread_date': '2017-01-07',
'invoice_line_id': self.invoice_line.id,
'move_line_auto_post': False,
})
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
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.invoice.action_invoice_open()
invoice_mls = self.invoice.move_id.mapped('line_ids')
self.assertTrue(invoice_mls)
count_balance_sheet = 0
for invoice_ml in invoice_mls:
if invoice_ml.account_id == balance_sheet:
count_balance_sheet += 1
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:
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.invoice_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):
self.spread2.write({
'estimated_amount': 1000.0,
'period_number': 12,
'period_type': 'month',
'spread_date': '2017-01-07',
'invoice_line_id': self.invoice_line_2.id,
'move_line_auto_post': False,
})
spread_lines = self.spread2.line_ids
for line in spread_lines:
self.assertFalse(line.move_id)
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.spread.credit_account_id
balance_sheet = self.spread.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)
if spread_ml.credit:
self.assertEqual(spread_ml.account_id, payable_account)
# Validate invoice
self.invoice_2.action_invoice_open()
invoice_mls = self.invoice_2.move_id.mapped('line_ids')
self.assertTrue(invoice_mls)
for invoice_ml in invoice_mls:
if invoice_ml.credit:
self.assertEqual(invoice_ml.account_id, balance_sheet)
self.spread2.line_ids.create_and_reconcile_moves()
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.assertTrue(spread_ml.full_reconcile_id)
if spread_ml.credit:
self.assertFalse(spread_ml.full_reconcile_id)
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_2.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': '2017-01-07',
})
self.spread2.compute_spread_board()
spread_lines = self.spread2.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')
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.spread.debit_account_id
self.assertTrue(balance_sheet.reconcile)
self.spread2.write({
'invoice_line_id': self.invoice_line_2.id,
})
# Validate invoice
self.invoice_2.action_invoice_open()
invoice_mls = self.invoice_2.move_id.mapped('line_ids')
self.assertTrue(invoice_mls)
for invoice_ml in invoice_mls:
if invoice_ml.credit:
self.assertEqual(invoice_ml.account_id, balance_sheet)
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.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()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 12)
for line in spread_lines:
self.assertFalse(line.move_id)
# create moves for all the spread lines
self.spread.create_all_moves()
spread_lines = self.spread.line_ids
for line in spread_lines:
self.assertTrue(line.move_id)
with self.assertRaises(ValidationError):
self.spread.unlink()
def test_15_invoice_refund(self):
self.invoice_line.spread_id = self.spread
# Validate invoice
self.invoice.action_invoice_open()
self.assertTrue(self.invoice.invoice_line_ids.mapped('spread_id'))
# Create a refund for invoice.
self.env['account.invoice.refund'].with_context({
'active_model': 'account.invoice',
'active_ids': [self.invoice.id],
'active_id': self.invoice.id
}).create(dict(
description='Invoice Refund',
filter_refund='refund',
)).invoice_refund()
# Invoice lines do not contain the lint to the spread.
refund = self.invoice.refund_invoice_ids[0]
self.assertFalse(refund.invoice_line_ids.mapped('spread_id'))

View File

@@ -0,0 +1,406 @@
# Copyright 2018-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import time
from psycopg2 import IntegrityError
from odoo.tools import convert_file, mute_logger
from odoo.modules.module import get_module_resource
from odoo.exceptions import ValidationError
from odoo.tests import common
class TestAccountSpreadCostRevenue(common.TransactionCase):
def _load(self, module, *args):
convert_file(
self.cr,
'account_spread_cost_revenue',
get_module_resource(module, *args),
{}, 'init', False, 'test', self.registry._assertion_report)
def setUp(self):
super().setUp()
self._load('account', 'test', 'account_minimal_test.xml')
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.account_receivable = self.env['account.account'].create({
'name': 'test_account_receivable',
'code': '123',
'user_type_id': type_receivable.id,
'reconcile': True
})
self.credit_account = self.account_receivable
self.account_expenses = self.env['account.account'].create({
'name': 'test account_expenses',
'code': '765',
'user_type_id': type_expenses.id,
'reconcile': True
})
self.debit_account = self.account_expenses
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):
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,
})
my_company = self.env.user.company_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, my_company)
self.assertEqual(spread.currency_id, my_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.)
self.assertEqual(spread.unposted_amount, 0.)
self.assertEqual(spread.total_amount, 0.)
self.assertEqual(spread.estimated_amount, 0.)
self.assertEqual(spread.spread_date, time.strftime('%Y-01-01'))
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):
my_company = self.env.user.company_id
self.assertFalse(my_company.default_spread_revenue_account_id)
self.assertFalse(my_company.default_spread_expense_account_id)
self.assertFalse(my_company.default_spread_revenue_journal_id)
self.assertFalse(my_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):
my_company = self.env.user.company_id
account_revenue = self.account_revenue
exp_journal = self.ref('account_spread_cost_revenue.expenses_journal')
sales_journal = self.ref('account_spread_cost_revenue.sales_journal')
my_company.default_spread_revenue_account_id = account_revenue
my_company.default_spread_expense_account_id = self.account_payable
my_company.default_spread_revenue_journal_id = sales_journal
my_company.default_spread_expense_journal_id = exp_journal
self.assertTrue(my_company.default_spread_revenue_account_id)
self.assertTrue(my_company.default_spread_expense_account_id)
self.assertTrue(my_company.default_spread_revenue_journal_id)
self.assertTrue(my_company.default_spread_expense_journal_id)
spread = self.env['account.spread'].new({
'name': 'test',
})
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.assertFalse(spread.move_line_auto_post)
defaults = (self.env['account.spread'].default_get([
'company_id',
'currency_id',
]))
self.assertEqual(defaults['company_id'], my_company.id)
self.assertEqual(defaults['currency_id'], my_company.currency_id.id)
spread.invoice_type = 'out_invoice'
spread.company_id = my_company
spread.onchange_invoice_type()
self.assertEqual(spread.debit_account_id, account_revenue)
self.assertFalse(spread.credit_account_id)
self.assertEqual(spread.journal_id.id, sales_journal)
self.assertEqual(spread.spread_type, 'sale')
spread.invoice_type = 'in_invoice'
spread.onchange_invoice_type()
self.assertEqual(spread.credit_account_id, self.account_payable)
self.assertEqual(spread.journal_id.id, exp_journal)
self.assertEqual(spread.spread_type, 'purchase')
def test_06_invoice_line_compute_spread_check(self):
invoice_account = self.account_receivable
invoice_line_account = self.account_expenses
invoice = self.env['account.invoice'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'account_id': invoice_account.id,
'type': 'in_invoice',
})
invoice_line = self.env['account.invoice.line'].create({
'product_id': self.env.ref('product.product_product_4').id,
'quantity': 1.0,
'price_unit': 100.0,
'invoice_id': invoice.id,
'name': 'product that cost 100',
'account_id': invoice_line_account.id,
})
invoice_line2 = invoice_line.copy()
self.assertFalse(invoice_line.spread_id)
self.assertEqual(invoice_line.spread_check, 'unlinked')
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,
})
invoice_line.spread_id = spread
self.assertTrue(invoice_line.spread_id)
self.assertEqual(invoice_line.spread_check, 'linked')
self.assertFalse(invoice_line2.spread_id)
self.assertEqual(invoice_line2.spread_check, 'unlinked')
invoice.action_invoice_open()
self.assertTrue(invoice_line.spread_id)
self.assertEqual(invoice_line.spread_check, 'linked')
self.assertFalse(invoice_line2.spread_id)
self.assertEqual(invoice_line2.spread_check, 'unavailable')
self.assertTrue(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
def test_07_create_spread_template(self):
account_revenue = self.account_revenue
account_payable = self.account_payable
spread_template = self.env['account.spread.template'].create({
'name': 'test',
'spread_type': 'sale',
'spread_account_id': account_revenue.id,
})
my_company = self.env.user.company_id
self.assertEqual(spread_template.company_id, my_company)
self.assertTrue(spread_template.spread_journal_id)
exp_journal = self.ref('account_spread_cost_revenue.expenses_journal')
sales_journal = self.ref('account_spread_cost_revenue.sales_journal')
my_company.default_spread_revenue_account_id = account_revenue
my_company.default_spread_expense_account_id = account_payable
my_company.default_spread_revenue_journal_id = sales_journal
my_company.default_spread_expense_journal_id = exp_journal
spread_template.spread_type = 'purchase'
spread_template.onchange_spread_type()
self.assertTrue(spread_template.spread_journal_id)
self.assertTrue(spread_template.spread_account_id)
self.assertEqual(spread_template.spread_account_id, account_payable)
self.assertEqual(spread_template.spread_journal_id.id, exp_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'
spread_template.onchange_spread_type()
self.assertTrue(spread_template.spread_journal_id)
self.assertTrue(spread_template.spread_account_id)
self.assertEqual(spread_template.spread_account_id, account_revenue)
self.assertEqual(spread_template.spread_journal_id.id, 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):
account_revenue = self.account_revenue
template_sale = self.env['account.spread.template'].create({
'name': 'test',
'spread_type': 'sale',
'spread_account_id': 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,
})
spread.template_id = template_sale
self.assertEqual(spread.template_id, template_sale)
with self.assertRaises(ValidationError):
spread.template_id = template_purchase
self.assertEqual(spread.invoice_type, 'out_invoice')
spread.onchange_template()
self.assertEqual(spread.invoice_type, 'in_invoice')
spread.template_id = False
spread.invoice_type = 'in_invoice'
spread.template_id = template_purchase
self.assertEqual(spread.template_id, template_purchase)
with self.assertRaises(ValidationError):
spread.template_id = template_sale
self.assertEqual(spread.invoice_type, 'in_invoice')
spread.onchange_template()
self.assertEqual(spread.invoice_type, 'out_invoice')
spread.template_id = False
spread.invoice_type = 'out_invoice'
spread.template_id = template_sale
self.assertEqual(spread.template_id, template_sale)
with self.assertRaises(ValidationError):
spread.invoice_type = 'in_invoice'
self.assertEqual(spread.invoice_type, 'in_invoice')
spread.onchange_template()
self.assertEqual(spread.invoice_type, 'out_invoice')
spread.template_id = False
spread.invoice_type = 'in_invoice'
spread.template_id = template_purchase
self.assertEqual(spread.template_id, template_purchase)
with self.assertRaises(ValidationError):
spread.invoice_type = 'out_invoice'
self.assertEqual(spread.invoice_type, 'out_invoice')
spread.onchange_template()
self.assertEqual(spread.invoice_type, 'in_invoice')
self.assertFalse(spread.display_create_all_moves)
self.assertTrue(spread.display_recompute_buttons)
self.assertTrue(spread.display_move_line_auto_post)
def test_09_wrong_invoice_type(self):
invoice_account = self.account_receivable
invoice_line_account = self.account_expenses
invoice = self.env['account.invoice'].create({
'partner_id': self.env.ref('base.res_partner_2').id,
'account_id': invoice_account.id,
'type': 'in_invoice',
})
invoice_line = self.env['account.invoice.line'].create({
'product_id': self.env.ref('product.product_product_4').id,
'quantity': 1.0,
'price_unit': 100.0,
'invoice_id': invoice.id,
'name': 'product that cost 100',
'account_id': invoice_line_account.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):
invoice_line.spread_id = spread
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,603 @@
# Copyright 2017-2019 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo.tests import common
from odoo.exceptions import UserError
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('2017-02-28', spread_lines[0].date)
self.assertEqual('2017-03-31', spread_lines[1].date)
self.assertEqual('2017-04-30', spread_lines[2].date)
self.assertEqual('2017-05-31', spread_lines[3].date)
self.assertEqual('2017-06-30', spread_lines[4].date)
self.assertEqual('2017-07-31', spread_lines[5].date)
self.assertEqual('2017-08-31', spread_lines[6].date)
self.assertEqual('2017-09-30', spread_lines[7].date)
self.assertEqual('2017-10-31', spread_lines[8].date)
self.assertEqual('2017-11-30', spread_lines[9].date)
self.assertEqual('2017-12-31', spread_lines[10].date)
self.assertEqual('2018-01-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.journal_id.update_posted = True
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': '2017-01-07'
})
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('2017-01-31', spread_lines[0].date)
self.assertEqual('2017-02-28', spread_lines[1].date)
self.assertEqual('2017-03-31', spread_lines[2].date)
self.assertEqual('2017-04-30', spread_lines[3].date)
self.assertEqual('2017-05-31', spread_lines[4].date)
self.assertEqual('2017-06-30', spread_lines[5].date)
self.assertEqual('2017-07-31', spread_lines[6].date)
self.assertEqual('2017-08-31', spread_lines[7].date)
self.assertEqual('2017-09-30', spread_lines[8].date)
self.assertEqual('2017-10-31', spread_lines[9].date)
self.assertEqual('2017-11-30', spread_lines[10].date)
self.assertEqual('2017-12-31', spread_lines[11].date)
self.assertEqual('2018-01-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': '2017-01-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('2017-01-31', spread_lines[0].date)
self.assertEqual('2017-02-28', spread_lines[1].date)
self.assertEqual('2017-03-31', spread_lines[2].date)
self.assertEqual('2017-04-30', spread_lines[3].date)
self.assertEqual('2017-05-31', spread_lines[4].date)
self.assertEqual('2017-06-30', spread_lines[5].date)
self.assertEqual('2017-07-31', spread_lines[6].date)
self.assertEqual('2017-08-31', spread_lines[7].date)
self.assertEqual('2017-09-30', spread_lines[8].date)
self.assertEqual('2017-10-31', spread_lines[9].date)
self.assertEqual('2017-11-30', spread_lines[10].date)
self.assertEqual('2017-12-31', spread_lines[11].date)
self.assertEqual('2018-01-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('2017-01-31', spread_lines[0].date)
self.assertEqual('2017-02-28', spread_lines[1].date)
self.assertEqual('2017-03-31', spread_lines[2].date)
self.assertEqual('2017-04-30', spread_lines[3].date)
self.assertEqual('2017-05-31', spread_lines[4].date)
self.assertEqual('2017-06-30', spread_lines[5].date)
self.assertEqual('2017-07-31', spread_lines[6].date)
self.assertEqual('2017-08-31', spread_lines[7].date)
self.assertEqual('2017-09-30', spread_lines[8].date)
self.assertEqual('2017-10-31', spread_lines[9].date)
self.assertEqual('2017-11-30', spread_lines[10].date)
self.assertEqual('2017-12-31', spread_lines[11].date)
self.assertEqual('2018-01-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': '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': '2017-02-01'
})
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))
# post and then unlink all created moves
self.spread.journal_id.write({'update_posted': True})
for line in self.spread.line_ids:
line.move_id.post()
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.)
self.assertEqual(self.spread.unposted_amount, 1000.)
# 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.)
self.assertEqual(self.spread.unposted_amount, 0.)
def test_07_supplier_invoice(self):
self.spread.write({
'period_number': 3,
'period_type': 'month',
'spread_date': '2017-01-01',
'estimated_amount': 345.96,
})
self.spread.compute_spread_board()
spread_lines = self.spread.line_ids
self.assertEqual(len(spread_lines), 3)
self.assertEqual(115.32, spread_lines[0].amount)
self.assertEqual(115.32, spread_lines[1].amount)
self.assertEqual(115.32, spread_lines[2].amount)
self.assertEqual('2017-01-31', spread_lines[0].date)
self.assertEqual('2017-02-28', spread_lines[1].date)
self.assertEqual('2017-03-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': '2017-02-01'
})
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': '2017-02-01'
})
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.journal_id.update_posted = True
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.)
self.assertEqual(self.spread.unposted_amount, 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': '2017-01-07'
})
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': '2017-01-07'
})
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('2017-01-31', spread_lines[0].date)
self.assertEqual('2017-02-28', spread_lines[1].date)
self.assertEqual('2017-03-31', spread_lines[2].date)
self.assertEqual('2017-04-30', spread_lines[3].date)
self.assertEqual('2017-05-31', spread_lines[4].date)
self.assertEqual('2017-06-30', spread_lines[5].date)
self.assertEqual('2017-07-31', spread_lines[6].date)
self.assertEqual('2017-08-31', spread_lines[7].date)
self.assertEqual('2017-09-30', spread_lines[8].date)
self.assertEqual('2017-10-31', spread_lines[9].date)
self.assertEqual('2017-11-30', spread_lines[10].date)
self.assertEqual('2017-12-31', spread_lines[11].date)
self.assertEqual('2018-01-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,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_supplier_invoice_spread" model="ir.ui.view">
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_supplier_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="view_customer_invoice_spread" model="ir.ui.view">
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_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>
</odoo>

View File

@@ -0,0 +1,199 @@
<?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 class="oe_button_box">
<button name="open_reconcile_view"
class="oe_stat_button"
icon="fa-bars"
type="object"
string="Reconciled entries">
</button>
<button name="toggle_active" type="object"
attrs="{'invisible': [('all_posted','=',False)]}"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<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"/>
</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'))]}"/>
<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>
<field name="debit_account_id" required="1" domain="[('company_id', '=', company_id), ('deprecated', '=', False)]" 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'))]}"/>
<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>
<field name="credit_account_id" required="1" domain="[('company_id', '=', company_id), ('deprecated', '=', False)]" 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>
<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_type">form</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_entries"
action="action_account_spread_form"
groups="account.group_account_user,account.group_account_manager"/>
</odoo>

View File

@@ -0,0 +1,58 @@
<?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="spread_account_id" 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>
</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_type">form</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,34 @@
<?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"/>
</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,198 @@
# Copyright 2018-2019 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.invoice.line',
string='Invoice Line',
readonly=True,
required=True,
ondelete='cascade')
invoice_id = fields.Many2one(
related='invoice_line_id.invoice_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_id = fields.Many2one(
'account.spread',
string='Spread Board')
company_id = fields.Many2one(
'res.company',
string='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')
spread_account_id = fields.Many2one(
'account.account',
string='Balance sheet account / Spread account',
store=True)
spread_journal_id = fields.Many2one(
'account.journal',
string='Spread Journal',
store=True)
@api.depends('invoice_line_id')
def _compute_invoice_type(self):
for wizard in self:
invoice = wizard.invoice_line_id.invoice_id
wizard.invoice_type = invoice.type
if invoice.type in ['out_invoice', 'out_refund']:
wizard.spread_type = 'sale'
else:
wizard.spread_type = 'purchase'
@api.onchange('company_id', 'invoice_type')
def _onchange_spread_journal_account(self):
for wizard in self:
company = wizard.company_id
acc_revenue = company.default_spread_revenue_account_id
acc_expense = company.default_spread_expense_account_id
journal_revenue = company.default_spread_revenue_journal_id
journal_expense = company.default_spread_expense_journal_id
if wizard.invoice_type in ('out_invoice', 'in_refund'):
wizard.spread_account_id = acc_revenue
wizard.spread_journal_id = journal_revenue
else:
wizard.spread_account_id = acc_expense
wizard.spread_journal_id = journal_expense
@api.multi
def _inverse_spread_journal_account(self):
"""Keep this for making the fields editable"""
pass
@api.onchange('invoice_type')
def onchange_invoice_type(self):
domain = [
('invoice_id', '=', False),
('invoice_type', '=', self.invoice_type),
('company_id', '=', self.company_id.id)]
res = {'domain': {'spread_id': domain}}
return res
@api.multi
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_type': 'form',
'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.invoice_line_id.account_id
else:
debit_account = self.invoice_line_id.account_id
analytic_account = self.invoice_line_id.account_analytic_id
analytic_tags = self.invoice_line_id.analytic_tag_ids
date_invoice = self.invoice_id.date_invoice 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_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_vals = self.template_id._prepare_spread_from_template()
date_invoice = self.invoice_id.date_invoice
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.account_analytic_id
spread_vals['account_analytic_id'] = analytic_account.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_type': 'form',
'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,32 @@
<?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="spread_account_id" attrs="{'invisible': [('spread_action_type', '!=', 'new')],'required': [('spread_action_type', '=', 'new')]}" 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>