Skip to content

Commit 8eb346a

Browse files
committed
[16.0][ADD] sale_spread_cost_revenue
1 parent 627b3be commit 8eb346a

File tree

18 files changed

+1125
-0
lines changed

18 files changed

+1125
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
=========================
2+
Sales Cost-Revenue Spread
3+
=========================
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:1525dce2359d79020203ff8b1e34a994251c5dfcc87f00ce18fee59aa4dbdcd6
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Beta
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
18+
:alt: License: AGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsale--workflow-lightgray.png?logo=github
20+
:target: https://github.com/OCA/sale-workflow/tree/16.0/sale_spread_cost_revenue
21+
:alt: OCA/sale-workflow
22+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
23+
:target: https://translation.odoo-community.org/projects/sale-workflow-16-0/sale-workflow-16-0-sale_spread_cost_revenue
24+
:alt: Translate me on Weblate
25+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/sale-workflow&target_branch=16.0
27+
:alt: Try me on Runboat
28+
29+
|badge1| |badge2| |badge3| |badge4| |badge5|
30+
31+
This module works exactly the same as account_spread_cost_revenue.
32+
Except that it allow create spread revenue from sales order line
33+
and if the spread is created from sales order, it will be linked
34+
automatically with the invoice created by this sales order.
35+
36+
**Table of contents**
37+
38+
.. contents::
39+
:local:
40+
41+
Bug Tracker
42+
===========
43+
44+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/sale-workflow/issues>`_.
45+
In case of trouble, please check there if your issue has already been reported.
46+
If you spotted it first, help us to smash it by providing a detailed and welcomed
47+
`feedback <https://github.com/OCA/sale-workflow/issues/new?body=module:%20sale_spread_cost_revenue%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
48+
49+
Do not contact contributors directly about support or help with technical issues.
50+
51+
Credits
52+
=======
53+
54+
Authors
55+
~~~~~~~
56+
57+
* Ecosoft
58+
59+
Contributors
60+
~~~~~~~~~~~~
61+
62+
* Kitti U. <kittiu@ecosoft.co.th>
63+
64+
Maintainers
65+
~~~~~~~~~~~
66+
67+
This module is maintained by the OCA.
68+
69+
.. image:: https://odoo-community.org/logo.png
70+
:alt: Odoo Community Association
71+
:target: https://odoo-community.org
72+
73+
OCA, or the Odoo Community Association, is a nonprofit organization whose
74+
mission is to support the collaborative development of Odoo features and
75+
promote its widespread use.
76+
77+
This module is part of the `OCA/sale-workflow <https://github.com/OCA/sale-workflow/tree/16.0/sale_spread_cost_revenue>`_ project on GitHub.
78+
79+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
2+
3+
from . import models
4+
from . import wizards
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2024 Ecosoft (<https://ecosoft.co.th>)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
{
5+
"name": "Sales Cost-Revenue Spread",
6+
"summary": "Spread costs and revenues over a custom period on sales order",
7+
"version": "16.0.1.0.0",
8+
"development_status": "Beta",
9+
"author": "Ecosoft,Odoo Community Association (OCA)",
10+
"license": "AGPL-3",
11+
"website": "https://github.com/OCA/sale-workflow",
12+
"category": "Sales Management",
13+
"depends": ["sale", "account_spread_cost_revenue"],
14+
"data": [
15+
"security/ir.model.access.csv",
16+
"views/sale_views.xml",
17+
"views/account_spread.xml",
18+
"wizards/account_spread_sale_line_link_wizard.xml",
19+
],
20+
"installable": True,
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
2+
3+
from . import sale
4+
from . import sale_line
5+
from . import account_spread
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2024 Ecosoft (<https://ecosoft.co.th>)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
5+
from odoo import _, api, fields, models
6+
from odoo.exceptions import UserError
7+
8+
9+
class AccountSpread(models.Model):
10+
_inherit = "account.spread"
11+
12+
sale_line_id = fields.Many2one(
13+
"sale.order.line",
14+
string="Sales order line",
15+
)
16+
sale_id = fields.Many2one(
17+
related="sale_line_id.order_id",
18+
readonly=True,
19+
store=True,
20+
)
21+
22+
@api.model
23+
def create(self, vals):
24+
res = super().create(vals)
25+
if vals.get("sale_line_id"):
26+
sline = self.env["sale.order.line"].browse(vals["sale_line_id"])
27+
sline.write({"spread_id": res.id})
28+
return res
29+
30+
@api.depends(
31+
"estimated_amount",
32+
"currency_id",
33+
"company_id",
34+
"invoice_line_id.price_subtotal",
35+
"invoice_line_id.currency_id",
36+
"sale_line_id.price_subtotal",
37+
"sale_line_id.currency_id",
38+
"line_ids.amount",
39+
"line_ids.move_id.state",
40+
)
41+
def _compute_amounts(self):
42+
for spread in self:
43+
if spread.sale_line_id:
44+
spread.estimated_amount = -spread.sale_line_id.price_subtotal
45+
res = super()._compute_amounts()
46+
return res
47+
48+
def action_unlink_sale_line(self):
49+
"""Unlink the sale line from the spread board"""
50+
self.ensure_one()
51+
if self.sale_id.state != "draft":
52+
msg = _("Cannot unlink sales lines if the sales order is validated")
53+
raise UserError(msg)
54+
self._action_unlink_sale_line()
55+
56+
def _action_unlink_sale_line(self):
57+
self._message_post_unlink_sale_line()
58+
self.sale_line_id.spread_id = False
59+
self.sale_line_id = False
60+
61+
def _message_post_unlink_sale_line(self):
62+
for spread in self:
63+
sale_link = (
64+
"<a href=# data-oe-model=account.move "
65+
"data-oe-id=%d>%s</a>" % (spread.sale_id.id, _("Sales Order"))
66+
)
67+
msg_body = _(
68+
"Unlinked invoice line '%(spread_line_name)s' (view %(sale_link)s)."
69+
) % {
70+
"spread_line_name": spread.sale_line_id.name,
71+
"sale_link": sale_link,
72+
}
73+
spread.message_post(body=msg_body)
74+
spread_link = (
75+
"<a href=# data-oe-model=account.spread "
76+
"data-oe-id=%d>%s</a>" % (spread.id, _("Spread"))
77+
)
78+
msg_body = _("Unlinked '%(spread_link)s' (sales line %(sale_line)s).") % {
79+
"spread_link": spread_link,
80+
"sale_line": spread.sale_line_id.name,
81+
}
82+
spread.sale_id.message_post(body=msg_body)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2024 Ecosoft (<https://ecosoft.co.th>)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from odoo import models
5+
6+
7+
class SaleOrder(models.Model):
8+
_inherit = "sale.order"
9+
10+
def action_confirm(self):
11+
"""Invoked when confirm order."""
12+
self.mapped("order_line").create_auto_spread()
13+
res = super().action_confirm()
14+
spreads = self.mapped("order_line.spread_id")
15+
spreads.compute_spread_board()
16+
return res
17+
18+
def action_cancel(self):
19+
"""Cancel the spread lines and their related moves when
20+
the sales is canceled."""
21+
spread_lines = self.mapped("order_line.spread_id.line_ids")
22+
moves = spread_lines.mapped("move_id")
23+
moves.line_ids.remove_move_reconcile()
24+
moves.filtered(lambda move: move.state == "posted").button_draft()
25+
moves.with_context(force_delete=True).unlink()
26+
spread_lines.unlink()
27+
res = super().action_cancel()
28+
return res
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Copyright 2024 Ecosoft (<https://ecosoft.co.th>)
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3+
4+
from odoo import _, api, fields, models
5+
from odoo.exceptions import UserError
6+
7+
8+
class SaleOrderLine(models.Model):
9+
_inherit = "sale.order.line"
10+
11+
spread_id = fields.Many2one("account.spread", string="Spread Board", copy=False)
12+
spread_check = fields.Selection(
13+
[
14+
("linked", "Linked"),
15+
("unlinked", "Unlinked"),
16+
("unavailable", "Unavailable"),
17+
],
18+
compute="_compute_spread_check",
19+
)
20+
21+
@api.depends("spread_id", "order_id.state")
22+
def _compute_spread_check(self):
23+
for line in self:
24+
if line.spread_id:
25+
line.spread_check = "linked"
26+
elif line.order_id.state == "draft":
27+
line.spread_check = "unlinked"
28+
else:
29+
line.spread_check = "unavailable"
30+
31+
def spread_details(self):
32+
"""Button on the sale lines tree view of the sales order
33+
form to show the spread form view."""
34+
if not self:
35+
# In case the widget clicked before the creation of the line
36+
return
37+
38+
if self.spread_id:
39+
return {
40+
"name": _("Spread Details"),
41+
"view_mode": "form",
42+
"res_model": "account.spread",
43+
"type": "ir.actions.act_window",
44+
"target": "current",
45+
"readonly": False,
46+
"res_id": self.spread_id.id,
47+
}
48+
49+
# In case no spread board is linked to the sale line
50+
# open the wizard to link them
51+
ctx = dict(
52+
self.env.context,
53+
default_sale_line_id=self.id,
54+
default_company_id=self.order_id.company_id.id,
55+
allow_spread_planning=self.order_id.company_id.allow_spread_planning,
56+
)
57+
return {
58+
"name": _("Link Sales Line with Spread Board"),
59+
"view_mode": "form",
60+
"res_model": "account.spread.sale.line.link.wizard",
61+
"type": "ir.actions.act_window",
62+
"target": "new",
63+
"context": ctx,
64+
}
65+
66+
def create_auto_spread(self):
67+
"""Create auto spread table for each sale line, when needed"""
68+
69+
def _filter_line(aline, sline):
70+
"""Find matching template auto line with sale line"""
71+
if aline.product_id and sline.product_id != aline.product_id:
72+
return False
73+
return True
74+
75+
# Skip create new template when create move on spread lines
76+
if self.env.context.get("skip_create_template"):
77+
return
78+
79+
for line in self:
80+
if line.spread_check == "linked":
81+
continue
82+
spread_type = "sale"
83+
spread_auto = self.env["account.spread.template.auto"].search(
84+
[
85+
("template_id.auto_spread", "=", True),
86+
("template_id.spread_type", "=", spread_type),
87+
]
88+
)
89+
matched = spread_auto.filtered(lambda a, s=line: _filter_line(a, s))
90+
template = matched.mapped("template_id")
91+
if not template:
92+
continue
93+
elif len(template) > 1:
94+
raise UserError(
95+
_(
96+
"Too many auto spread templates (%(len_template)s) matched with the "
97+
"sale line, %(line_name)s"
98+
)
99+
% {"len_template": len(template), "line_name": line.display_name}
100+
)
101+
# Found auto spread template for this invoice line, create it
102+
wizard = self.env["account.spread.sale.line.link.wizard"].new(
103+
{
104+
"sale_line_id": line.id,
105+
"company_id": line.company_id.id,
106+
"spread_action_type": "template",
107+
"template_id": template.id,
108+
}
109+
)
110+
wizard.confirm()
111+
112+
def _prepare_invoice_line(self, **optional_values):
113+
res = super()._prepare_invoice_line(**optional_values)
114+
# Creating invoice from sales order, ensure same spread account
115+
if self.spread_id:
116+
res["spread_id"] = self.spread_id.id
117+
res["account_id"] = self.spread_id.debit_account_id.id
118+
return res
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Kitti U. <kittiu@ecosoft.co.th>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
This module works exactly the same as account_spread_cost_revenue.
2+
Except that it allow create spread revenue from sales order line
3+
and if the spread is created from sales order, it will be linked
4+
automaticall with the invoice created by this sales order.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2+
access_account_spread_sale_line_link_wizard,access_account_spread_sale_line_link_wizard,model_account_spread_sale_line_link_wizard,base.group_user,1,1,1,1

0 commit comments

Comments
 (0)