Skip to content

Commit 173f7c2

Browse files
committed
[IMP] budget_management: added analytic account and updated accordingly
Added analytic account in wizard which add selected accounts to each budget while creating it. Updated button for state management of the budget. Edited action_on_revise button to post message in chatter when it create revised budget for it. Updated functions to compute achive amount and achive percentage.
1 parent abbcce9 commit 173f7c2

10 files changed

+238
-130
lines changed

budget_management/__manifest__.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"name": "Budget Management",
3+
"category": "Budget",
34
"version": "1.0",
4-
"depends": ["base", "account"],
5+
"depends": ["base", "account", "accountant"],
56
"author": "djip-odoo",
67
"description": """
78
Part of technical training
@@ -10,11 +11,9 @@
1011
"data": [
1112
"security/ir.model.access.csv",
1213
"views/budget_line_views.xml",
13-
"wizards/actions_wizard.xml",
1414
"views/actions_menu_and_button.xml",
1515
"views/menu_views.xml",
1616
"views/budget_views.xml",
17-
'views/account_analytic_line_view.xml',
1817
"wizards/wizard_add_budgets_view.xml",
1918
],
2019
"application": True,

budget_management/models/account_analytic_line.py

+64-27
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,79 @@
44

55
class AccountAnalyticLine(models.Model):
66
_inherit = "account.analytic.line"
7-
8-
budget_line_id = fields.Many2one(comodel_name="budget.management.budget.lines")
9-
7+
108
@api.model_create_multi
119
def create(self, vals_list):
1210
# print("* " * 100)
1311
# print(vals_list)
1412
# print("* " * 100)
13+
1514
for vals in vals_list:
16-
budget_line = self.env["budget.management.budget.lines"].browse(
17-
vals.get("budget_line_id")
18-
)
19-
budget = budget_line.budget_id
20-
if budget.on_over_budget == "restriction":
21-
if sum(budget_line.analytic_line_ids.mapped("amount"))+vals.get("amount") > budget_line.budget_amount:
22-
raise ValidationError(
23-
"You cannot create a budget line because it exceeds the allowed budget!"
24-
)
15+
entry_date = vals.get("date")
16+
if not entry_date:
17+
raise ValidationError("The date field is required to determine the appropriate budget.")
18+
19+
budget_line = self.env["budget.management.budget.lines"].search([
20+
("budget_id.date_start", "<=", entry_date),
21+
("budget_id.date_end", ">=", entry_date),
22+
("budget_id.active", "=", True),
23+
("analytic_account_id", "=", vals.get("account_id"))
24+
], limit=1)
25+
26+
if budget_line:
27+
budget = budget_line.budget_id
28+
29+
analytic_account_lines = self.env["account.analytic.line"].search_read(
30+
[
31+
("account_id", "=", vals.get("account_id")),
32+
("date", ">=", budget.date_start),
33+
("date", "<=", budget.date_end),
34+
("amount", "<", 0),
35+
],
36+
fields=["amount"],
37+
)
38+
# print(list(line.get("amount") for line in analytic_account_lines))
39+
achieved = (sum(line.get("amount") for line in analytic_account_lines))
40+
# print(budget.on_over_budget, abs(achieved + vals.get("amount")), budget_line.budget_amount)
41+
42+
if budget.on_over_budget == "restriction":
43+
if abs(achieved + vals.get("amount")) > budget_line.budget_amount:
44+
raise ValidationError(
45+
"You cannot create a budget line because it exceeds the allowed budget!"
46+
)
2547
return super(AccountAnalyticLine, self).create(vals_list)
2648

2749
def write(self, vals):
28-
# print("* " * 100)
29-
# print(vals)
30-
# print("* " * 100)
31-
if "amount" in vals:
50+
if "date" in vals or "amount" in vals or "account_id" in vals:
3251
for record in self:
33-
old_amount = record.amount
34-
new_amount = vals.get("amount")
35-
print(old_amount,new_amount)
36-
total_amount = sum(record.budget_line_id.analytic_line_ids.mapped("amount")) + new_amount - old_amount
52+
entry_date = vals.get("date", record.date)
53+
54+
budget_line = self.env["budget.management.budget.lines"].search([
55+
("budget_id.date_start", "<=", entry_date),
56+
("budget_id.date_end", ">=", entry_date),
57+
("budget_id.active", "=", True),
58+
("analytic_account_id", "=", vals.get("account_id", record.account_id.id))
59+
], limit=1)
60+
61+
if budget_line:
62+
budget = budget_line.budget_id
63+
64+
analytic_account_lines = self.env["account.analytic.line"].search_read(
65+
[
66+
("account_id", "=", vals.get("account_id", record.account_id.id)),
67+
("date", ">=", budget.date_start),
68+
("date", "<=", budget.date_end),
69+
("amount", "<", 0),
70+
],
71+
fields=["amount"],
72+
)
73+
achieved = (sum(line.get("amount") for line in analytic_account_lines))
74+
75+
new_amount = vals.get("amount", record.amount)
76+
if budget.on_over_budget == "restriction":
77+
if abs(achieved - record.amount + new_amount) > budget_line.budget_amount:
78+
raise ValidationError(
79+
"You cannot modify the budget line because it exceeds the allowed budget!"
80+
)
3781

38-
budget_line = record.budget_line_id
39-
budget = budget_line.budget_id
40-
if budget.on_over_budget == "restriction" and total_amount > budget_line.budget_amount:
41-
raise ValidationError(
42-
"You cannot update this budget line because it exceeds the allowed budget!"
43-
)
44-
4582
return super(AccountAnalyticLine, self).write(vals)

budget_management/models/budget.py

+61-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from odoo import models, fields, api
22
from odoo.exceptions import ValidationError, UserError
3+
from markupsafe import escape, Markup
34

45

56
class Budget(models.Model):
@@ -35,6 +36,7 @@ class Budget(models.Model):
3536
comodel_name="res.users", # Assuming you want a link to Odoo users
3637
tracking=True,
3738
readonly=True,
39+
string="Revised by"
3840
)
3941
date_start = fields.Date(string="Start Date", required=True)
4042
date_end = fields.Date(string="Expiration Date", required=True, index=True)
@@ -46,7 +48,7 @@ class Budget(models.Model):
4648
budget_line_ids = fields.One2many(
4749
comodel_name="budget.management.budget.lines", inverse_name="budget_id"
4850
)
49-
warnings = fields.Text(readonly=True)
51+
warnings = fields.Text(compute="_check_over_budget")
5052
currency_id = fields.Many2one(
5153
comodel_name="res.currency",
5254
string="Currency",
@@ -58,9 +60,15 @@ class Budget(models.Model):
5860
def _compute_budget_name(self):
5961
for record in self:
6062
if record.date_start and record.date_end:
61-
start_date = record.date_start.strftime("%Y-%m")
62-
end_date = record.date_end.strftime("%Y-%m")
63-
record.name = f"Budget {start_date} to {end_date}"
63+
start_date = record.date_start
64+
end_date = record.date_end
65+
if (
66+
start_date.year == end_date.year
67+
and start_date.month == end_date.month
68+
):
69+
record.name = f"Budget - {start_date.strftime('%B %Y')}"
70+
else:
71+
record.name = f"Budget - {start_date.strftime('%d %B, %Y')} to {end_date.strftime('%d %B, %Y')}"
6472
else:
6573
record.name = "Unknown Budget"
6674

@@ -78,13 +86,37 @@ def onclick_revise(self):
7886
for record in self:
7987
if record.state != "confirmed":
8088
raise UserError("Only confirmed budgets can be revised.")
81-
if record.state in ["confirmed"]:
89+
90+
if record.state == "confirmed":
8291
record.revision_id = self.env.user
8392
record.state = "revised"
84-
# new_budget = record.copy({"revision_id": self.id, "state": "draft"})
85-
# record.message_post(
86-
# body=f'Revised into <a href="#id={new_budget.id}&model=budget.budget">{new_budget.name}</a>.'
87-
# )
93+
record.active = False
94+
95+
new_budget = record.copy(
96+
{"revision_id": None, "state": "draft", "active": True}
97+
)
98+
99+
for budget_line in record.budget_line_ids:
100+
self.env["budget.management.budget.lines"].create(
101+
{
102+
"budget_id": new_budget.id,
103+
"name": budget_line.name,
104+
"budget_amount": budget_line.budget_amount,
105+
"achieved_amount": budget_line.achieved_amount,
106+
"achieved_percentage": budget_line.achieved_percentage,
107+
"analytic_account_id": budget_line.analytic_account_id.id,
108+
"currency_id": budget_line.currency_id.id,
109+
}
110+
)
111+
112+
action = self.env.ref(
113+
"budget_management.action_budget_management_menu_budget"
114+
)
115+
record.message_post(
116+
body=Markup(
117+
f'<a href="odoo/action-{action.id}/{new_budget.id}">{new_budget.name}</a>.'
118+
)
119+
)
88120

89121
def onclick_done(self):
90122
for record in self:
@@ -102,7 +134,25 @@ def _check_period_overlap(self):
102134
("company_id", "=", record.company_id.id),
103135
]
104136
)
105-
if overlapping_budgets:
137+
overlapping_non_revised = False
138+
139+
for budget in overlapping_budgets:
140+
if budget.state != "revised":
141+
overlapping_non_revised = True
142+
break
143+
144+
if overlapping_non_revised:
106145
raise ValidationError(
107-
"Cannot create overlapping budgets for the same period and company."
146+
"Cannot create overlapping budgets for the same period and company. If not displayed your selected period budget! please check archived budgets"
108147
)
148+
149+
@api.depends("budget_line_ids.over_budget")
150+
def _check_over_budget(self):
151+
for record in self:
152+
if (
153+
record.on_over_budget == "warning"
154+
and any(ob > 0 for ob in record.budget_line_ids.mapped("over_budget")) > 0
155+
):
156+
record.warnings = "Achieved amount exceeds the budget!"
157+
else:
158+
record.warnings = False

budget_management/models/budget_line.py

+63-32
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class BudgetLine(models.Model):
1010
budget_id = fields.Many2one(
1111
comodel_name="budget.budget", string="Budget", required=True
1212
)
13-
state = fields.Selection(related="budget_id.state")
13+
state = fields.Selection(related="budget_id.state", readonly=True)
1414
budget_amount = fields.Monetary(
1515
string="Budget Amount",
1616
default=0.0,
@@ -19,30 +19,21 @@ class BudgetLine(models.Model):
1919
)
2020
achieved_amount = fields.Monetary(
2121
string="Achieved Amount",
22-
default=0.0,
2322
compute="_compute_achieved_amount",
24-
store=True,
2523
currency_field="currency_id",
2624
)
2725
achieved_percentage = fields.Float(
2826
string="Achieved (%)",
2927
compute="_compute_achieved_amount",
30-
store=True,
3128
readonly=True,
3229
help="Percentage of the budget achieved based on analytic lines.",
3330
)
3431
analytic_account_id = fields.Many2one(
3532
"account.analytic.account", string="Analytic Account", required=True
3633
)
37-
analytic_line_ids = fields.One2many(
38-
comodel_name="account.analytic.line",
39-
inverse_name="budget_line_id",
40-
string="Analytic Lines",
41-
)
4234
over_budget = fields.Monetary(
4335
string="Over Budget",
4436
compute="_compute_achieved_amount",
45-
store=True,
4637
help="The amount by which the budget line exceeds its allocated budget.",
4738
currency_field="currency_id",
4839
)
@@ -53,50 +44,90 @@ class BudgetLine(models.Model):
5344
readonly=True,
5445
)
5546

56-
@api.depends("analytic_line_ids.amount")
47+
@api.depends("budget_amount")
5748
def _compute_achieved_amount(self):
5849
for record in self:
59-
record.achieved_amount = sum(record.analytic_line_ids.mapped("amount"))
50+
if not record.analytic_account_id:
51+
record.achieved_amount = 0.0
52+
record.achieved_percentage = 0.0
53+
record.over_budget = 0.0
54+
continue
55+
analytic_account_lines = self.env["account.analytic.line"].search_read(
56+
[
57+
("auto_account_id", "=", record.analytic_account_id.id),
58+
("date", ">=", record.budget_id.date_start),
59+
("date", "<=", record.budget_id.date_end),
60+
("amount", "<", 0),
61+
],
62+
fields=["amount"],
63+
)
64+
65+
achieved = sum(line.get("amount") for line in analytic_account_lines)
66+
67+
record.achieved_amount = abs(achieved)
6068
record.achieved_percentage = (
6169
(record.achieved_amount / record.budget_amount) * 100
6270
if record.budget_amount > 0
6371
else 0.0
6472
)
6573
record.over_budget = max(0.0, record.achieved_amount - record.budget_amount)
6674

67-
if (
68-
record.budget_id.on_over_budget == "warning"
69-
and record.achieved_amount > record.budget_amount
70-
):
71-
record.budget_id.warnings = "Achived amount is more than your budget!"
72-
else:
73-
record.budget_id.warnings = False
75+
# if (
76+
# record.budget_id.on_over_budget == "warning"
77+
# and any(record.over_budget for record in self) > 0
78+
# ):
79+
# record.budget_id.warnings = "Achieved amount exceeds the budget!"
80+
# else:
81+
# record.budget_id.warnings = False
7482

7583
@api.constrains("budget_amount")
7684
def _check_budget_amount(self):
7785
for record in self:
7886
if record.budget_amount < 0:
7987
raise ValidationError("Budget amount cannot be negative.")
8088

81-
8289
@api.model_create_multi
8390
def create(self, vals_list):
8491
active_budget = None
8592
if self.env.context.get("active_id"):
86-
active_budget = self.env["budget.budget"].browse(self.env.context.get("active_id"))
87-
if active_budget.state != "draft":
88-
raise UserError("Budget lines can only be created when the state is 'draft'.")
93+
active_budget = self.env["budget.budget"].browse(
94+
self.env.context["active_id"]
95+
)
8996
else:
9097
for vals in vals_list:
91-
budget_id = vals.get("budget_id")
92-
if budget_id:
93-
active_budget = self.env["budget.budget"].browse(budget_id)
98+
if vals.get("budget_id"):
99+
active_budget = self.env["budget.budget"].browse(vals["budget_id"])
94100
break
95-
96-
if not active_budget:
97-
raise UserError("No budget found in context or record.")
98101

99-
if active_budget.state != "draft":
100-
raise UserError("Budget lines can only be created when the state is 'draft'.")
102+
if not active_budget or active_budget.state != "draft":
103+
raise UserError(
104+
"Budget lines can only be created when the budget is in 'draft' state."
105+
)
106+
107+
return super(BudgetLine, self).create(vals_list)
108+
109+
def open_analytic_lines_action(self):
110+
if not self.budget_id:
111+
raise UserError("No budget linked to this budget line.")
112+
113+
budget_start_date = self.budget_id.date_start
114+
budget_end_date = self.budget_id.date_end
101115

102-
return super(BudgetLine, self).create(vals_list)
116+
return {
117+
"type": "ir.actions.act_window",
118+
"name": "Analytic Lines",
119+
"res_model": "account.analytic.line",
120+
"view_mode": "list",
121+
"target": "current",
122+
"context": {
123+
"default_account_id": self.analytic_account_id.id,
124+
"budget_start_date": budget_start_date,
125+
"budget_end_date": budget_end_date,
126+
},
127+
"domain": [
128+
("account_id", "=", self.analytic_account_id.id),
129+
("date", ">=", budget_start_date),
130+
("date", "<=", budget_end_date),
131+
("amount", "<", 0),
132+
],
133+
}

0 commit comments

Comments
 (0)