Skip to content

Commit

Permalink
[ADD] budget_management: add module for managing budgets
Browse files Browse the repository at this point in the history
Add  budget_management module for managing budgets.
Implemented bulk creation of budgets on monthly and quarter basis.
Each budget line is linked to its respective analytic account lines and
analytic account.
Budget lines have graph, gantt and pivot views for better presentation.
  • Loading branch information
dija-odoo committed Jan 29, 2025
1 parent 733c9f3 commit dfc9e0f
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 108 deletions.
2 changes: 1 addition & 1 deletion budget_management/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import models
from . import models, wizard
3 changes: 2 additions & 1 deletion budget_management/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "Budget Management",
"category": "Accounting/Budgeting",
"depends": ["base", "analytic"],
"depends": ["base", "account"],
"description": """
This module is for managing Budgets.
""",
"data": [
"security/ir.model.access.csv",
"views/budget_views.xml",
"views/budget_lines_views.xml",
"wizard/bulk_budget_views.xml",
"views/budget_menus.xml",
],
"demo": [],
Expand Down
4 changes: 3 additions & 1 deletion budget_management/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import budget_budget, budget_lines
from . import budget_budget
from . import budget_line
from . import account_analytic_line
7 changes: 7 additions & 0 deletions budget_management/models/account_analytic_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import fields, models


class AccountAnalyticLine(models.Model):
_inherit = "account.analytic.line"

budget_line_id = fields.Many2one(comodel_name="budget.line", string="Budget Line", ondelete="cascade" )
116 changes: 66 additions & 50 deletions budget_management/models/budget_budget.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
from markupsafe import Markup
from odoo import api, fields, models, _
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


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

_inherit = ["mail.thread", "mail.activity.mixin"]

name = fields.Char("Budget Name", compute="_compute_budget_name", required=True)
user_id = fields.Many2one(
"res.users", "Responsible", default=lambda self: self.env.user
)
revision_id = fields.Many2one(
"res.users", "Revision_id", default=lambda self: self.env.user
)
company_id = fields.Many2one(
"res.company", "Company", required=True, default=lambda self: self.env.company
)
active = fields.Boolean(default=True)
warning_message = fields.Boolean(default=False, compute='_compute_warning')
date_from = fields.Date("Start Date", required=True)
date_to = fields.Date("End Date", required=True)
user_id = fields.Many2one("res.users", "Responsible", default=lambda self: self.env.user)
revision_id = fields.Many2one("res.users", "Revision_id", default=lambda self: self.env.user)
company_id = fields.Many2one("res.company", "Company", default=lambda self: self.env.company)
parent_id = fields.Many2one(string="Revision Of",comodel_name="budget.budget", ondelete="cascade")
children_ids = fields.One2many(string="Revisions",comodel_name="budget.budget",inverse_name="parent_id")
budget_line_ids = fields.One2many("budget.line", "budget_id", copy=True)
over_budget = fields.Selection(
string="On Over Budget",
selection=[
("warning", "Warning"),
("restriction", "Restriction"),
],
required=True,
)
required=True,default="warning")
state = fields.Selection(
string="Status",
selection=[
Expand All @@ -38,28 +37,57 @@ class BudgetBudget(models.Model):
required=True,
default="draft",
)
color = fields.Integer("Color Index", default=0)
parent_id = fields.Many2one(
string="Revision Of",
comodel_name="budget.budget",
ondelete="cascade",
)
children_ids = fields.One2many(
string="Revisions",
comodel_name="budget.budget",
inverse_name="parent_id",
)
budget_lines = fields.One2many("budget.line", "budget_id")
active = fields.Boolean(default=True)

def action_view_tasks(self):
action = (
self.env["ir.actions.act_window"]
.with_context({"active_id": self.id})
._for_xml_id("budget_management.budget_lines_action")
)
action["display_name"] = self.name
return action
@api.constrains("date_from", "date_to")
def _constrains_date(self):
for record in self:
check_budget = self.env["budget.budget"].search(
[
("date_from", "=", record.date_from),
("date_to", "=", record.date_to),
("id", "not in", self.ids),
("active", "=", True),
]
)
if check_budget:
raise ValidationError(
"BUDGET ALREADY EXISTS FOR THIS PERIOD"
)
if record.date_from > record.date_to:
raise ValidationError(
"START DATE MUST BE BEFORE END DATE"
)


@api.depends("date_from", "date_to")
def _compute_budget_name(self):
for budget in self:
if budget.date_from:
budget.name = _(
"Budget : %(date_from)s to %(date_to)s",
date_from=budget.date_from or "",
date_to=budget.date_to or "",
)
else:
budget.name = "Budget : "

@api.depends("budget_line_ids.achieved_amount","budget_line_ids.budget_amount")
def _compute_warning(self):
for record in self:
if record.over_budget=="warning" and any(line.achieved_amount > line.budget_amount for line in record.budget_line_ids):
record.warning_message = True
else:
record.warning_message=False

def action_view_budget_lines(self):
return {
"name": _("Budget Lines"),
"type": "ir.actions.act_window",
"view_mode": "list,graph,pivot,gantt",
"res_model": "budget.line",
"target": "self",
"domain": [("budget_id", "=", self.id)],
}

def action_budget_confirm(self):
self.parent_id.filtered(lambda b: b.state == "confirmed").state = "revised"
Expand All @@ -78,14 +106,15 @@ def action_budget_done(self):
def create_revised_budget(self):
revised = self.browse()
for budget in self:
budget.state="revised"
budget.active=False
revised_budget = budget.copy(
default={
"name": _("REV %s", budget.name),
"name": _("REV %s") % budget.name,
"parent_id": budget.id,
}
)
revised += revised_budget
budget.write({"active": False})
budget.message_post(
body=Markup(
"%s: <a href='#' data-oe-model='budget.budget' data-oe-id='%s'>%s</a>"
Expand All @@ -96,16 +125,3 @@ def create_revised_budget(self):
revised_budget.name,
)
)
return revised._get_records_action()

@api.depends("date_from", "date_to")
def _compute_budget_name(self):
for budget in self:
if budget.date_from:
budget.name = _(
"Budget : %(date_from)s to %(date_to)s",
date_from=budget.date_from,
date_to=budget.date_to,
)
else:
budget.name = "Budget : "
62 changes: 62 additions & 0 deletions budget_management/models/budget_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from odoo import api, fields, models
from odoo.exceptions import ValidationError


class BudgetLine(models.Model):
_name = "budget.line"
_description = "Budget Lines"

name = fields.Char("Name")
analytic_account = fields.Many2one("account.analytic.account")
budget_amount = fields.Integer("Budget Amount", required=True)
achieved_amount = fields.Float(
"Achieved Amount", compute="_compute_achieved_amount", store=True
)
budget_id = fields.Many2one("budget.budget")
progress = fields.Float("Achieved (%)", compute="_compute_progress")
account_analytics_line_ids = fields.One2many(
comodel_name="account.analytic.line", inverse_name="budget_line_id"
)
date_from=fields.Date(related='budget_id.date_from')
date_to=fields.Date(related='budget_id.date_to')
user_id=fields.Many2one(related='budget_id.user_id')

@api.constrains("achieved_amount", "budget_amount", "account_analytics_line_ids")
def _check_restriction_on_creation(self):
for record in self:
if record.budget_id.over_budget == "restriction":
if record.achieved_amount > record.budget_amount:
raise ValidationError(
"Cannot create this analytic line, achieved amount is greater than budget amount"
)

@api.depends("account_analytics_line_ids.amount",)
def _compute_achieved_amount(self):
for budget_line in self:
budget_line.achieved_amount = abs(
sum(budget_line.account_analytics_line_ids.filtered(lambda l : l.amount<0).mapped("amount"))
)

@api.depends("achieved_amount", "budget_amount")
def _compute_progress(self):
for record in self:
if record.budget_amount:
record.progress = abs(
(record.achieved_amount / record.budget_amount) * 100
)
else:
record.progress = 0

def action_related_account_analytic_lines(self):
self.ensure_one()
for record in self:
action = record.env["ir.actions.act_window"]._for_xml_id(
"analytic.account_analytic_line_action"
)
action["display_name"] = f"Analytic Lines ({self.analytic_account.name})"
action["domain"] = [("budget_line_id", "=", self.id)]
action["context"] = {
"default_budget_line_id": self.id,
"default_account_id": self.analytic_account.id,
}
return action
15 changes: 0 additions & 15 deletions budget_management/models/budget_lines.py

This file was deleted.

3 changes: 2 additions & 1 deletion budget_management/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -1,3 +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_line,access_budget_line,budget_management.model_budget_line,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
access_bulk_budget,access_bulk_budget,budget_management.model_bulk_budget,base.group_user,1,1,1,1
40 changes: 38 additions & 2 deletions budget_management/views/budget_lines_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<record id="budget_lines_action" model="ir.actions.act_window">
<field name="name">Budget Lines</field>
<field name="res_model">budget.line</field>
<field name="view_mode">list</field>
<field name="view_mode">list,graph,pivot,gantt</field>
<field name="domain">[('budget_id', '=', active_id)]</field>
</record>

Expand All @@ -13,13 +13,49 @@
<field name="model">budget.line</field>
<field name="arch" type="xml">
<list sample="1" editable="bottom">
<field name="name" string="Name"/>
<field name="analytic_account" string="Analytic Account"/>
<field name="budget_amount" />
<field name="achieved_amount"></field>
<field name="progress" widget="progressbar"></field>
<button name="related_account_analytic_lines" string="view"/>
<button name="action_related_account_analytic_lines" type='object' string="view"/>
</list>
</field>
</record>

<record id="view_budget_line_graph" model="ir.ui.view">
<field name="name">budget.line.graph</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<graph sample="1" disable_linking="1" string="Budget Lines" type="bar" stacked="True">
<field name="name" type="row"/>
<field name="budget_amount" type="measure" string="Budget Amount"/>
<field name="achieved_amount" type="measure" string="Achieved Amount"/>
</graph>
</field>
</record>


<record id="view_budget_line_gantt" model="ir.ui.view">
<field name="name">budget.line.gantt.view</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<gantt default_group_by="name" progress="progress" date_start="date_from" date_stop="date_to" string="Budget Lines" color="id" >
<field name="name"/>
<field name="analytic_account"/>

</gantt>
</field>
</record>

<record id="view_budget_line_pivot" model="ir.ui.view">
<field name="name">budget.line.pivot</field>
<field name="model">budget.line</field>
<field name="arch" type="xml">
<pivot string="Budget Lines" sample="1">
<field name="name" type="row"/>
</pivot>
</field>
</record>

</odoo>
Loading

0 comments on commit dfc9e0f

Please sign in to comment.