Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# Account Cut-off Accrual Sale
Odoo addon: account_cutoff_accrual_sale
## Installation
```bash
pip install odoo-bringout-oca-account-closing-account_cutoff_accrual_sale
```
## Dependencies
This addon depends on:
- account_cutoff_accrual_order_base
- sale
- sale_force_invoiced
## Manifest Information
- **Name**: Account Cut-off Accrual Sale
- **Version**: 16.0.1.1.0
- **Category**: Accounting & Finance
- **License**: AGPL-3
- **Installable**: True
## Source
Based on [OCA/account-closing](https://github.com/OCA/account-closing) branch 16.0, addon `account_cutoff_accrual_sale`.
## License
This package maintains the original AGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,119 @@
============================
Account Cut-off Accrual Sale
============================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f11af8c1fbd7db1da420dd7f9a17b3a48557c17970f5c81a4289d9c0e55bd529
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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--closing-lightgray.png?logo=github
:target: https://github.com/OCA/account-closing/tree/16.0/account_cutoff_accrual_sale
:alt: OCA/account-closing
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-closing-16-0/account-closing-16-0-account_cutoff_accrual_sale
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/account-closing&target_branch=16.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module extends the functionality of account_cutoff_accrual_order_base
to allow the computation of revenue cutoffs on sales orders.
The accrual is computed by comparing on the order, the quantity
delivered/received and the quantity invoiced. In case, some deliveries or
invoices have occurred after the cutoff date, those quantities can be affected
and are recomputed. This allows to quickly generate a cutoff snapshot by
processing few lines.
For SO, you can make the difference between:
* invoice to generate (delivered qty > invoiced qty)
* goods to send (prepayment) (delivered qty < invoiced qty)
At each end of period, a cron job generates the cutoff entries for the revenues
(based on SO).
Orders forced in status invoiced won't have cutoff entries.
Once the cutoff lines have been generated but the accounting entries are not yet
created, you are still able to create or modify invoices before the accounting
butoff date. The cutoff lines will be adapted automatically to reflect the new
situation.
Once the cutoff accounting entries are generated you cannot create or modify
invoices before the accounting cutoff date.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure this module, you need to:
#. Go to the accounting settings to select the journals and accounts used for
the cutoff.
#. Analytic accounting needs to be enable in Accounting - Settings.
#. If you want to also accrue the taxes, you need in Accounting - Taxes, for
each type of taxes an accrued tax account.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-closing/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/account-closing/issues/new?body=module:%20account_cutoff_accrual_sale%0Aversion:%2016.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
~~~~~~~
* BCIM
Contributors
~~~~~~~~~~~~
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
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-jbaudoux| image:: https://github.com/jbaudoux.png?size=40px
:target: https://github.com/jbaudoux
:alt: jbaudoux
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-jbaudoux|
This module is part of the `OCA/account-closing <https://github.com/OCA/account-closing/tree/16.0/account_cutoff_accrual_sale>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,5 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from . import models
from .hooks import post_init_hook

View file

@ -0,0 +1,22 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
{
"name": "Account Cut-off Accrual Sale",
"version": "16.0.1.1.0",
"category": "Accounting & Finance",
"license": "AGPL-3",
"summary": "Accrued Revenue on Sales Order",
"author": "BCIM, Odoo Community Association (OCA)",
"maintainers": ["jbaudoux"],
"website": "https://github.com/OCA/account-closing",
"depends": ["account_cutoff_accrual_order_base", "sale", "sale_force_invoiced"],
"data": [
"views/account_cutoff.xml",
"views/account_cutoff_line.xml",
"data/ir_cron.xml",
],
"post_init_hook": "post_init_hook",
"installable": True,
"application": True,
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo noupdate="1">
<record forcecreate="True" id="ir_cron_cutoff_revenue" model="ir.cron">
<field name="name">Make cutoff at end of period - sales order lines</field>
<field eval="True" name="active" />
<field name="model_id" ref="account_cutoff_base.model_account_cutoff" />
<field name="state">code</field>
<field
name="code"
>model._cron_cutoff("accrued_revenue", "sale.order.line")</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">months</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
<field name="nextcall" eval="(DateTime.now()).strftime('%Y-%m-01 00:00:00')" />
</record>
</odoo>

View file

@ -0,0 +1,13 @@
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
def post_init_hook(cr, registry):
cr.execute(
"""
UPDATE sale_order_line
SET is_cutoff_accrual_excluded = TRUE
WHERE order_id IN
( SELECT id FROM sale_order WHERE force_invoiced )
"""
)

View file

@ -0,0 +1,90 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_cutoff_accrual_sale
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff
msgid "Account Cut-off"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff_line
msgid "Account Cut-off Line"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__account_cutoff_line_ids
msgid "Account Cutoff Lines"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.actions.act_window,name:account_cutoff_accrual_sale.account_accrual_action
#: model:ir.ui.menu,name:account_cutoff_accrual_sale.account_accrual_menu
msgid "Accrued Revenue on Sales Orders"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid "Click to start preparing a new revenue accrual."
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__is_cutoff_accrual_excluded
msgid "Do not generate cut-off entries"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_move
msgid "Journal Entry"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.actions.server,name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue_ir_actions_server
#: model:ir.cron,cron_name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue
msgid "Make cutoff at end of period - sales order lines"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff__order_line_model
msgid "Order Line Model"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_order_id
msgid "Order Reference"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order_line
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_line_id
msgid "Sales Order Line"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields.selection,name:account_cutoff_accrual_sale.selection__account_cutoff__order_line_model__sale_order_line
msgid "Sales Orders"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid ""
"This view can be used by accountants in order to collect information about "
"accrued expenses. It then allows to generate the corresponding cut-off "
"journal entry in one click."
msgstr ""

View file

@ -0,0 +1,90 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_cutoff_accrual_sale
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff
msgid "Account Cut-off"
msgstr "Računovodstveno presjecanje"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff_line
msgid "Account Cut-off Line"
msgstr "Linija računovodstvenog presjecanja"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__account_cutoff_line_ids
msgid "Account Cutoff Lines"
msgstr "Linije računovodstvenog presjecanja"
#. module: account_cutoff_accrual_sale
#: model:ir.actions.act_window,name:account_cutoff_accrual_sale.account_accrual_action
#: model:ir.ui.menu,name:account_cutoff_accrual_sale.account_accrual_menu
msgid "Accrued Revenue on Sales Orders"
msgstr "Akruelni prihod na prodajnim narudžbama"
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid "Click to start preparing a new revenue accrual."
msgstr "Kliknite da počnete pripremu novog akruala prihoda."
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__is_cutoff_accrual_excluded
msgid "Do not generate cut-off entries"
msgstr "Ne generiši zapise presjecanja"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_move
msgid "Journal Entry"
msgstr "Žurnal"
#. module: account_cutoff_accrual_sale
#: model:ir.actions.server,name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue_ir_actions_server
#: model:ir.cron,cron_name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue
msgid "Make cutoff at end of period - sales order lines"
msgstr "Napravi presjecanje na kraju perioda - linije prodajnih narudžbi"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff__order_line_model
msgid "Order Line Model"
msgstr "Model linije narudžbe"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_order_id
msgid "Order Reference"
msgstr "Referenca narudžbe"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order
msgid "Sales Order"
msgstr "Prodajni nalog"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order_line
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_line_id
msgid "Sales Order Line"
msgstr "Stavka prodajne narudžbe"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields.selection,name:account_cutoff_accrual_sale.selection__account_cutoff__order_line_model__sale_order_line
msgid "Sales Orders"
msgstr "Prodajni nalozi"
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid ""
"This view can be used by accountants in order to collect information about "
"accrued expenses. It then allows to generate the corresponding cut-off "
"journal entry in one click."
msgstr ""

View file

@ -0,0 +1,94 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_cutoff_accrual_sale
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-26 11:51+0000\n"
"PO-Revision-Date: 2023-10-26 11:51+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff
msgid "Account Cut-off"
msgstr "Provision"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff_line
msgid "Account Cut-off Line"
msgstr "Ligne de provision"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__account_cutoff_line_ids
msgid "Account Cutoff Lines"
msgstr "Lignes de provision"
#. module: account_cutoff_accrual_sale
#: model:ir.actions.act_window,name:account_cutoff_accrual_sale.account_accrual_action
#: model:ir.ui.menu,name:account_cutoff_accrual_sale.account_accrual_menu
msgid "Accrued Revenue on Sales Orders"
msgstr "Provisions sur Ventes"
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid "Click to start preparing a new revenue accrual."
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__is_cutoff_accrual_excluded
msgid "Do not generate cut-off entries"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_move
msgid "Journal Entry"
msgstr "Pièce comptable"
#. module: account_cutoff_accrual_sale
#: model:ir.actions.server,name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue_ir_actions_server
#: model:ir.cron,cron_name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue
msgid "Make cutoff at end of period - sales order lines"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff__order_line_model
msgid "Order Line Model"
msgstr ""
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_order_id
msgid "Order Reference"
msgstr "Commande"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order
#, fuzzy
msgid "Sales Order"
msgstr "Ventes"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order_line
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_line_id
msgid "Sales Order Line"
msgstr "Ligne de commande"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields.selection,name:account_cutoff_accrual_sale.selection__account_cutoff__order_line_model__sale_order_line
msgid "Sales Orders"
msgstr "Ventes"
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid ""
"This view can be used by accountants in order to collect information about "
"accrued expenses. It then allows to generate the corresponding cut-off "
"journal entry in one click."
msgstr ""

View file

@ -0,0 +1,96 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * account_cutoff_accrual_sale
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-10-21 12:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.6.2\n"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff
msgid "Account Cut-off"
msgstr "Scritture di fine periodo"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_cutoff_line
msgid "Account Cut-off Line"
msgstr "Riga scritture fine periodo"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__account_cutoff_line_ids
msgid "Account Cutoff Lines"
msgstr "Righe scritture fine periodo"
#. module: account_cutoff_accrual_sale
#: model:ir.actions.act_window,name:account_cutoff_accrual_sale.account_accrual_action
#: model:ir.ui.menu,name:account_cutoff_accrual_sale.account_accrual_menu
msgid "Accrued Revenue on Sales Orders"
msgstr "Ratei ricavi negli ordini di vendita"
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid "Click to start preparing a new revenue accrual."
msgstr "Fare click per iniziare a preparare un nuovo rateo attivo."
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_sale_order_line__is_cutoff_accrual_excluded
msgid "Do not generate cut-off entries"
msgstr "Non generare registrazioni di fine periodo"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_account_move
msgid "Journal Entry"
msgstr "Registrazione contabile"
#. module: account_cutoff_accrual_sale
#: model:ir.actions.server,name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue_ir_actions_server
#: model:ir.cron,cron_name:account_cutoff_accrual_sale.ir_cron_cutoff_revenue
msgid "Make cutoff at end of period - sales order lines"
msgstr "Crea registrazioni alla fine del periodo - righe ordine di vendita"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff__order_line_model
msgid "Order Line Model"
msgstr "Modello riga ordine"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_order_id
msgid "Order Reference"
msgstr "Riferimento ordine"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: account_cutoff_accrual_sale
#: model:ir.model,name:account_cutoff_accrual_sale.model_sale_order_line
#: model:ir.model.fields,field_description:account_cutoff_accrual_sale.field_account_cutoff_line__sale_line_id
msgid "Sales Order Line"
msgstr "Riga ordine di vendita"
#. module: account_cutoff_accrual_sale
#: model:ir.model.fields.selection,name:account_cutoff_accrual_sale.selection__account_cutoff__order_line_model__sale_order_line
msgid "Sales Orders"
msgstr "Ordini di vendita"
#. module: account_cutoff_accrual_sale
#: model_terms:ir.actions.act_window,help:account_cutoff_accrual_sale.account_accrual_action
msgid ""
"This view can be used by accountants in order to collect information about "
"accrued expenses. It then allows to generate the corresponding cut-off "
"journal entry in one click."
msgstr ""
"Questa vista può essere usata dai contabili per raccogliere informazioni sui "
"ratei passivi. Permette di generare la relativa registrazione di "
"ripartizione con un clic."

View file

@ -0,0 +1,8 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from . import account_cutoff
from . import account_cutoff_line
from . import sale_order
from . import sale_order_line
from . import account_move

View file

@ -0,0 +1,12 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import fields, models
class AccountCutoff(models.Model):
_inherit = "account.cutoff"
order_line_model = fields.Selection(
selection_add=[("sale.order.line", "Sales Orders")]
)

View file

@ -0,0 +1,26 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import api, fields, models
class AccountCutoffLine(models.Model):
_inherit = "account.cutoff.line"
sale_line_id = fields.Many2one(
comodel_name="sale.order.line", string="Sales Order Line", readonly=True
)
sale_order_id = fields.Many2one(related="sale_line_id.order_id")
def _get_order_line(self):
if self.sale_line_id:
return self.sale_line_id
return super()._get_order_line()
@api.depends("sale_line_id")
def _compute_invoice_lines(self):
for rec in self:
if rec.sale_line_id:
rec.invoice_line_ids = rec.sale_line_id.invoice_lines
super()._compute_invoice_lines()
return

View file

@ -0,0 +1,15 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class AccountMove(models.Model):
_inherit = "account.move"
def _get_cutoff_accrual_order_lines(self):
"""Return a list of order lines to process"""
res = super()._get_cutoff_accrual_order_lines()
if self.move_type in ("out_invoice", "out_refund"):
res.append(self.invoice_line_ids.sale_line_ids)
return res

View file

@ -0,0 +1,18 @@
# Copyright 2023 Jacques-Etienne Baudoux (BCIM sprl) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models
class SaleOrder(models.Model):
_inherit = "sale.order"
def write(self, vals):
res = super().write(vals)
if "force_invoiced" in vals or vals.get("invoice_status") == "to invoice":
# As the order could be non invoiceable while a line is invoiceable
# (see delivery module), we need to check each line when the order
# invoice status becomes "to invoice"
for line in self.order_line:
line.sudo()._update_cutoff_accrual()
return res

View file

@ -0,0 +1,175 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM sprl) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import api, fields, models
from odoo.osv import expression
_logger = logging.getLogger(__name__)
class SaleOrderLine(models.Model):
_name = "sale.order.line"
_inherit = ["sale.order.line", "order.line.cutoff.accrual.mixin"]
account_cutoff_line_ids = fields.One2many(
"account.cutoff.line",
"sale_line_id",
string="Account Cutoff Lines",
readonly=True,
)
is_cutoff_accrual_excluded = fields.Boolean(
compute="_compute_is_cutoff_accrual_excluded",
store=True,
)
@api.depends("order_id.force_invoiced")
def _compute_is_cutoff_accrual_excluded(self):
for rec in self:
# If the order is not to invoice
rec.is_cutoff_accrual_excluded = rec.order_id.force_invoiced
def _get_cutoff_accrual_partner(self):
return self.order_id.partner_invoice_id
def _get_cutoff_accrual_product_qty(self):
return self.product_uom_qty
def _get_cutoff_accrual_price_unit(self):
if self.is_downpayment:
return self.price_unit
return super()._get_cutoff_accrual_price_unit()
@api.model
def _get_cutoff_accrual_lines_domain(self, cutoff):
domain = super()._get_cutoff_accrual_lines_domain(cutoff)
domain = expression.AND(
(
domain,
(
("state", "in", ("sale", "done")),
("display_type", "=", False),
),
)
)
return domain
@api.model
def _get_cutoff_accrual_lines_query(self, cutoff):
query = super()._get_cutoff_accrual_lines_query(cutoff)
self.flush_model(
[
"qty_delivered_method",
"qty_delivered",
"qty_invoiced",
"qty_to_invoice",
"is_downpayment",
]
)
# The delivery line could be invoiceable but not the order (see
# delivery module). So check also the SO invoice status.
so_alias = query.join(
self._table, "order_id", self.order_id._table, "id", "order_id"
)
self.order_id.flush_model(["invoice_status"])
# For stock products, we always consider the delivered quantity as it
# impacts the stock valuation.
# Otherwise, we consider the invoice policy by checking the
# qty_to_invoice.
query.add_where(
f"""
CASE
WHEN "{self._table}".qty_delivered_method = 'stock_move'
THEN "{self._table}".qty_delivered != "{self._table}".qty_invoiced
ELSE "{self._table}".qty_to_invoice != 0
AND (
"{so_alias}".invoice_status = 'to invoice'
OR "{self._table}".is_downpayment
)
END
"""
)
return query
def _prepare_cutoff_accrual_line(self, cutoff):
res = super()._prepare_cutoff_accrual_line(cutoff)
if not res:
return
res["sale_line_id"] = self.id
return res
def _get_cutoff_accrual_lines_invoiced_after(self, cutoff):
cutoff_nextday = cutoff._nextday_start_dt()
# Take all invoices impacting the cutoff
# FIXME: what about ("move_id.payment_state", "=", "invoicing_legacy")
domain = [
("sale_line_ids.is_cutoff_accrual_excluded", "!=", True),
("move_id.move_type", "in", ("out_invoice", "out_refund")),
("sale_line_ids", "!=", False),
"|",
("move_id.state", "=", "draft"),
"&",
("move_id.state", "=", "posted"),
("move_id.date", ">=", cutoff_nextday),
]
invoice_line_after = self.env["account.move.line"].search(domain, order="id")
_logger.debug(
"Sales Invoice Lines done after cutoff: %s" % len(invoice_line_after)
)
if not invoice_line_after:
return self.env["sale.order.line"]
# In SQL to reduce memory usage as we could process large dataset
self.env.cr.execute(
"""
SELECT order_id
FROM sale_order_line
WHERE id in (
SELECT order_line_id
FROM sale_order_line_invoice_rel
WHERE invoice_line_id in %s
)
""",
(tuple(invoice_line_after.ids),),
)
sale_ids = [x[0] for x in self.env.cr.fetchall()]
lines = self.env["sale.order.line"].search(
[("order_id", "in", sale_ids)], order="id"
)
return lines
def _get_cutoff_accrual_delivered_service_quantity(self, cutoff):
self.ensure_one()
cutoff_nextday = cutoff._nextday_start_dt()
if self.create_date >= cutoff_nextday:
# A line added after the cutoff cannot be delivered in the past
return 0
# In case of service, we consider what should be invoiced and this is
# given by the invoice policy.
if self.product_id.invoice_policy == "order":
return self.product_uom_qty
return self.qty_delivered
def _get_cutoff_accrual_delivered_stock_quantity(self, cutoff):
self.ensure_one()
cutoff_nextday = cutoff._nextday_start_dt()
if self.create_date >= cutoff_nextday:
# A line added after the cutoff cannot be delivered in the past
return 0
# In case of stock, we always consider what is delivered as this
# impacted the stock valuation.
return self.qty_delivered
def _get_cutoff_accrual_delivered_min_date(self):
"""Return first delivery date"""
self.ensure_one()
if self.product_id.invoice_policy == "order":
date_local = self.order_id.date_order
company_tz = self.env.company.partner_id.tz or "UTC"
date_utc = fields.Datetime.context_timestamp(
self.with_context(tz=company_tz),
date_local,
)
return date_utc.date()
return

View file

@ -0,0 +1,7 @@
To configure this module, you need to:
#. Go to the accounting settings to select the journals and accounts used for
the cutoff.
#. Analytic accounting needs to be enable in Accounting - Settings.
#. If you want to also accrue the taxes, you need in Accounting - Taxes, for
each type of taxes an accrued tax account.

View file

@ -0,0 +1 @@
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>

View file

@ -0,0 +1,25 @@
This module extends the functionality of account_cutoff_accrual_order_base
to allow the computation of revenue cutoffs on sales orders.
The accrual is computed by comparing on the order, the quantity
delivered/received and the quantity invoiced. In case, some deliveries or
invoices have occurred after the cutoff date, those quantities can be affected
and are recomputed. This allows to quickly generate a cutoff snapshot by
processing few lines.
For SO, you can make the difference between:
* invoice to generate (delivered qty > invoiced qty)
* goods to send (prepayment) (delivered qty < invoiced qty)
At each end of period, a cron job generates the cutoff entries for the revenues
(based on SO).
Orders forced in status invoiced won't have cutoff entries.
Once the cutoff lines have been generated but the accounting entries are not yet
created, you are still able to create or modify invoices before the accounting
butoff date. The cutoff lines will be adapted automatically to reflect the new
situation.
Once the cutoff accounting entries are generated you cannot create or modify
invoices before the accounting cutoff date.

View file

@ -0,0 +1,455 @@
<!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: https://docutils.sourceforge.io/" />
<title>Account Cut-off Accrual Sale</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/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: gray; } /* 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, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="account-cut-off-accrual-sale">
<h1 class="title">Account Cut-off Accrual Sale</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:f11af8c1fbd7db1da420dd7f9a17b3a48557c17970f5c81a4289d9c0e55bd529
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" 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 image-reference" 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 image-reference" href="https://github.com/OCA/account-closing/tree/16.0/account_cutoff_accrual_sale"><img alt="OCA/account-closing" src="https://img.shields.io/badge/github-OCA%2Faccount--closing-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-closing-16-0/account-closing-16-0-account_cutoff_accrual_sale"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-closing&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module extends the functionality of account_cutoff_accrual_order_base
to allow the computation of revenue cutoffs on sales orders.</p>
<p>The accrual is computed by comparing on the order, the quantity
delivered/received and the quantity invoiced. In case, some deliveries or
invoices have occurred after the cutoff date, those quantities can be affected
and are recomputed. This allows to quickly generate a cutoff snapshot by
processing few lines.</p>
<p>For SO, you can make the difference between:
* invoice to generate (delivered qty &gt; invoiced qty)
* goods to send (prepayment) (delivered qty &lt; invoiced qty)</p>
<p>At each end of period, a cron job generates the cutoff entries for the revenues
(based on SO).</p>
<p>Orders forced in status invoiced wont have cutoff entries.</p>
<p>Once the cutoff lines have been generated but the accounting entries are not yet
created, you are still able to create or modify invoices before the accounting
butoff date. The cutoff lines will be adapted automatically to reflect the new
situation.</p>
<p>Once the cutoff accounting entries are generated you cannot create or modify
invoices before the accounting cutoff date.</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="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>To configure this module, you need to:</p>
<ol class="arabic simple">
<li>Go to the accounting settings to select the journals and accounts used for
the cutoff.</li>
<li>Analytic accounting needs to be enable in Accounting - Settings.</li>
<li>If you want to also accrue the taxes, you need in Accounting - Taxes, for
each type of taxes an accrued tax account.</li>
</ol>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account-closing/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 to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/account-closing/issues/new?body=module:%20account_cutoff_accrual_sale%0Aversion:%2016.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="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>BCIM</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>Jacques-Etienne Baudoux (BCIM) &lt;<a class="reference external" href="mailto:je&#64;bcim.be">je&#64;bcim.be</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">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 image-reference" href="https://github.com/jbaudoux"><img alt="jbaudoux" src="https://github.com/jbaudoux.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-closing/tree/16.0/account_cutoff_accrual_sale">OCA/account-closing</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>

View file

@ -0,0 +1 @@
from . import test_cutoff_revenue

View file

@ -0,0 +1,69 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import Command, fields
from odoo.addons.account_cutoff_accrual_order_base.tests.common import (
TestAccountCutoffAccrualOrderCommon,
)
class TestAccountCutoffAccrualSaleCommon(TestAccountCutoffAccrualOrderCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.tax_sale = cls.env.company.account_sale_tax_id
cls.cutoff_account = cls.env["account.account"].create(
{
"name": "account accrued revenue",
"code": "accountAccruedExpense",
"account_type": "asset_current",
"company_id": cls.env.company.id,
}
)
cls.tax_sale.account_accrued_revenue_id = cls.cutoff_account
# Removing all existing SO
cls.env.cr.execute("DELETE FROM sale_order;")
# Make service product invoicable on order
cls.env.ref("product.expense_product").invoice_policy = "order"
# Create SO and confirm
cls.price = 100
cls.qty = 5
cls.so = cls.env["sale.order"].create(
{
"partner_id": cls.partner.id,
"partner_invoice_id": cls.partner.id,
"partner_shipping_id": cls.partner.id,
"order_line": [
Command.create(
{
"name": p.name,
"product_id": p.id,
"product_uom_qty": cls.qty,
"product_uom": p.uom_id.id,
"price_unit": cls.price,
"analytic_distribution": {
str(cls.analytic_account.id): 100.0
},
"tax_id": [Command.set(cls.tax_sale.ids)],
},
)
for p in cls.products
],
"pricelist_id": cls.env.ref("product.list0").id,
}
)
# Create cutoff
type_cutoff = "accrued_revenue"
cls.revenue_cutoff = (
cls.env["account.cutoff"]
.with_context(default_cutoff_type=type_cutoff)
.create(
{
"cutoff_type": type_cutoff,
"order_line_model": "sale.order.line",
"company_id": 1,
"cutoff_date": fields.Date.today(),
}
)
)

View file

@ -0,0 +1,406 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import timedelta
from odoo import Command
from .common import TestAccountCutoffAccrualSaleCommon
class TestAccountCutoffAccrualSale(TestAccountCutoffAccrualSaleCommon):
def test_accrued_revenue_empty(self):
"""Test cutoff when there is no confirmed SO."""
cutoff = self.revenue_cutoff
cutoff.get_lines()
self.assertEqual(
len(cutoff.line_ids), 0, "There should be no SO line to process"
)
def test_revenue_analytic_distribution(self):
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
for line in cutoff.line_ids:
self.assertDictEqual(
line.analytic_distribution,
{str(self.analytic_account.id): self.price},
"Analytic distribution is not correctly set",
)
def test_revenue_tax_line(self):
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff lines should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
len(line.tax_line_ids), 1, "tax lines is not correctly set"
)
self.assertEqual(line.tax_line_ids.cutoff_account_id, self.cutoff_account)
self.assertEqual(line.tax_line_ids.tax_id, self.tax_sale)
self.assertEqual(line.tax_line_ids.base, amount)
self.assertEqual(line.tax_line_ids.amount, amount * 15 / 100)
self.assertEqual(line.tax_line_ids.cutoff_amount, amount * 15 / 100)
def test_accrued_revenue_downpayment(self):
"""Test cutoff based on SO with downpayment."""
cutoff = self.revenue_cutoff
self.so.action_confirm()
downpayment_amount = 10
self.env["sale.advance.payment.inv"].create(
{
"advance_payment_method": "fixed",
"sale_order_ids": [Command.set(self.so.ids)],
"fixed_amount": downpayment_amount,
}
).create_invoices()
self.so.invoice_ids.action_post()
cutoff.get_lines()
downpayment_so_line = self.so.order_line.filtered(
lambda line: line.is_downpayment and not line.display_type
)
self.assertEqual(
len(downpayment_so_line), 1, "1 down payment SO line should be found"
)
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
downpayment_cutoff_line = cutoff.line_ids.filtered(
lambda line: line.product_id == downpayment_so_line.product_id
)
self.assertEqual(
len(downpayment_cutoff_line),
1,
"1 down payment Cutoff line should be found",
)
self.assertEqual(
downpayment_cutoff_line.cutoff_amount,
-downpayment_amount,
"Down payment cutoff amount incorrect",
)
# Make invoice
invoice = self.so._create_invoices(final=True)
# - invoice is in draft, no change to cutoff
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
downpayment_cutoff_line = cutoff.line_ids.filtered(
lambda line: line.product_id == downpayment_so_line.product_id
)
self.assertEqual(
len(downpayment_cutoff_line),
1,
"1 down payment Cutoff line should be found",
)
self.assertEqual(
downpayment_cutoff_line.cutoff_amount,
-downpayment_amount,
"Down payment cutoff amount incorrect",
)
# Validate invoice
invoice.action_post()
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
for line in cutoff.line_ids:
self.assertEqual(line.cutoff_amount, 0, "SO line cutoff amount incorrect")
# Make a refund - the refund reset the SO lines qty_invoiced
self._refund_invoice(invoice)
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
downpayment_cutoff_line = cutoff.line_ids.filtered(
lambda line: line.product_id == downpayment_so_line.product_id
)
self.assertEqual(
len(downpayment_cutoff_line),
1,
"1 down payment Cutoff line should be found",
)
self.assertEqual(
downpayment_cutoff_line.cutoff_amount,
-downpayment_amount,
"Down payment cutoff amount incorrect",
)
# Make tests for product with invoice policy on order
def test_accrued_revenue_on_so_not_invoiced(self):
"""Test cutoff based on SO where product_uom_qty > qty_invoiced."""
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Make invoice
self.so._create_invoices(final=True)
# - invoice is in draft, no change to cutoff
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Validate invoice
self.so.invoice_ids.action_post()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
for line in cutoff.line_ids:
self.assertEqual(line.cutoff_amount, 0, "SO line cutoff amount incorrect")
# Make a refund - the refund reset the SO lines qty_invoiced
self._refund_invoice(self.so.invoice_ids)
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff lines should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_delivery_not_invoiced(self):
"""Test cutoff based on SO where product_uom_qty > qty_invoiced."""
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
# 1 cutoff line for the service
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
delivered_qty = (
cutoff.line_ids.sale_line_id._get_cutoff_accrual_delivered_quantity(cutoff)
)
self.assertEqual(delivered_qty, 5, "the delivery line should be delivered")
# simulate a delivery service line added after cutoff date
cutoff.cutoff_date -= timedelta(days=1)
delivered_qty = (
cutoff.line_ids.sale_line_id._get_cutoff_accrual_delivered_quantity(cutoff)
)
self.assertEqual(delivered_qty, 0, "the delivery line should not be delivered")
# regenerate cutoff
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 0, "0 cutoff line should be found")
def test_accrued_revenue_on_so_all_invoiced(self):
"""Test cutoff based on SO where product_uom_qty = qty_invoiced."""
cutoff = self.revenue_cutoff
self.so.action_confirm()
# Make invoice
self.so._create_invoices(final=True)
# Validate invoice
self.so.invoice_ids.action_post()
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 0, "No cutoff lines should be found")
# Make a refund - the refund reset qty_invoiced
self._refund_invoice(self.so.invoice_ids)
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_draft_invoice(self):
"""Test cutoff based on SO where product_uom_qty = qty_invoiced but the.
invoice is still in draft
"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
# Make invoice
self.so._create_invoices(final=True)
# - invoice is in draft, no change to cutoff
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Validate invoice
self.so.invoice_ids.action_post()
self.assertEqual(len(cutoff.line_ids), 1, "no cutoff lines should be found")
for line in cutoff.line_ids:
self.assertEqual(line.cutoff_amount, 0, "SO line cutoff amount incorrect")
# Make a refund - the refund reset SO lines qty_invoiced
self._refund_invoice(self.so.invoice_ids)
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff lines should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_not_invoiced_after_cutoff(self):
"""Test cutoff based on SO where product_uom_qty > qty_invoiced.
And make invoice after cutoff date
"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
# Make invoice
self.so._create_invoices(final=True)
# - invoice is in draft, no change to cutoff
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Validate invoice after cutoff
self.so.invoice_ids.invoice_date = cutoff.cutoff_date + timedelta(days=1)
self.so.invoice_ids.action_post()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff lines should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Make a refund after cutoff
refund = self._refund_invoice(self.so.invoice_ids, post=False)
refund.date = cutoff.cutoff_date + timedelta(days=1)
refund.action_post()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_all_invoiced_after_cutoff(self):
"""Test cutoff based on SO where product_uom_qty = qty_invoiced.
And make invoice after cutoff date
"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
# Make invoice
self.so._create_invoices(final=True)
# Validate invoice after cutoff
self.so.invoice_ids.invoice_date = cutoff.cutoff_date + timedelta(days=1)
self.so.invoice_ids.action_post()
# as there is no delivery and invoice is after cutoff, one line is
# generated for the service
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Make a refund - the refund reset SO lines qty_invoiced
refund = self._refund_invoice(self.so.invoice_ids, post=False)
refund.date = cutoff.cutoff_date + timedelta(days=1)
refund.action_post()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_force_invoiced_after(self):
"""Test cutoff when SO is force invoiced after cutoff"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Force invoiced after cutoff lines generated, lines should be deleted
self.so.force_invoiced = True
self.assertEqual(len(cutoff.line_ids), 0, "cutoff line should be deleted")
# Remove Force invoiced, lines should be recreated
self.so.force_invoiced = False
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff lines should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_force_invoiced_before(self):
"""Test cutoff when SO is force invoiced before cutoff"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
# Force invoiced before cutoff lines generated, lines should be deleted
self.so.force_invoiced = True
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 0, "no cutoff line should be generated")
# Remove Force invoiced, lines should be created
self.so.force_invoiced = False
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_force_invoiced_after_but_posted(self):
"""Test cutoff when SO is force invoiced after closed cutoff"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
cutoff.state = "done"
# Force invoiced after cutoff lines generated, cutoff is posted
self.so.force_invoiced = True
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
# Remove Force invoiced, nothing changes
self.so.force_invoiced = False
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
def test_accrued_revenue_on_so_force_invoiced_before_but_posted(self):
"""Test cutoff when SO is force invoiced before closed cutoff"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
# Force invoiced before cutoff lines generated, lines should be deleted
self.so.force_invoiced = True
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 0, "no cutoff line should be generated")
cutoff.state = "done"
# Remove Force invoiced, lines should be created
self.so.force_invoiced = False
self.assertEqual(len(cutoff.line_ids), 0, "no cutoff line should be generated")
def test_accrued_revenue_on_so_force_invoiced_line_added(self):
"""Test cutoff when SO is force invoiced and line is added"""
cutoff = self.revenue_cutoff
self.so.action_confirm()
self.so.force_invoiced = True
p = self.env.ref("product.expense_product")
self.so.order_line = [
Command.create(
{
"name": p.name,
"product_id": p.id,
"product_uom_qty": 1,
"product_uom": p.uom_id.id,
"price_unit": self.price,
"tax_id": [Command.set(self.tax_sale.ids)],
},
)
]
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 0, "0 cutoff line should be found")
for sol in self.so.order_line:
self.assertEqual(sol.is_cutoff_accrual_excluded, True)

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_accrual_action" model="ir.actions.act_window">
<field name="name">Accrued Revenue on Sales Orders</field>
<field name="res_model">account.cutoff</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('order_line_model', '=', 'sale.order.line')]</field>
<field
name="context"
>{'default_order_line_model': 'sale.order.line', 'default_cutoff_type': 'accrued_revenue'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start preparing a new revenue accrual.
</p><p>
This view can be used by accountants in order to collect information about accrued expenses. It then allows to generate the corresponding cut-off journal entry in one click.
</p>
</field>
</record>
<menuitem
id="account_accrual_menu"
parent="account_cutoff_base.cutoff_menu"
action="account_accrual_action"
sequence="5"
/>
</odoo>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018 Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_cutoff_line_form" model="ir.ui.view">
<field name="model">account.cutoff.line</field>
<field name="inherit_id" ref="account_cutoff_base.account_cutoff_line_form" />
<field name="arch" type="xml">
<field name="parent_id" position="after">
<field name="sale_line_id" invisible="1" />
<field
name="sale_order_id"
attrs="{'invisible': [('sale_line_id', '=', False)]}"
/>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Account_cutoff_accrual_sale Module - account_cutoff_accrual_sale
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for account_cutoff_accrual_sale. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,7 @@
# Dependencies
This addon depends on:
- [account_cutoff_accrual_order_base](../../odoo-bringout-oca-account-closing-account_cutoff_accrual_order_base)
- [sale](../../odoo-bringout-oca-ocb-sale)
- [sale_force_invoiced](../../odoo-bringout-oca-sale-workflow-sale_force_invoiced)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon account_cutoff_accrual_sale or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-account-closing-account_cutoff_accrual_sale"
# or
uv pip install odoo-bringout-oca-account-closing-account_cutoff_accrual_sale"
```

View file

@ -0,0 +1,16 @@
# Models
Detected core models and extensions in account_cutoff_accrual_sale.
```mermaid
classDiagram
class sale_order_line
class account_cutoff
class account_cutoff_line
class account_move
class sale_order
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: account_cutoff_accrual_sale. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon account_cutoff_accrual_sale
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon account_cutoff_accrual_sale
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,44 @@
[project]
name = "odoo-bringout-oca-account-closing-account_cutoff_accrual_sale"
version = "16.0.0"
description = "Account Cut-off Accrual Sale - Accrued Revenue on Sales Order"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-account-closing-account_cutoff_accrual_order_base>=16.0.0",
"odoo-bringout-oca-ocb-sale>=16.0.0",
"odoo-bringout-oca-account-closing-sale_force_invoiced>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["account_cutoff_accrual_sale"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]