From 77e84f54ae6ddc66069d917aecf7f17eab1b4115 Mon Sep 17 00:00:00 2001 From: dija-odoo Date: Wed, 29 Jan 2025 18:25:56 +0530 Subject: [PATCH] [ADD] budget_management: add module for managing budgets 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. --- budget_management/__init__.py | 2 +- budget_management/__manifest__.py | 3 +- budget_management/models/__init__.py | 4 +- .../models/account_analytic_line.py | 7 ++ budget_management/models/budget_budget.py | 116 ++++++++++-------- budget_management/models/budget_line.py | 62 ++++++++++ budget_management/models/budget_lines.py | 15 --- .../security/ir.model.access.csv | 3 +- .../views/budget_lines_views.xml | 40 +++++- budget_management/views/budget_views.xml | 78 ++++++------ budget_management/wizard/__init__.py | 1 + budget_management/wizard/bulk_budget.py | 70 +++++++++++ .../wizard/bulk_budget_views.xml | 49 ++++++++ 13 files changed, 342 insertions(+), 108 deletions(-) create mode 100644 budget_management/models/account_analytic_line.py create mode 100644 budget_management/models/budget_line.py delete mode 100644 budget_management/models/budget_lines.py create mode 100644 budget_management/wizard/__init__.py create mode 100644 budget_management/wizard/bulk_budget.py create mode 100644 budget_management/wizard/bulk_budget_views.xml diff --git a/budget_management/__init__.py b/budget_management/__init__.py index 0650744f6b..b046ff82fc 100644 --- a/budget_management/__init__.py +++ b/budget_management/__init__.py @@ -1 +1 @@ -from . import models +from . import models, wizard diff --git a/budget_management/__manifest__.py b/budget_management/__manifest__.py index bd733b2f77..6bfe34be1b 100644 --- a/budget_management/__manifest__.py +++ b/budget_management/__manifest__.py @@ -1,7 +1,7 @@ { "name": "Budget Management", "category": "Accounting/Budgeting", - "depends": ["base", "analytic"], + "depends": ["base", "account"], "description": """ This module is for managing Budgets. """, @@ -9,6 +9,7 @@ "security/ir.model.access.csv", "views/budget_views.xml", "views/budget_lines_views.xml", + "wizard/bulk_budget_views.xml", "views/budget_menus.xml", ], "demo": [], diff --git a/budget_management/models/__init__.py b/budget_management/models/__init__.py index 80925dd24d..b540356b91 100644 --- a/budget_management/models/__init__.py +++ b/budget_management/models/__init__.py @@ -1 +1,3 @@ -from . import budget_budget, budget_lines +from . import budget_budget +from . import budget_line +from . import account_analytic_line diff --git a/budget_management/models/account_analytic_line.py b/budget_management/models/account_analytic_line.py new file mode 100644 index 0000000000..1a657d291f --- /dev/null +++ b/budget_management/models/account_analytic_line.py @@ -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" ) diff --git a/budget_management/models/budget_budget.py b/budget_management/models/budget_budget.py index 8db2c6786c..9c1b618232 100644 --- a/budget_management/models/budget_budget.py +++ b/budget_management/models/budget_budget.py @@ -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=[ @@ -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" @@ -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: %s" @@ -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 : " diff --git a/budget_management/models/budget_line.py b/budget_management/models/budget_line.py new file mode 100644 index 0000000000..a121257c5a --- /dev/null +++ b/budget_management/models/budget_line.py @@ -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 ({record.analytic_account.name})" + action["domain"] = [("budget_line_id", "=", record.id)] + action["context"] = { + "default_budget_line_id": record.id, + "default_account_id": record.analytic_account.id, + } + return action diff --git a/budget_management/models/budget_lines.py b/budget_management/models/budget_lines.py deleted file mode 100644 index c3747b0faf..0000000000 --- a/budget_management/models/budget_lines.py +++ /dev/null @@ -1,15 +0,0 @@ -from odoo import api, fields, models - - -class BudgetBudget(models.Model): - _name = "budget.line" - _description = "Budget Lines" - - analytic_account = fields.Many2one("account.analytic.account") - budget_amount = fields.Integer("Budget Amount", required=True) - achieved_amount = fields.Integer("Achieved Amount") - budget_id = fields.Many2one("budget.budget") - progress = fields.Float("Achieved (%)") - - def related_account_analytic_lines(): - return 1 diff --git a/budget_management/security/ir.model.access.csv b/budget_management/security/ir.model.access.csv index b87d99cd08..1eca834799 100644 --- a/budget_management/security/ir.model.access.csv +++ b/budget_management/security/ir.model.access.csv @@ -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 \ No newline at end of file +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 diff --git a/budget_management/views/budget_lines_views.xml b/budget_management/views/budget_lines_views.xml index 0f745c670d..e85dbf1f08 100644 --- a/budget_management/views/budget_lines_views.xml +++ b/budget_management/views/budget_lines_views.xml @@ -4,7 +4,7 @@ Budget Lines budget.line - list + list,graph,pivot,gantt [('budget_id', '=', active_id)] @@ -13,13 +13,49 @@ budget.line + -