Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,220 @@
# Part of Fusion Accounting. See LICENSE file for full copyright and licensing details.
from odoo import api, Command, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools import date_utils, float_is_zero, float_round
class FusionBudget(models.Model):
"""Represents a financial budget linked to accounting reports.
A budget groups together individual budget line items, each targeting
a specific account and month. Budgets are company-specific and appear
as additional columns in accounting reports.
"""
_name = 'account.report.budget'
_description = "Fusion Report Budget"
_order = 'sequence, id'
name = fields.Char(
string="Budget Name",
required=True,
)
sequence = fields.Integer(
string="Display Order",
default=10,
)
company_id = fields.Many2one(
comodel_name='res.company',
string="Company",
required=True,
default=lambda self: self.env.company,
)
item_ids = fields.One2many(
comodel_name='account.report.budget.item',
inverse_name='budget_id',
string="Budget Lines",
)
# --------------------------------------------------
# CRUD
# --------------------------------------------------
@api.model_create_multi
def create(self, vals_list):
"""Override create to sanitize the budget name by stripping whitespace."""
for record_vals in vals_list:
raw_name = record_vals.get('name')
if raw_name:
record_vals['name'] = raw_name.strip()
return super().create(vals_list)
# --------------------------------------------------
# Constraints
# --------------------------------------------------
@api.constrains('name')
def _check_budget_name_not_empty(self):
"""Ensure every budget record has a non-empty name."""
for record in self:
if not record.name or not record.name.strip():
raise ValidationError(
_("A budget must have a non-empty name.")
)
# --------------------------------------------------
# Duplication helpers
# --------------------------------------------------
def copy_data(self, default=None):
"""Append '(copy)' suffix to duplicated budget names."""
data_list = super().copy_data(default=default)
result = []
for budget, vals in zip(self, data_list):
vals['name'] = _("%s (copy)", budget.name)
result.append(vals)
return result
def copy(self, default=None):
"""Duplicate budgets together with their line items."""
duplicated_budgets = super().copy(default)
for source_budget, target_budget in zip(self, duplicated_budgets):
for line in source_budget.item_ids:
line.copy({
'budget_id': target_budget.id,
'account_id': line.account_id.id,
'amount': line.amount,
'date': line.date,
})
return duplicated_budgets
# --------------------------------------------------
# Budget item management (called from report engine)
# --------------------------------------------------
def _create_or_update_budget_items(
self, value_to_set, account_id, rounding, date_from, date_to
):
"""Distribute a target amount across monthly budget items.
When the user edits a budget cell in the report view, this method
calculates the difference between the desired total and the existing
total for the given account/date range, then distributes that delta
evenly across the months in the range.
Existing items within the range are updated in place; new items are
created for months that don't have one yet.
Args:
value_to_set: The desired total amount for the date range.
account_id: The ``account.account`` record id.
rounding: Number of decimal digits for monetary precision.
date_from: Start date (inclusive) of the budget period.
date_to: End date (inclusive) of the budget period.
"""
self.ensure_one()
period_start = fields.Date.to_date(date_from)
period_end = fields.Date.to_date(date_to)
BudgetItem = self.env['account.report.budget.item']
# Fetch all items that already cover (part of) the requested range
matching_items = BudgetItem.search_fetch(
[
('budget_id', '=', self.id),
('account_id', '=', account_id),
('date', '>=', period_start),
('date', '<=', period_end),
],
['id', 'amount'],
)
current_total = sum(matching_items.mapped('amount'))
# Calculate the remaining amount to distribute
remaining_delta = value_to_set - current_total
if float_is_zero(remaining_delta, precision_digits=rounding):
return
# Build a list of first-of-month dates spanning the period
month_starts = [
date_utils.start_of(d, 'month')
for d in date_utils.date_range(period_start, period_end)
]
month_count = len(month_starts)
# Spread the delta equally across months (rounding down),
# then assign any leftover cents to the final month.
per_month = float_round(
remaining_delta / month_count,
precision_digits=rounding,
rounding_method='DOWN',
)
monthly_portions = [per_month] * month_count
distributed_sum = float_round(sum(monthly_portions), precision_digits=rounding)
monthly_portions[-1] += float_round(
remaining_delta - distributed_sum,
precision_digits=rounding,
)
# Pair existing items with months and amounts; create or update as needed
write_commands = []
idx = 0
for month_date, portion in zip(month_starts, monthly_portions):
if idx < len(matching_items):
# Update an existing item
existing = matching_items[idx]
write_commands.append(
Command.update(existing.id, {
'amount': existing.amount + portion,
})
)
else:
# No existing item for this slot create a new one
write_commands.append(
Command.create({
'account_id': account_id,
'amount': portion,
'date': month_date,
})
)
idx += 1
if write_commands:
self.item_ids = write_commands
# Ensure the ORM flushes new records to the database so
# subsequent queries within the same request see them.
BudgetItem.flush_model()
class FusionBudgetItem(models.Model):
"""A single monthly budget entry for one account within a budget.
Each item records a monetary amount allocated to a specific
``account.account`` for a particular month. The ``date`` field
stores the first day of the relevant month.
"""
_name = 'account.report.budget.item'
_description = "Fusion Report Budget Line"
budget_id = fields.Many2one(
comodel_name='account.report.budget',
string="Parent Budget",
required=True,
ondelete='cascade',
)
account_id = fields.Many2one(
comodel_name='account.account',
string="Account",
required=True,
)
date = fields.Date(
string="Month",
required=True,
)
amount = fields.Float(
string="Budgeted Amount",
default=0.0,
)