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 Oct 25, 2023
1 parent 603aff2 commit d0e4982
Show file tree
Hide file tree
Showing 16 changed files with 279 additions and 0 deletions.
Empty file.
4 changes: 4 additions & 0 deletions account_cutoff_accrual_sale/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2018 Jacques-Etienne Baudoux (BCIM) <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)

from . import models
20 changes: 20 additions & 0 deletions account_cutoff_accrual_sale/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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)",
"website": "https://github.com/OCA/account-closing",
"depends": ["account_cutoff_accrual_order_base", "sale"],
"data": [
"views/account_cutoff.xml",
"views/account_cutoff_line.xml",
"data/ir_cron.xml",
],
"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>
7 changes: 7 additions & 0 deletions account_cutoff_accrual_sale/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 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_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
80 changes: 80 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,80 @@
# 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

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 = [
("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),
]
if self.env.company.cutoff_exclude_locked_orders:
domain += [("sale_line_ids.order_id.state", "!=", "done")]
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_lines_delivered_after(self, cutoff):
# FIXME sale_stock
cutoff_nextday = cutoff._nextday_start_dt()
# Take all moves done after the cutoff date
moves_after = self.env["stock.move"].search(
[
("state", "=", "done"),
("date", ">=", cutoff_nextday),
("sale_line_id", "!=", False),
],
order="id",
)
sale_ids = set(moves_after.sale_line_id.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 Expense 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>
6 changes: 6 additions & 0 deletions setup/account_cutoff_accrual_sale/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

0 comments on commit d0e4982

Please sign in to comment.