Skip to content

Commit

Permalink
[IMP] budget_management: added analytic account and updated accordingly
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
djip-odoo committed Dec 12, 2024
1 parent abbcce9 commit 173f7c2
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 130 deletions.
5 changes: 2 additions & 3 deletions budget_management/__manifest__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand Down
91 changes: 64 additions & 27 deletions budget_management/models/account_analytic_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
72 changes: 61 additions & 11 deletions budget_management/models/budget.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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",
Expand All @@ -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"

Expand All @@ -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 <a href="#id={new_budget.id}&model=budget.budget">{new_budget.name}</a>.'
# )
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'<a href="odoo/action-{action.id}/{new_budget.id}">{new_budget.name}</a>.'
)
)

def onclick_done(self):
for record in self:
Expand All @@ -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
95 changes: 63 additions & 32 deletions budget_management/models/budget_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
)
Expand All @@ -53,50 +44,90 @@ 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
else 0.0
)
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):
for record in 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)
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),
],
}
Loading

0 comments on commit 173f7c2

Please sign in to comment.