Skip to content

Commit

Permalink
[ADD] budget_management: add budget management feature
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hipr-odoo committed Jan 29, 2025
1 parent 1d65eaa commit 4efea14
Show file tree
Hide file tree
Showing 14 changed files with 512 additions and 0 deletions.
1 change: 1 addition & 0 deletions budget_management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models, wizard
19 changes: 19 additions & 0 deletions budget_management/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': "Budget",
'version': '1.0',
'depends': ['base', 'account'],
'author': "Hitesh Prajapati",
'category': 'Budget/Budget',
'license': 'LGPL-3',
'application': True,
'instalable': True,

'data':[
'security/ir.model.access.csv',
'views/account_analytic_line.xml',
'wizard/budget_wizard.xml',
'views/budget_budget.xml',
'views/budget_line.xml',
'views/budget_menu.xml',
]
}
1 change: 1 addition & 0 deletions budget_management/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import budget_line, budget_budget, account_analytical_line
6 changes: 6 additions & 0 deletions budget_management/models/account_analytical_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from odoo import fields, models

class AccountAnalyticalLine(models.Model):
_inherit = 'account.analytic.line'

budget_line_id = fields.Many2one('budget.line', ondelete='cascade')
112 changes: 112 additions & 0 deletions budget_management/models/budget_budget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from odoo import fields, models, exceptions, api
from markupsafe import Markup

class BudgetCreate(models.Model):
_name = 'budget.budget'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Budget"

name = fields.Char(required=True)
user_id = fields.Many2one("res.users")
period_start_date = fields.Date(required=True)
period_end_date = fields.Date(index=True)
con_color = fields.Integer()
over_budget = fields.Selection(
[
('warning', 'Warning'),
('restriction', 'Restriction')
],
default="warning"
)
budget_line_ids = fields.One2many('budget.line', 'budget_id')
state = fields.Selection(
[
('draft', 'Draft'),
('conformed', 'Conformed'),
('revised', 'Revised'),
('done', 'Done')
],
default='draft'
)
company_id = fields.Many2one('res.company')
active = fields.Boolean(default=True)
message_follower_ids = fields.Many2many(
'res.partner', 'res_partner_followers_rel', 'res_id', 'partner_id',
string='Followers', help="Partners following this record"
)
message_ids = fields.One2many(
'mail.message', 'res_id',
domain=[('model', '=', 'budget.budget'), ('model', '=', 'budget.line')],
string="Messages"
)
is_warning = fields.Boolean(default=False, compute="_compute_warning_message")


@api.depends('budget_line_ids.archived_amount', 'budget_line_ids.budget_amount')
def _compute_warning_message(self): # Compute Method : Manage the warning message
for record in self:
is_warning = False
for line in record.budget_line_ids:
if line.archived_amount and line.budget_amount and line.archived_amount > line.budget_amount:
is_warning = True
break
record.is_warning = is_warning


def action_budget_line_form(self): # Action Button : for open Budget Lines
action = (
self.env["ir.actions.act_window"]
.with_context({"active_id": self.id})
._for_xml_id("budget_management.act_budget_lines_view")
)
action["display_name"] = self.name
return action


def action_budget_form_view(self): # Action Button : for open budget Form
return {
"type": "ir.actions.act_window",
"res_model": "budget.budget",
"view_mode": "form",
"res_id": self.id
}

def action_to_draft(self): # Button Method : for draft
self.state = 'draft'

def action_to_conform(self): # Button Method : for conformed
self.state = 'conformed'


def action_to_revised(self): # Button Method : for revised
if self.state == 'conformed':
self.state = 'revised'

orignal_record = self.browse(self.id)

new_record = orignal_record.copy(default={
'state': 'draft',
'period_start_date': self.period_start_date,
'period_end_date': self.period_end_date,
'name': f"Revised: {self.name}"
})

for line in self.budget_line_ids:
line.copy({
'budget_id': new_record.id,
})

new_record.message_follower_ids = self.message_follower_ids
message_body = f"Revised to: <a href='#id={new_record.id}&model=budget.budget'>{new_record.name}</a>"
self.message_post(body=Markup(message_body))

message_body = f"Revised from: <a href='#id={self.id}&model=budget.budget' target='__blank'>{self.name}</a>"
new_record.message_post(body=Markup(message_body))

self.active = False

else:
raise exceptions.ValidationError("The budget is not conformed yet...")

def action_to_done(self): # Button Method : for done
self.state = 'done'
57 changes: 57 additions & 0 deletions budget_management/models/budget_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from odoo import fields, models, api, exceptions

class BudgetLine(models.Model):
_name = 'budget.line'
_description = "Budget Line Table"

name = fields.Char()
analytic_account_id = fields.Many2one('account.analytic.account')
period_start_date = fields.Date("Start Date", related="budget_id.period_start_date", store=True)
period_end_date = fields.Date("End Date", related="budget_id.period_end_date", store=True)
budget_amount = fields.Float()
archived_amount = fields.Float(compute="_calculate_amount")
budget_id = fields.Many2one('budget.budget', ondelete="cascade")
account_analytic_line_ids = fields.One2many('account.analytic.line', 'budget_line_id')
company_id = fields.Many2one(related='budget_id.company_id', comodel_name='res.company')
progress_percentage = fields.Float(string='Progress Percentage', compute='_compute_progress_percentage')


@api.depends('archived_amount', 'budget_amount')
def _compute_progress_percentage(self): # Compute Method : Percentage of the progressbar
for record in self:
if record.budget_amount > 0:
record.progress_percentage = (record.archived_amount / record.budget_amount) * 100
else:
record.progress_percentage = 0


@api.depends('analytic_account_id')
def _calculate_amount(self): # Compute Method : Calculatation of Archive Amount
for record in self:
linked_lines = self.env["account.analytic.line"].search(
[
("amount", "<", 0),
("date", ">=", record.budget_id.period_start_date),
("date", "<=", record.budget_id.period_end_date),
("account_id", "=", record.analytic_account_id.id),
]
)
achieved_sum = sum(linked_lines.mapped("amount"))
record.write({
'archived_amount': abs(achieved_sum)
})

def action_open_budget_entries(self): # Action Button : Open Form view of account.analytic.line
return {
"type": "ir.actions.act_window",
"name": "Analytical Lines",
"res_model": "account.analytic.line",
"target": "current",
"view_mode": "list",
"res_id": self.id,
"domain": [
("account_id", "=", self.analytic_account_id.id),
("date", ">=", self.period_start_date),
("date", "<=", self.period_end_date),
],
}
4 changes: 4 additions & 0 deletions budget_management/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_budget_budget,access_budget_budget,budget_management.model_budget_budget,base.group_user,1,1,1,1
access_budget_main,access_budget_main,budget_management.model_budget_main,base.group_user,1,1,1,1
access_budget_line,access_budget_line,budget_management.model_budget_line,base.group_user,1,1,1,1
10 changes: 10 additions & 0 deletions budget_management/views/account_analytic_line.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="analytical_account_line_view" model="ir.actions.act_window">
<field name="name">acount.analytic.line</field>
<field name="res_model">account.analytic.line</field>
<field name="view_mode">list,form</field>
</record>

</odoo>
117 changes: 117 additions & 0 deletions budget_management/views/budget_budget.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="budget_budget_action" model="ir.actions.act_window">
<field name="name">budget.budget</field>
<field name="res_model">budget.budget</field>
<field name="view_mode">kanban,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Define new Budget
</p>
<p>
Use options specified in form to list Budget
</p>
</field>
</record>

<!-- Kanban View -->
<record id="budget_budget_kanban_view" model="ir.ui.view">
<field name="name">budget.budget.kanban</field>
<field name="model">budget.budget</field>
<field name="arch" type="xml">
<kanban
on_create="budget_management.budget_wizard_action"
action="action_budget_line_form" type="object"
quick_create_view="budget_management.budget_wizard_form_view"
>
<templates>
<t t-name="menu">
<div class="container">
<div class="row">
<div name="card_menu_view" class="col-6">
<div role="menuitem">
<a name="action_budget_form_view" type="object">Configuration</a>
<field name="con_color" widget="kanban_color_picker"/>
</div>
</div>
</div>
</div>
</t>
<t t-name="card">
<div>
<div>
<text>Name : </text>
<field name="name"></field>
</div>
<div>
<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" />
<field name="period_end_date" invisible="1" required="period_start_date"/>
</div>
</div>
<footer>
<div class="d-flex ms-auto align-items-center">
<field name="user_id" widget="many2one_avatar_user" class="me-1"/>
</div>
</footer>
</t>
</templates>
</kanban>
</field>
</record>

<!-- Form View -->
<record id="budget_budget_form_view" model="ir.ui.view">
<field name="name">budget.budget.form</field>
<field name="model">budget.budget</field>
<field name="arch" type="xml">
<form>
<div class="alert alert-warning p-2 mb-3" invisible="is_warning==False" role="alert">
<strong>Warning:</strong> achieved amount is greater than its budget amount in the budget line.
</div>
<header>
<!-- <button name="action_to_draft" class="btn btn-primary" type="object" string="draft" invisible="state not in ['draft']" /> -->
<button name="action_to_conform" class="btn btn-primary" type="object" string="conform" invisible="state in ['done', 'conformed']"/>
<button name="action_to_revised" class="btn btn-primary" type="object" string="revised" invisible="state in ['draft', 'revised', 'done']"/>
<button name="action_to_done" class="btn btn-primary" type="object" string="done" invisible="state in ['done','revised']"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,conformed,revised,done" />
</header>
<sheet>
<group>
<field name="name" readonly="state in ['revised']"/>
</group>
<group>
<group>
<field name="user_id" readonly="state in ['revised']"></field>
<field name="company_id" readonly="state in ['revised']"></field>
</group>
<group>
<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" readonly="state in ['revised']"/>
<field name="over_budget" readonly="state not in ['draft']"></field>
</group>
</group>
<notebook>
<page name="Budget Lines" string="Budget Lines" >
<field name="budget_line_ids" >
<list editable="bottom">
<field name="name" />
<field name="analytic_account_id" />
<field name="budget_amount" />
<field name="archived_amount" />
<field name="progress_percentage" widget="progressbar" />
<button type="object" name="action_open_budget_entries" string="View" icon="fa-arrow-circle-o-right" />
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>

</odoo>
63 changes: 63 additions & 0 deletions budget_management/views/budget_line.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="act_budget_lines_view" model="ir.actions.act_window" >
<field name="name">Budget Lines</field>
<field name="res_model">budget.line</field>
<field name="view_mode">list,form,pivot,graph,gantt</field>
<field name="domain">[('budget_id', '=', active_id)]</field>
</record>

<!-- List View -->
<record id="view_budget_line_tree" model="ir.ui.view" >
<field name="name">budget.line.list</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<list string="Budget Lines">
<field name="name" />
<field name="analytic_account_id" />
<field name="budget_amount" />
<field name="archived_amount" />
<field name="progress_percentage" widget="progressbar" />
<button type="object" name="action_open_budget_entries" string="View" icon="fa-arrow-circle-o-right" />
</list>
</field>
</record>

<!-- Pivot View -->
<record id="budget_line_view_pivot" model="ir.ui.view">
<field name="name">budget.line.view.pivot</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<pivot string="Budget Pivot">
<field name="analytic_account_id" type="row"/>
</pivot>
</field>
</record>



<!-- Graph View -->
<record id="budget_line_view_graph" model="ir.ui.view">
<field name="name">budget.line.view.graph</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<graph string="Budget Graph" type="bar">
<field name="name" type="row"/>
<field name="budget_amount" type="measure"/>
</graph>
</field>
</record>

<!-- Gantt View -->
<record id="budget_budget_view_gantt" model="ir.ui.view">
<field name="name">budget.line.view.gantt</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<gantt string="Budget Gantt" default_group_by="name" color="analytic_account_id" date_start="period_start_date" date_stop="period_end_date">
<field name="analytic_account_id" type="row"/>
</gantt>
</field>
</record>

</odoo>
Loading

0 comments on commit 4efea14

Please sign in to comment.