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