From 173f7c2910eb82e081c3f312843dbcd7622ec009 Mon Sep 17 00:00:00 2001 From: dhruv Date: Thu, 12 Dec 2024 17:01:05 +0530 Subject: [PATCH] [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. --- budget_management/__manifest__.py | 5 +- .../models/account_analytic_line.py | 91 ++++++++++++------ budget_management/models/budget.py | 72 +++++++++++--- budget_management/models/budget_line.py | 95 ++++++++++++------- .../views/account_analytic_line_view.xml | 16 ---- .../views/actions_menu_and_button.xml | 10 +- budget_management/views/budget_line_views.xml | 35 ++++--- budget_management/views/budget_views.xml | 12 ++- budget_management/wizards/actions_wizard.xml | 3 - .../wizards/add_budget_wizard.py | 29 ++++-- 10 files changed, 238 insertions(+), 130 deletions(-) delete mode 100644 budget_management/views/account_analytic_line_view.xml delete mode 100644 budget_management/wizards/actions_wizard.xml diff --git a/budget_management/__manifest__.py b/budget_management/__manifest__.py index f3dbd3571a..611d9557d2 100644 --- a/budget_management/__manifest__.py +++ b/budget_management/__manifest__.py @@ -1,7 +1,8 @@ { "name": "Budget Management", + "category": "Budget", "version": "1.0", - "depends": ["base", "account"], + "depends": ["base", "account", "accountant"], "author": "djip-odoo", "description": """ Part of technical training @@ -10,11 +11,9 @@ "data": [ "security/ir.model.access.csv", "views/budget_line_views.xml", - "wizards/actions_wizard.xml", "views/actions_menu_and_button.xml", "views/menu_views.xml", "views/budget_views.xml", - 'views/account_analytic_line_view.xml', "wizards/wizard_add_budgets_view.xml", ], "application": True, diff --git a/budget_management/models/account_analytic_line.py b/budget_management/models/account_analytic_line.py index c0dcc7c479..57fb99c146 100644 --- a/budget_management/models/account_analytic_line.py +++ b/budget_management/models/account_analytic_line.py @@ -4,42 +4,79 @@ class AccountAnalyticLine(models.Model): _inherit = "account.analytic.line" - - budget_line_id = fields.Many2one(comodel_name="budget.management.budget.lines") - + @api.model_create_multi def create(self, vals_list): # print("* " * 100) # print(vals_list) # print("* " * 100) + for vals in vals_list: - budget_line = self.env["budget.management.budget.lines"].browse( - vals.get("budget_line_id") - ) - budget = budget_line.budget_id - if budget.on_over_budget == "restriction": - if sum(budget_line.analytic_line_ids.mapped("amount"))+vals.get("amount") > budget_line.budget_amount: - raise ValidationError( - "You cannot create a budget line because it exceeds the allowed budget!" - ) + entry_date = vals.get("date") + if not entry_date: + raise ValidationError("The date field is required to determine the appropriate budget.") + + budget_line = self.env["budget.management.budget.lines"].search([ + ("budget_id.date_start", "<=", entry_date), + ("budget_id.date_end", ">=", entry_date), + ("budget_id.active", "=", True), + ("analytic_account_id", "=", vals.get("account_id")) + ], limit=1) + + if budget_line: + budget = budget_line.budget_id + + analytic_account_lines = self.env["account.analytic.line"].search_read( + [ + ("account_id", "=", vals.get("account_id")), + ("date", ">=", budget.date_start), + ("date", "<=", budget.date_end), + ("amount", "<", 0), + ], + fields=["amount"], + ) + # print(list(line.get("amount") for line in analytic_account_lines)) + achieved = (sum(line.get("amount") for line in analytic_account_lines)) + # print(budget.on_over_budget, abs(achieved + vals.get("amount")), budget_line.budget_amount) + + if budget.on_over_budget == "restriction": + if abs(achieved + vals.get("amount")) > budget_line.budget_amount: + raise ValidationError( + "You cannot create a budget line because it exceeds the allowed budget!" + ) return super(AccountAnalyticLine, self).create(vals_list) def write(self, vals): - # print("* " * 100) - # print(vals) - # print("* " * 100) - if "amount" in vals: + if "date" in vals or "amount" in vals or "account_id" in vals: for record in self: - old_amount = record.amount - new_amount = vals.get("amount") - print(old_amount,new_amount) - total_amount = sum(record.budget_line_id.analytic_line_ids.mapped("amount")) + new_amount - old_amount + entry_date = vals.get("date", record.date) + + budget_line = self.env["budget.management.budget.lines"].search([ + ("budget_id.date_start", "<=", entry_date), + ("budget_id.date_end", ">=", entry_date), + ("budget_id.active", "=", True), + ("analytic_account_id", "=", vals.get("account_id", record.account_id.id)) + ], limit=1) + + if budget_line: + budget = budget_line.budget_id + + analytic_account_lines = self.env["account.analytic.line"].search_read( + [ + ("account_id", "=", vals.get("account_id", record.account_id.id)), + ("date", ">=", budget.date_start), + ("date", "<=", budget.date_end), + ("amount", "<", 0), + ], + fields=["amount"], + ) + achieved = (sum(line.get("amount") for line in analytic_account_lines)) + + new_amount = vals.get("amount", record.amount) + if budget.on_over_budget == "restriction": + if abs(achieved - record.amount + new_amount) > budget_line.budget_amount: + raise ValidationError( + "You cannot modify the budget line because it exceeds the allowed budget!" + ) - budget_line = record.budget_line_id - budget = budget_line.budget_id - if budget.on_over_budget == "restriction" and total_amount > budget_line.budget_amount: - raise ValidationError( - "You cannot update this budget line because it exceeds the allowed budget!" - ) - return super(AccountAnalyticLine, self).write(vals) diff --git a/budget_management/models/budget.py b/budget_management/models/budget.py index 82de9c443d..cd8e8db07c 100644 --- a/budget_management/models/budget.py +++ b/budget_management/models/budget.py @@ -1,5 +1,6 @@ from odoo import models, fields, api from odoo.exceptions import ValidationError, UserError +from markupsafe import escape, Markup class Budget(models.Model): @@ -35,6 +36,7 @@ class Budget(models.Model): comodel_name="res.users", # Assuming you want a link to Odoo users tracking=True, readonly=True, + string="Revised by" ) date_start = fields.Date(string="Start Date", required=True) date_end = fields.Date(string="Expiration Date", required=True, index=True) @@ -46,7 +48,7 @@ class Budget(models.Model): budget_line_ids = fields.One2many( comodel_name="budget.management.budget.lines", inverse_name="budget_id" ) - warnings = fields.Text(readonly=True) + warnings = fields.Text(compute="_check_over_budget") currency_id = fields.Many2one( comodel_name="res.currency", string="Currency", @@ -58,9 +60,15 @@ class Budget(models.Model): def _compute_budget_name(self): for record in self: if record.date_start and record.date_end: - start_date = record.date_start.strftime("%Y-%m") - end_date = record.date_end.strftime("%Y-%m") - record.name = f"Budget {start_date} to {end_date}" + start_date = record.date_start + end_date = record.date_end + if ( + start_date.year == end_date.year + and start_date.month == end_date.month + ): + record.name = f"Budget - {start_date.strftime('%B %Y')}" + else: + record.name = f"Budget - {start_date.strftime('%d %B, %Y')} to {end_date.strftime('%d %B, %Y')}" else: record.name = "Unknown Budget" @@ -78,13 +86,37 @@ def onclick_revise(self): for record in self: if record.state != "confirmed": raise UserError("Only confirmed budgets can be revised.") - if record.state in ["confirmed"]: + + if record.state == "confirmed": record.revision_id = self.env.user record.state = "revised" - # new_budget = record.copy({"revision_id": self.id, "state": "draft"}) - # record.message_post( - # body=f'Revised into {new_budget.name}.' - # ) + record.active = False + + new_budget = record.copy( + {"revision_id": None, "state": "draft", "active": True} + ) + + for budget_line in record.budget_line_ids: + self.env["budget.management.budget.lines"].create( + { + "budget_id": new_budget.id, + "name": budget_line.name, + "budget_amount": budget_line.budget_amount, + "achieved_amount": budget_line.achieved_amount, + "achieved_percentage": budget_line.achieved_percentage, + "analytic_account_id": budget_line.analytic_account_id.id, + "currency_id": budget_line.currency_id.id, + } + ) + + action = self.env.ref( + "budget_management.action_budget_management_menu_budget" + ) + record.message_post( + body=Markup( + f'{new_budget.name}.' + ) + ) def onclick_done(self): for record in self: @@ -102,7 +134,25 @@ def _check_period_overlap(self): ("company_id", "=", record.company_id.id), ] ) - if overlapping_budgets: + overlapping_non_revised = False + + for budget in overlapping_budgets: + if budget.state != "revised": + overlapping_non_revised = True + break + + if overlapping_non_revised: raise ValidationError( - "Cannot create overlapping budgets for the same period and company." + "Cannot create overlapping budgets for the same period and company. If not displayed your selected period budget! please check archived budgets" ) + + @api.depends("budget_line_ids.over_budget") + def _check_over_budget(self): + for record in self: + if ( + record.on_over_budget == "warning" + and any(ob > 0 for ob in record.budget_line_ids.mapped("over_budget")) > 0 + ): + record.warnings = "Achieved amount exceeds the budget!" + else: + record.warnings = False diff --git a/budget_management/models/budget_line.py b/budget_management/models/budget_line.py index 99b3356000..8047c360c8 100644 --- a/budget_management/models/budget_line.py +++ b/budget_management/models/budget_line.py @@ -10,7 +10,7 @@ class BudgetLine(models.Model): budget_id = fields.Many2one( comodel_name="budget.budget", string="Budget", required=True ) - state = fields.Selection(related="budget_id.state") + state = fields.Selection(related="budget_id.state", readonly=True) budget_amount = fields.Monetary( string="Budget Amount", default=0.0, @@ -19,30 +19,21 @@ class BudgetLine(models.Model): ) achieved_amount = fields.Monetary( string="Achieved Amount", - default=0.0, compute="_compute_achieved_amount", - store=True, currency_field="currency_id", ) achieved_percentage = fields.Float( string="Achieved (%)", compute="_compute_achieved_amount", - store=True, readonly=True, help="Percentage of the budget achieved based on analytic lines.", ) analytic_account_id = fields.Many2one( "account.analytic.account", string="Analytic Account", required=True ) - analytic_line_ids = fields.One2many( - comodel_name="account.analytic.line", - inverse_name="budget_line_id", - string="Analytic Lines", - ) over_budget = fields.Monetary( string="Over Budget", compute="_compute_achieved_amount", - store=True, help="The amount by which the budget line exceeds its allocated budget.", currency_field="currency_id", ) @@ -53,10 +44,27 @@ class BudgetLine(models.Model): readonly=True, ) - @api.depends("analytic_line_ids.amount") + @api.depends("budget_amount") def _compute_achieved_amount(self): for record in self: - record.achieved_amount = sum(record.analytic_line_ids.mapped("amount")) + if not record.analytic_account_id: + record.achieved_amount = 0.0 + record.achieved_percentage = 0.0 + record.over_budget = 0.0 + continue + analytic_account_lines = self.env["account.analytic.line"].search_read( + [ + ("auto_account_id", "=", record.analytic_account_id.id), + ("date", ">=", record.budget_id.date_start), + ("date", "<=", record.budget_id.date_end), + ("amount", "<", 0), + ], + fields=["amount"], + ) + + achieved = sum(line.get("amount") for line in analytic_account_lines) + + record.achieved_amount = abs(achieved) record.achieved_percentage = ( (record.achieved_amount / record.budget_amount) * 100 if record.budget_amount > 0 @@ -64,13 +72,13 @@ def _compute_achieved_amount(self): ) record.over_budget = max(0.0, record.achieved_amount - record.budget_amount) - if ( - record.budget_id.on_over_budget == "warning" - and record.achieved_amount > record.budget_amount - ): - record.budget_id.warnings = "Achived amount is more than your budget!" - else: - record.budget_id.warnings = False + # if ( + # record.budget_id.on_over_budget == "warning" + # and any(record.over_budget for record in self) > 0 + # ): + # record.budget_id.warnings = "Achieved amount exceeds the budget!" + # else: + # record.budget_id.warnings = False @api.constrains("budget_amount") def _check_budget_amount(self): @@ -78,25 +86,48 @@ def _check_budget_amount(self): if record.budget_amount < 0: raise ValidationError("Budget amount cannot be negative.") - @api.model_create_multi def create(self, vals_list): active_budget = None if self.env.context.get("active_id"): - active_budget = self.env["budget.budget"].browse(self.env.context.get("active_id")) - if active_budget.state != "draft": - raise UserError("Budget lines can only be created when the state is 'draft'.") + active_budget = self.env["budget.budget"].browse( + self.env.context["active_id"] + ) else: for vals in vals_list: - budget_id = vals.get("budget_id") - if budget_id: - active_budget = self.env["budget.budget"].browse(budget_id) + if vals.get("budget_id"): + active_budget = self.env["budget.budget"].browse(vals["budget_id"]) break - - if not active_budget: - raise UserError("No budget found in context or record.") - if active_budget.state != "draft": - raise UserError("Budget lines can only be created when the state is 'draft'.") + if not active_budget or active_budget.state != "draft": + raise UserError( + "Budget lines can only be created when the budget is in 'draft' state." + ) + + return super(BudgetLine, self).create(vals_list) + + def open_analytic_lines_action(self): + if not self.budget_id: + raise UserError("No budget linked to this budget line.") + + budget_start_date = self.budget_id.date_start + budget_end_date = self.budget_id.date_end - return super(BudgetLine, self).create(vals_list) \ No newline at end of file + return { + "type": "ir.actions.act_window", + "name": "Analytic Lines", + "res_model": "account.analytic.line", + "view_mode": "list", + "target": "current", + "context": { + "default_account_id": self.analytic_account_id.id, + "budget_start_date": budget_start_date, + "budget_end_date": budget_end_date, + }, + "domain": [ + ("account_id", "=", self.analytic_account_id.id), + ("date", ">=", budget_start_date), + ("date", "<=", budget_end_date), + ("amount", "<", 0), + ], + } diff --git a/budget_management/views/account_analytic_line_view.xml b/budget_management/views/account_analytic_line_view.xml deleted file mode 100644 index fe40f473ba..0000000000 --- a/budget_management/views/account_analytic_line_view.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - account.analytic.line.list - account.analytic.line - - - - - bottom - - - - - - - diff --git a/budget_management/views/actions_menu_and_button.xml b/budget_management/views/actions_menu_and_button.xml index 33d50110ac..60375fbc6b 100644 --- a/budget_management/views/actions_menu_and_button.xml +++ b/budget_management/views/actions_menu_and_button.xml @@ -2,7 +2,7 @@ Budgets budget.budget - kanban,form,list + kanban,form

No records available. @@ -22,12 +22,4 @@ [('budget_id', '=', context.get('default_budget_id'))] - - - Analytic Lines - account.analytic.line - list - {'default_budget_line_id': active_id} - [('budget_line_id', '=', context.get('default_budget_line_id'))] - diff --git a/budget_management/views/budget_line_views.xml b/budget_management/views/budget_line_views.xml index 793cfc3f16..65af754809 100644 --- a/budget_management/views/budget_line_views.xml +++ b/budget_management/views/budget_line_views.xml @@ -6,11 +6,10 @@ - @@ -18,18 +17,6 @@ - - budget.management.budget.lines.graph - budget.management.budget.lines - - - - - - - - - budget.management.budget.lines.pivot @@ -57,4 +44,22 @@ --> - \ No newline at end of file + + + + diff --git a/budget_management/views/budget_views.xml b/budget_management/views/budget_views.xml index ea89e50a79..06573ae9fc 100644 --- a/budget_management/views/budget_views.xml +++ b/budget_management/views/budget_views.xml @@ -79,7 +79,7 @@