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 @@