Skip to content

Commit 4efea14

Browse files
committed
[ADD] budget_management: add budget management feature
Created a budget management module to handle budgets for different periods (monthly and quarterly). Users can set a budget amount for each account. Automatically calculates expenses based on the provided amount. Shows a warning if the expense exceeds the budget amount. Added 3 types of charts: Gantt, Pivot, and Graph for better budget tracking.
1 parent 1d65eaa commit 4efea14

File tree

14 files changed

+512
-0
lines changed

14 files changed

+512
-0
lines changed

budget_management/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models, wizard

budget_management/__manifest__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
'name': "Budget",
3+
'version': '1.0',
4+
'depends': ['base', 'account'],
5+
'author': "Hitesh Prajapati",
6+
'category': 'Budget/Budget',
7+
'license': 'LGPL-3',
8+
'application': True,
9+
'instalable': True,
10+
11+
'data':[
12+
'security/ir.model.access.csv',
13+
'views/account_analytic_line.xml',
14+
'wizard/budget_wizard.xml',
15+
'views/budget_budget.xml',
16+
'views/budget_line.xml',
17+
'views/budget_menu.xml',
18+
]
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import budget_line, budget_budget, account_analytical_line
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from odoo import fields, models
2+
3+
class AccountAnalyticalLine(models.Model):
4+
_inherit = 'account.analytic.line'
5+
6+
budget_line_id = fields.Many2one('budget.line', ondelete='cascade')
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from odoo import fields, models, exceptions, api
2+
from markupsafe import Markup
3+
4+
class BudgetCreate(models.Model):
5+
_name = 'budget.budget'
6+
_inherit = ['mail.thread', 'mail.activity.mixin']
7+
_description = "Budget"
8+
9+
name = fields.Char(required=True)
10+
user_id = fields.Many2one("res.users")
11+
period_start_date = fields.Date(required=True)
12+
period_end_date = fields.Date(index=True)
13+
con_color = fields.Integer()
14+
over_budget = fields.Selection(
15+
[
16+
('warning', 'Warning'),
17+
('restriction', 'Restriction')
18+
],
19+
default="warning"
20+
)
21+
budget_line_ids = fields.One2many('budget.line', 'budget_id')
22+
state = fields.Selection(
23+
[
24+
('draft', 'Draft'),
25+
('conformed', 'Conformed'),
26+
('revised', 'Revised'),
27+
('done', 'Done')
28+
],
29+
default='draft'
30+
)
31+
company_id = fields.Many2one('res.company')
32+
active = fields.Boolean(default=True)
33+
message_follower_ids = fields.Many2many(
34+
'res.partner', 'res_partner_followers_rel', 'res_id', 'partner_id',
35+
string='Followers', help="Partners following this record"
36+
)
37+
message_ids = fields.One2many(
38+
'mail.message', 'res_id',
39+
domain=[('model', '=', 'budget.budget'), ('model', '=', 'budget.line')],
40+
string="Messages"
41+
)
42+
is_warning = fields.Boolean(default=False, compute="_compute_warning_message")
43+
44+
45+
@api.depends('budget_line_ids.archived_amount', 'budget_line_ids.budget_amount')
46+
def _compute_warning_message(self): # Compute Method : Manage the warning message
47+
for record in self:
48+
is_warning = False
49+
for line in record.budget_line_ids:
50+
if line.archived_amount and line.budget_amount and line.archived_amount > line.budget_amount:
51+
is_warning = True
52+
break
53+
record.is_warning = is_warning
54+
55+
56+
def action_budget_line_form(self): # Action Button : for open Budget Lines
57+
action = (
58+
self.env["ir.actions.act_window"]
59+
.with_context({"active_id": self.id})
60+
._for_xml_id("budget_management.act_budget_lines_view")
61+
)
62+
action["display_name"] = self.name
63+
return action
64+
65+
66+
def action_budget_form_view(self): # Action Button : for open budget Form
67+
return {
68+
"type": "ir.actions.act_window",
69+
"res_model": "budget.budget",
70+
"view_mode": "form",
71+
"res_id": self.id
72+
}
73+
74+
def action_to_draft(self): # Button Method : for draft
75+
self.state = 'draft'
76+
77+
def action_to_conform(self): # Button Method : for conformed
78+
self.state = 'conformed'
79+
80+
81+
def action_to_revised(self): # Button Method : for revised
82+
if self.state == 'conformed':
83+
self.state = 'revised'
84+
85+
orignal_record = self.browse(self.id)
86+
87+
new_record = orignal_record.copy(default={
88+
'state': 'draft',
89+
'period_start_date': self.period_start_date,
90+
'period_end_date': self.period_end_date,
91+
'name': f"Revised: {self.name}"
92+
})
93+
94+
for line in self.budget_line_ids:
95+
line.copy({
96+
'budget_id': new_record.id,
97+
})
98+
99+
new_record.message_follower_ids = self.message_follower_ids
100+
message_body = f"Revised to: <a href='#id={new_record.id}&model=budget.budget'>{new_record.name}</a>"
101+
self.message_post(body=Markup(message_body))
102+
103+
message_body = f"Revised from: <a href='#id={self.id}&model=budget.budget' target='__blank'>{self.name}</a>"
104+
new_record.message_post(body=Markup(message_body))
105+
106+
self.active = False
107+
108+
else:
109+
raise exceptions.ValidationError("The budget is not conformed yet...")
110+
111+
def action_to_done(self): # Button Method : for done
112+
self.state = 'done'
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from odoo import fields, models, api, exceptions
2+
3+
class BudgetLine(models.Model):
4+
_name = 'budget.line'
5+
_description = "Budget Line Table"
6+
7+
name = fields.Char()
8+
analytic_account_id = fields.Many2one('account.analytic.account')
9+
period_start_date = fields.Date("Start Date", related="budget_id.period_start_date", store=True)
10+
period_end_date = fields.Date("End Date", related="budget_id.period_end_date", store=True)
11+
budget_amount = fields.Float()
12+
archived_amount = fields.Float(compute="_calculate_amount")
13+
budget_id = fields.Many2one('budget.budget', ondelete="cascade")
14+
account_analytic_line_ids = fields.One2many('account.analytic.line', 'budget_line_id')
15+
company_id = fields.Many2one(related='budget_id.company_id', comodel_name='res.company')
16+
progress_percentage = fields.Float(string='Progress Percentage', compute='_compute_progress_percentage')
17+
18+
19+
@api.depends('archived_amount', 'budget_amount')
20+
def _compute_progress_percentage(self): # Compute Method : Percentage of the progressbar
21+
for record in self:
22+
if record.budget_amount > 0:
23+
record.progress_percentage = (record.archived_amount / record.budget_amount) * 100
24+
else:
25+
record.progress_percentage = 0
26+
27+
28+
@api.depends('analytic_account_id')
29+
def _calculate_amount(self): # Compute Method : Calculatation of Archive Amount
30+
for record in self:
31+
linked_lines = self.env["account.analytic.line"].search(
32+
[
33+
("amount", "<", 0),
34+
("date", ">=", record.budget_id.period_start_date),
35+
("date", "<=", record.budget_id.period_end_date),
36+
("account_id", "=", record.analytic_account_id.id),
37+
]
38+
)
39+
achieved_sum = sum(linked_lines.mapped("amount"))
40+
record.write({
41+
'archived_amount': abs(achieved_sum)
42+
})
43+
44+
def action_open_budget_entries(self): # Action Button : Open Form view of account.analytic.line
45+
return {
46+
"type": "ir.actions.act_window",
47+
"name": "Analytical Lines",
48+
"res_model": "account.analytic.line",
49+
"target": "current",
50+
"view_mode": "list",
51+
"res_id": self.id,
52+
"domain": [
53+
("account_id", "=", self.analytic_account_id.id),
54+
("date", ">=", self.period_start_date),
55+
("date", "<=", self.period_end_date),
56+
],
57+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
2+
access_budget_budget,access_budget_budget,budget_management.model_budget_budget,base.group_user,1,1,1,1
3+
access_budget_main,access_budget_main,budget_management.model_budget_main,base.group_user,1,1,1,1
4+
access_budget_line,access_budget_line,budget_management.model_budget_line,base.group_user,1,1,1,1
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
4+
<record id="analytical_account_line_view" model="ir.actions.act_window">
5+
<field name="name">acount.analytic.line</field>
6+
<field name="res_model">account.analytic.line</field>
7+
<field name="view_mode">list,form</field>
8+
</record>
9+
10+
</odoo>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
4+
<record id="budget_budget_action" model="ir.actions.act_window">
5+
<field name="name">budget.budget</field>
6+
<field name="res_model">budget.budget</field>
7+
<field name="view_mode">kanban,form</field>
8+
<field name="help" type="html">
9+
<p class="o_view_nocontent_smiling_face">
10+
Define new Budget
11+
</p>
12+
<p>
13+
Use options specified in form to list Budget
14+
</p>
15+
</field>
16+
</record>
17+
18+
<!-- Kanban View -->
19+
<record id="budget_budget_kanban_view" model="ir.ui.view">
20+
<field name="name">budget.budget.kanban</field>
21+
<field name="model">budget.budget</field>
22+
<field name="arch" type="xml">
23+
<kanban
24+
on_create="budget_management.budget_wizard_action"
25+
action="action_budget_line_form" type="object"
26+
quick_create_view="budget_management.budget_wizard_form_view"
27+
>
28+
<templates>
29+
<t t-name="menu">
30+
<div class="container">
31+
<div class="row">
32+
<div name="card_menu_view" class="col-6">
33+
<div role="menuitem">
34+
<a name="action_budget_form_view" type="object">Configuration</a>
35+
<field name="con_color" widget="kanban_color_picker"/>
36+
</div>
37+
</div>
38+
</div>
39+
</div>
40+
</t>
41+
<t t-name="card">
42+
<div>
43+
<div>
44+
<text>Name : </text>
45+
<field name="name"></field>
46+
</div>
47+
<div>
48+
<field name="period_start_date" string="Planned Date" widget="daterange" options='{"end_date_field": "period_end_date", "always_range": "1"}' required="period_start_date or period_end_date" />
49+
<field name="period_end_date" invisible="1" required="period_start_date"/>
50+
</div>
51+
</div>
52+
<footer>
53+
<div class="d-flex ms-auto align-items-center">
54+
<field name="user_id" widget="many2one_avatar_user" class="me-1"/>
55+
</div>
56+
</footer>
57+
</t>
58+
</templates>
59+
</kanban>
60+
</field>
61+
</record>
62+
63+
<!-- Form View -->
64+
<record id="budget_budget_form_view" model="ir.ui.view">
65+
<field name="name">budget.budget.form</field>
66+
<field name="model">budget.budget</field>
67+
<field name="arch" type="xml">
68+
<form>
69+
<div class="alert alert-warning p-2 mb-3" invisible="is_warning==False" role="alert">
70+
<strong>Warning:</strong> achieved amount is greater than its budget amount in the budget line.
71+
</div>
72+
<header>
73+
<!-- <button name="action_to_draft" class="btn btn-primary" type="object" string="draft" invisible="state not in ['draft']" /> -->
74+
<button name="action_to_conform" class="btn btn-primary" type="object" string="conform" invisible="state in ['done', 'conformed']"/>
75+
<button name="action_to_revised" class="btn btn-primary" type="object" string="revised" invisible="state in ['draft', 'revised', 'done']"/>
76+
<button name="action_to_done" class="btn btn-primary" type="object" string="done" invisible="state in ['done','revised']"/>
77+
<field name="state" widget="statusbar"
78+
statusbar_visible="draft,conformed,revised,done" />
79+
</header>
80+
<sheet>
81+
<group>
82+
<field name="name" readonly="state in ['revised']"/>
83+
</group>
84+
<group>
85+
<group>
86+
<field name="user_id" readonly="state in ['revised']"></field>
87+
<field name="company_id" readonly="state in ['revised']"></field>
88+
</group>
89+
<group>
90+
<field name="period_start_date" string="Planned Date"
91+
widget="daterange"
92+
options='{"end_date_field": "period_end_date", "always_range": "1"}'
93+
required="period_start_date or period_end_date" readonly="state in ['revised']"/>
94+
<field name="over_budget" readonly="state not in ['draft']"></field>
95+
</group>
96+
</group>
97+
<notebook>
98+
<page name="Budget Lines" string="Budget Lines" >
99+
<field name="budget_line_ids" >
100+
<list editable="bottom">
101+
<field name="name" />
102+
<field name="analytic_account_id" />
103+
<field name="budget_amount" />
104+
<field name="archived_amount" />
105+
<field name="progress_percentage" widget="progressbar" />
106+
<button type="object" name="action_open_budget_entries" string="View" icon="fa-arrow-circle-o-right" />
107+
</list>
108+
</field>
109+
</page>
110+
</notebook>
111+
</sheet>
112+
<chatter/>
113+
</form>
114+
</field>
115+
</record>
116+
117+
</odoo>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
4+
<record id="act_budget_lines_view" model="ir.actions.act_window" >
5+
<field name="name">Budget Lines</field>
6+
<field name="res_model">budget.line</field>
7+
<field name="view_mode">list,form,pivot,graph,gantt</field>
8+
<field name="domain">[('budget_id', '=', active_id)]</field>
9+
</record>
10+
11+
<!-- List View -->
12+
<record id="view_budget_line_tree" model="ir.ui.view" >
13+
<field name="name">budget.line.list</field>
14+
<field name="model">budget.line</field>
15+
<field name="arch" type="xml">
16+
<list string="Budget Lines">
17+
<field name="name" />
18+
<field name="analytic_account_id" />
19+
<field name="budget_amount" />
20+
<field name="archived_amount" />
21+
<field name="progress_percentage" widget="progressbar" />
22+
<button type="object" name="action_open_budget_entries" string="View" icon="fa-arrow-circle-o-right" />
23+
</list>
24+
</field>
25+
</record>
26+
27+
<!-- Pivot View -->
28+
<record id="budget_line_view_pivot" model="ir.ui.view">
29+
<field name="name">budget.line.view.pivot</field>
30+
<field name="model">budget.line</field>
31+
<field name="arch" type="xml">
32+
<pivot string="Budget Pivot">
33+
<field name="analytic_account_id" type="row"/>
34+
</pivot>
35+
</field>
36+
</record>
37+
38+
39+
40+
<!-- Graph View -->
41+
<record id="budget_line_view_graph" model="ir.ui.view">
42+
<field name="name">budget.line.view.graph</field>
43+
<field name="model">budget.line</field>
44+
<field name="arch" type="xml">
45+
<graph string="Budget Graph" type="bar">
46+
<field name="name" type="row"/>
47+
<field name="budget_amount" type="measure"/>
48+
</graph>
49+
</field>
50+
</record>
51+
52+
<!-- Gantt View -->
53+
<record id="budget_budget_view_gantt" model="ir.ui.view">
54+
<field name="name">budget.line.view.gantt</field>
55+
<field name="model">budget.line</field>
56+
<field name="arch" type="xml">
57+
<gantt string="Budget Gantt" default_group_by="name" color="analytic_account_id" date_start="period_start_date" date_stop="period_end_date">
58+
<field name="analytic_account_id" type="row"/>
59+
</gantt>
60+
</field>
61+
</record>
62+
63+
</odoo>

0 commit comments

Comments
 (0)