diff --git a/budget_management/__init__.py b/budget_management/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/budget_management/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/budget_management/__manifest__.py b/budget_management/__manifest__.py new file mode 100644 index 0000000000..1064063704 --- /dev/null +++ b/budget_management/__manifest__.py @@ -0,0 +1,23 @@ +{ + "name": "Budget Management", + "version": "1.0", + "depends": ["base", "account"], + "author": "djip-odoo", + "description": """ + Part of technical training + Creating Budget Management module as review task + """, + "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", + "wizards/wizard_add_budgets_view.xml", + ], + "application": True, + "installable": True, + "auto_install": False, + "license": "LGPL-3", +} diff --git a/budget_management/models/__init__.py b/budget_management/models/__init__.py new file mode 100644 index 0000000000..c79b006ead --- /dev/null +++ b/budget_management/models/__init__.py @@ -0,0 +1,2 @@ +from . import budget +from . import budget_line diff --git a/budget_management/models/budget.py b/budget_management/models/budget.py new file mode 100644 index 0000000000..ed5a28aaa0 --- /dev/null +++ b/budget_management/models/budget.py @@ -0,0 +1,73 @@ +from odoo import models, fields, api +from datetime import date + + +class Budget(models.Model): + _name = "budget.budget" + _inherit = ["mail.thread", "mail.activity.mixin"] + + name = fields.Char(compute="_compute_budget_name", store=True, readonly=True) + active = fields.Boolean(default=True) + is_favorite = fields.Boolean(default=False) + state = fields.Selection( + selection=[ + ("draft", "Draft"), + ("confirmed", "Confirmed"), + ("revised", "Revised"), + ("done", "Done"), + ], + required=True, + default="draft", + tracking=True, + ) + on_over_budget = fields.Selection( + selection=[("warning", "Warning"), ("restriction", "Restriction")], + tracking=True, + ) + responsible = fields.Many2one( + comodel_name="res.users", # Assuming you want a link to Odoo users + string="Responsible", + tracking=True, + ) + revision_id = fields.Many2one( + comodel_name="res.users", # Assuming you want a link to Odoo users + tracking=True, + readonly=True, + ) + date_start = fields.Date(string="Start Date", required=True) + date_end = fields.Date(string="Expiration Date", required=True, index=True) + company_id = fields.Many2one( + "res.company", + string="Company", + default=lambda self: self.env.company, + ) + + budget_line_ids = fields.One2many( + comodel_name="budget.management.budget.lines", inverse_name="budget_id" + ) + + @api.depends("date_start", "date_end") + 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}" + else: + record.name = "Unknown Budget" + + def onclick_reset_to_draft(self): + for record in self: + if record.state != "draft": + record.state = "draft" + + def onclick_revise(self): + for record in self: + if record.state in ["confirmed", "draft"]: + record.revision_id = lambda self: self.env.user + record.state = "revised" + + def onclick_done(self): + for record in self: + if record.state in ["confirmed", "revised"]: + record.state = "done" diff --git a/budget_management/models/budget_line.py b/budget_management/models/budget_line.py new file mode 100644 index 0000000000..010c9dc915 --- /dev/null +++ b/budget_management/models/budget_line.py @@ -0,0 +1,25 @@ +from odoo import models, fields, api + +class BudgetLine(models.Model): + _name = "budget.management.budget.lines" + + name = fields.Char() + budget_id = fields.Many2one(comodel_name="budget.budget", string="Budget") + budget_amount = fields.Float(default=0.0) + achieved_amount = fields.Float(default=0.0) + achieved_percentage = fields.Float( + default=0.0, + compute="_compute_achieved_percentage", + store=True + ) + analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account') + # analytic_line_ids = fields.One2many('account.analytic.line', string='Analytic Account') + + @api.depends("budget_amount", "achieved_amount") + def _compute_achieved_percentage(self): + for record in self: + if record.budget_amount: + record.achieved_percentage = (record.achieved_amount / record.budget_amount) * 100 + else: + record.achieved_percentage = 0.0 + diff --git a/budget_management/security/ir.model.access.csv b/budget_management/security/ir.model.access.csv new file mode 100644 index 0000000000..02a475aa8f --- /dev/null +++ b/budget_management/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +budget_management.access_budget_budget,access_budget_budget,budget_management.model_budget_budget,base.group_user,1,1,1,1 +budget_management.access_add_budget_wizard,access_add_budget_wizard,budget_management.model_add_budget_wizard,base.group_user,1,1,1,0 +budget_management.access_budget_management_budget_lines,access_budget_management_budget_lines,budget_management.model_budget_management_budget_lines,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/budget_management/views/actions_menu_and_button.xml b/budget_management/views/actions_menu_and_button.xml new file mode 100644 index 0000000000..45d14f248a --- /dev/null +++ b/budget_management/views/actions_menu_and_button.xml @@ -0,0 +1,24 @@ + + + Budgets + budget.budget + kanban,form + +

+ No records available. +

+

+ Please check your filters or create new records. +

+
+
+ + + Budgets Lines + budget.management.budget.lines + list + + {'default_budget_id': active_id} + [('budget_id', '=', context.get('default_budget_id'))] + +
\ No newline at end of file diff --git a/budget_management/views/budget_line_views.xml b/budget_management/views/budget_line_views.xml new file mode 100644 index 0000000000..8e17fae3f2 --- /dev/null +++ b/budget_management/views/budget_line_views.xml @@ -0,0 +1,18 @@ + + + budget.line.tree + budget.management.budget.lines + + + + + + + + + + \ No newline at end of file diff --git a/budget_management/views/budget_views.xml b/budget_management/views/budget_views.xml new file mode 100644 index 0000000000..40e7ce4d97 --- /dev/null +++ b/budget_management/views/budget_views.xml @@ -0,0 +1,129 @@ + + + budget.budget.kanban + budget.budget + + + + +
+
+ +
+
+
+ +
+
+ +

+ +

+
+
+
+ + + + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ + + + budget.budget.form + budget.budget + +
+
+
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ +
\ No newline at end of file diff --git a/budget_management/views/menu_views.xml b/budget_management/views/menu_views.xml new file mode 100644 index 0000000000..e41acb882e --- /dev/null +++ b/budget_management/views/menu_views.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/budget_management/wizards/__init__.py b/budget_management/wizards/__init__.py new file mode 100644 index 0000000000..3fb6f401df --- /dev/null +++ b/budget_management/wizards/__init__.py @@ -0,0 +1 @@ +from . import add_budget_wizard diff --git a/budget_management/wizards/actions_wizard.xml b/budget_management/wizards/actions_wizard.xml new file mode 100644 index 0000000000..056847fd26 --- /dev/null +++ b/budget_management/wizards/actions_wizard.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/budget_management/wizards/add_budget_wizard.py b/budget_management/wizards/add_budget_wizard.py new file mode 100644 index 0000000000..0db6b8612b --- /dev/null +++ b/budget_management/wizards/add_budget_wizard.py @@ -0,0 +1,53 @@ +from odoo import models, fields +from datetime import timedelta +from dateutil.relativedelta import relativedelta + + +class AddBudgetWizard(models.TransientModel): + _name = "add.budget.wizard" + + date_start = fields.Date(required=True, string="Start Date") + date_end = fields.Date( + string="Expiration Date", + required=True, + index=True, + ) + + periods = fields.Selection( + selection=[("monthly", "Monthly"), ("quarterly", "Quarterly")], + required=True, + default="monthly", + ) + + # analytic_account = fields.Many2many(comodel_name="account.analytic.account") + def action_add_budget(self): + """Creates budget records based on the selected periods.""" + if self.date_start >= self.date_end: + raise ValueError("Start Date must be before Expiration Date.") + + # Calculate the periods and create budgets + current_date = self.date_start + budget_entries = [] + + while current_date <= self.date_end: + next_date = ( + current_date + relativedelta(months=1) + if self.periods == "monthly" + else current_date + relativedelta(months=3) + ) + end_date = min(next_date - timedelta(days=1), self.date_end) + + budget_entries.append( + { + "name": f"Budget {current_date.strftime('%Y-%m')} to {end_date.strftime('%Y-%m')}", + "date_start": current_date, + "date_end": end_date, + } + ) + + current_date = next_date + print(budget_entries) + self.env["budget.budget"].create(budget_entries) + + # Return a window close action + return {"type": "ir.actions.act_window_close"} diff --git a/budget_management/wizards/wizard_add_budgets_view.xml b/budget_management/wizards/wizard_add_budgets_view.xml new file mode 100644 index 0000000000..1ceee9aac0 --- /dev/null +++ b/budget_management/wizards/wizard_add_budgets_view.xml @@ -0,0 +1,34 @@ + + + + + add.budget.wizard.form + add.budget.wizard + +
+ + + + + + + +
+
+
+
+
+ + Create Multiple Budgets + add.budget.wizard + form + + new + +
+
\ No newline at end of file