Skip to content

Commit

Permalink
[ADD] account_cutoff_accrual_sale
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaudoux committed Nov 8, 2023
1 parent 6c02d7e commit b9a7d68
Show file tree
Hide file tree
Showing 20 changed files with 404 additions and 0 deletions.
Empty file.
5 changes: 5 additions & 0 deletions account_cutoff_accrual_sale/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from . import models
from .hooks import post_init_hook
23 changes: 23 additions & 0 deletions account_cutoff_accrual_sale/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

{
"name": "Account Cut-off Accrual Sale",
"version": "16.0.1.0.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",
"views/sale_order.xml",
"data/ir_cron.xml",
],
"post_init_hook": "post_init_hook",
"installable": True,
"application": True,
}
20 changes: 20 additions & 0 deletions account_cutoff_accrual_sale/data/ir_cron.xml
Original file line number Diff line number Diff line change
@@ -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>
13 changes: 13 additions & 0 deletions account_cutoff_accrual_sale/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2023 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# 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 )
"""
)
82 changes: 82 additions & 0 deletions account_cutoff_accrual_sale/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# 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"
"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,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_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 ""
8 changes: 8 additions & 0 deletions account_cutoff_accrual_sale/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# 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
12 changes: 12 additions & 0 deletions account_cutoff_accrual_sale/models/account_cutoff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# 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")]
)
26 changes: 26 additions & 0 deletions account_cutoff_accrual_sale/models/account_cutoff_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# 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
15 changes: 15 additions & 0 deletions account_cutoff_accrual_sale/models/account_move.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# 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
14 changes: 14 additions & 0 deletions account_cutoff_accrual_sale/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2023 Jacques-Etienne Baudoux (BCIM sprl) <[email protected]>
# 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:
self.order_line.is_cutoff_accrual_excluded = vals["force_invoiced"]
return res
73 changes: 73 additions & 0 deletions account_cutoff_accrual_sale/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM sprl) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import logging

from odoo import api, fields, models

_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,
)

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

@api.model
def _get_cutoff_accrual_lines_query(self):
query = super()._get_cutoff_accrual_lines_query()
self.flush_model(["display_type", "qty_delivered", "qty_invoiced"])
query.add_where(
f'"{self._table}".display_type IS NULL AND '
f'"{self._table}".qty_delivered != "{self._table}".qty_invoiced'
)
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)
)
sale_ids = set(invoice_line_after.sale_line_ids.order_id.ids)
sales = self.env["sale.order"].browse(sale_ids)
return sales.order_line

def _get_cutoff_accrual_delivered_quantity(self, cutoff):
self.ensure_one()
return self.qty_delivered

@api.model
def _cron_cutoff_accrual(self):
self._cron_cutoff("accrued_revenue", self._model)
7 changes: 7 additions & 0 deletions account_cutoff_accrual_sale/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions account_cutoff_accrual_sale/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
* Alexis de Lattre (Akretion) <[email protected]>
* Jacques-Etienne Baudoux (BCIM) <[email protected]>
* Thierry Ducrest <[email protected]>
25 changes: 25 additions & 0 deletions account_cutoff_accrual_sale/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -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).

You can configure to disable the generation of cutoff entries for closed orders.

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.
31 changes: 31 additions & 0 deletions account_cutoff_accrual_sale/views/account_cutoff.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
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>
22 changes: 22 additions & 0 deletions account_cutoff_accrual_sale/views/account_cutoff_line.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
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>
Loading

0 comments on commit b9a7d68

Please sign in to comment.