175 lines
8.1 KiB
Python
175 lines
8.1 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from collections import defaultdict
|
|
import json
|
|
|
|
from odoo import fields, models, _
|
|
from odoo.fields import Domain
|
|
|
|
|
|
class ProjectProject(models.Model):
|
|
_inherit = "project.project"
|
|
|
|
total_budget_amount = fields.Monetary('Total planned amount', compute='_compute_budget', default=0, export_string_translation=False)
|
|
total_budget_progress = fields.Float("Budget Spent", compute="_compute_budget", export_string_translation=False)
|
|
|
|
def _get_budget_analytic_account_domain(self):
|
|
return Domain.OR([
|
|
[(account.plan_id._column_name(), '=', account.id)] for account in self.account_id
|
|
])
|
|
|
|
def _compute_budget(self):
|
|
budget_items = self.env['budget.line'].sudo()._read_group(
|
|
Domain.AND([
|
|
self._get_budget_analytic_account_domain(),
|
|
[('budget_analytic_id.state', 'in', ['confirmed', 'done'])],
|
|
]),
|
|
groupby=['account_id', 'budget_analytic_id'],
|
|
aggregates=['budget_amount:sum', 'achieved_amount:sum'],
|
|
)
|
|
budget_items_by_account_analytic = defaultdict(lambda: {
|
|
'budget_amount': 0,
|
|
'budget_amount_for_progress': 0,
|
|
'achieved_amount_for_progress': 0,
|
|
})
|
|
for analytic_account, budget_analytic_id, budget_amount_sum, achieved_amount_sum in budget_items:
|
|
type_factor = -1 if budget_analytic_id.budget_type == 'expense' else 1
|
|
budget_items_by_account_analytic[analytic_account.id]["budget_amount"] += budget_amount_sum
|
|
budget_items_by_account_analytic[analytic_account.id]["budget_amount_for_progress"] += budget_amount_sum * type_factor
|
|
budget_items_by_account_analytic[analytic_account.id]["achieved_amount_for_progress"] += achieved_amount_sum * type_factor
|
|
|
|
for project in self:
|
|
total_budget_amount = budget_items_by_account_analytic[project.account_id.id]['budget_amount']
|
|
total_budget_amount_fp = budget_items_by_account_analytic[project.account_id.id]['budget_amount_for_progress']
|
|
total_achieved_amount_fp = budget_items_by_account_analytic[project.account_id.id]['achieved_amount_for_progress']
|
|
project.total_budget_progress = total_budget_amount_fp and (total_achieved_amount_fp - total_budget_amount_fp) / abs(total_budget_amount_fp)
|
|
project.total_budget_amount = total_budget_amount
|
|
|
|
def action_view_budget_lines(self, domain=None):
|
|
self.ensure_one()
|
|
budget_lines = self.env['budget.line'].search(Domain.AND([
|
|
[(self.account_id.plan_id._column_name(), '=', self.account_id.id), ('budget_analytic_id.state', 'in', ['confirmed', 'done'])],
|
|
domain or [],
|
|
]))
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "budget.analytic",
|
|
"res_id": budget_lines.budget_analytic_id.id,
|
|
'context': {'create': False},
|
|
"name": _("Budget Items"),
|
|
'view_mode': 'form',
|
|
}
|
|
|
|
# ----------------------------
|
|
# Project Updates
|
|
# ----------------------------
|
|
|
|
def get_panel_data(self):
|
|
panel_data = super().get_panel_data()
|
|
panel_data['account_id'] = self.account_id.id
|
|
panel_data['budget_items'] = self._get_budget_items()
|
|
panel_data['show_budget_items'] = bool(self.account_id)
|
|
return panel_data
|
|
|
|
def get_budget_items(self):
|
|
self.ensure_one()
|
|
if self.account_id and self.env.user.has_group('project.group_project_user'):
|
|
return self._get_budget_items(True)
|
|
return {}
|
|
|
|
def _get_budget_items_domain(self):
|
|
self.ensure_one()
|
|
return [
|
|
(self.account_id.plan_id._column_name(), '=', self.account_id.id),
|
|
('budget_analytic_id', '!=', False),
|
|
('budget_analytic_id.state', 'in', ['confirmed', 'done']),
|
|
]
|
|
|
|
def _get_budget_items(self, with_action=True):
|
|
self.ensure_one()
|
|
if not self.account_id:
|
|
return
|
|
budget_items_domain = self._get_budget_items_domain()
|
|
budget_lines = self.env['budget.line'].sudo()._read_group(
|
|
budget_items_domain,
|
|
['budget_analytic_id', 'company_id'],
|
|
['budget_amount:sum', 'achieved_amount:sum', 'id:array_agg'],
|
|
)
|
|
has_company_access = False
|
|
for line in budget_lines:
|
|
if line[1].id in self.env.context.get('allowed_company_ids', []):
|
|
has_company_access = True
|
|
break
|
|
total_allocated = total_spent = 0.0
|
|
total_allocated_for_progress = total_spent_for_progress = 0.0
|
|
can_see_budget_items = with_action and has_company_access and (
|
|
self.env.user.has_group('account.group_account_readonly')
|
|
or self.env.user.has_group('analytic.group_analytic_accounting')
|
|
)
|
|
budget_data_per_budget = defaultdict(
|
|
lambda: {
|
|
'allocated': 0,
|
|
'spent': 0,
|
|
'budget_type': False,
|
|
**({
|
|
'ids': [],
|
|
'budgets': [],
|
|
} if can_see_budget_items else {})
|
|
}
|
|
)
|
|
|
|
for budget_analytic, _dummy, allocated, spent, ids in budget_lines:
|
|
budget_data = budget_data_per_budget[budget_analytic]
|
|
budget_data['id'] = budget_analytic.id
|
|
budget_data['name'] = budget_analytic.display_name
|
|
budget_data['allocated'] += allocated
|
|
budget_data['spent'] += spent
|
|
budget_data['budget_type'] = budget_analytic.budget_type
|
|
total_allocated += -allocated if budget_analytic.budget_type == 'expense' else allocated
|
|
total_spent += -spent if budget_analytic.budget_type == 'expense' else spent
|
|
total_allocated_for_progress += allocated * -1 if budget_analytic.budget_type == 'expense' else allocated
|
|
total_spent_for_progress += spent * -1 if budget_analytic.budget_type == 'expense' else spent
|
|
if can_see_budget_items:
|
|
budget_item = {
|
|
'id': budget_analytic.id,
|
|
'name': budget_analytic.display_name,
|
|
'allocated': allocated,
|
|
'spent': spent,
|
|
'budget_type': budget_analytic.budget_type,
|
|
'progress': allocated and (spent - allocated) / abs(allocated) * (-1 if budget_analytic.budget_type == 'expense' else 1),
|
|
}
|
|
budget_data['budgets'].append(budget_item)
|
|
budget_data['ids'] += ids
|
|
else:
|
|
budget_data['budgets'] = []
|
|
|
|
for budget_data in budget_data_per_budget.values():
|
|
budget_data['progress'] = budget_data['allocated'] and (budget_data['spent'] - budget_data['allocated']) / abs(budget_data['allocated']) \
|
|
* (-1 if budget_data['budget_type'] == 'expense' else 1)
|
|
|
|
budget_data_per_budget = list(budget_data_per_budget.values())
|
|
if can_see_budget_items:
|
|
for budget_data in budget_data_per_budget:
|
|
if len(budget_data['budgets']) == 1:
|
|
budget_data['budgets'].clear()
|
|
budget_data['action'] = {
|
|
'name': 'action_view_budget_lines',
|
|
'type': 'object',
|
|
'args': json.dumps([[('id', 'in', budget_data.pop('ids'))]]),
|
|
}
|
|
|
|
can_add_budget = with_action and self.env.user.has_group('account.group_account_user')
|
|
budget_items = {
|
|
'data': budget_data_per_budget,
|
|
'total': {
|
|
'allocated': total_allocated,
|
|
'spent': total_spent,
|
|
'progress': (total_spent_for_progress - total_allocated_for_progress) / abs(total_allocated_for_progress) if total_allocated_for_progress else 0,
|
|
},
|
|
'can_add_budget': can_add_budget,
|
|
}
|
|
if can_add_budget:
|
|
budget_items['form_view_id'] = self.env.ref('project_account_budget.view_budget_analytic_form_dialog').id
|
|
budget_items['company_id'] = self.company_id.id or self.env.company.id
|
|
return budget_items
|