flectra/addons/sale_timesheet/controllers/main.py

149 lines
7.4 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import http, _
from flectra.http import request
from flectra.osv import expression
from flectra.tools import float_round
class SaleTimesheetController(http.Controller):
@http.route('/timesheet/plan', type='json', auth="user")
def plan(self, domain):
domain = expression.AND([domain, [('project_id', '!=', False)]]) # force timesheet and not AAL
values = self._prepare_plan_values(domain)
view = request.env.ref('sale_timesheet.timesheet_plan')
return {
'html_content': view.render(values)
}
def _prepare_plan_values(self, domain):
timesheet_lines = request.env['account.analytic.line'].search(domain)
currency = request.env.user.company_id.currency_id
values = {
'currency': currency,
'timesheet_lines': timesheet_lines,
'domain': domain,
}
hour_rounding = request.env.ref('product.product_uom_hour').rounding
billable_types = ['non_billable', 'non_billable_project', 'billable_time', 'billable_fixed']
# -- Stat Buttons
values['stat_buttons'] = self._plan_get_stat_button(timesheet_lines)
# -- Dashboard (per billable type)
dashboard_values = {
'hours': dict.fromkeys(billable_types + ['total'], 0.0),
'rates': dict.fromkeys(billable_types + ['total'], 0.0),
'money_amount': {
'invoiced': 0.0,
'to_invoiced': 0.0,
'cost': 0.0,
'total': 0.0,
}
}
dashboard_domain = domain + [('timesheet_invoice_type', '!=', False)] # force billable type
dashboard_data = request.env['account.analytic.line'].read_group(dashboard_domain, ['unit_amount', 'timesheet_revenue', 'timesheet_invoice_type'], ['timesheet_invoice_type'])
dashboard_total_hours = sum([data['unit_amount'] for data in dashboard_data])
for data in dashboard_data:
billable_type = data['timesheet_invoice_type']
# hours
dashboard_values['hours'][billable_type] = float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
dashboard_values['hours']['total'] += float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
# rates
dashboard_values['rates'][billable_type] = dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
dashboard_values['rates']['total'] += dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
# money_amount
so_lines = values['timesheet_lines'].mapped('so_line')
invoice_lines = so_lines.mapped('invoice_lines')
dashboard_values['money_amount']['invoiced'] = sum([inv_line.currency_id.with_context(date=inv_line.invoice_id.date_invoice).compute(inv_line.price_unit * inv_line.quantity, currency) for inv_line in invoice_lines.filtered(lambda line: line.invoice_id.state in ['open', 'paid'])])
dashboard_values['money_amount']['to_invoice'] = sum([sol.currency_id.compute(sol.price_unit * (1 - (sol.discount or 0.0) / 100.0) * sol.qty_to_invoice, currency) for sol in so_lines]) + sum([i.currency_id.with_context(date=i.invoice_id.date_invoice).compute(i.price_unit * i.quantity, currency) for i in invoice_lines.filtered(lambda line: line.invoice_id.state == 'draft')])
dashboard_values['money_amount']['cost'] = sum(values['timesheet_lines'].mapped('amount'))
dashboard_values['money_amount']['total'] = sum([dashboard_values['money_amount'][item] for item in dashboard_values['money_amount'].keys()])
values['dashboard'] = dashboard_values
# -- Time Repartition (per employee)
repartition_domain = domain + [('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
repartition_data = request.env['account.analytic.line'].read_group(repartition_domain, ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
# set repartition per type per employee
repartition_employee = {}
for data in repartition_data:
employee_id = data['employee_id'][0]
repartition_employee.setdefault(employee_id, dict(
employee_id=data['employee_id'][0],
employee_name=data['employee_id'][1],
non_billable_project=0.0,
non_billable=0.0,
billable_time=0.0,
billable_fixed=0.0,
total=0.0,
))[data['timesheet_invoice_type']] = float_round(data.get('unit_amount', 0.0), precision_rounding=hour_rounding)
repartition_employee[employee_id]['__domain_'+data['timesheet_invoice_type']] = data['__domain']
# compute total
for employee_id, vals in repartition_employee.items():
repartition_employee[employee_id]['total'] = sum([vals[inv_type] for inv_type in billable_types])
hours_per_employee = [repartition_employee[employee_id]['total'] for employee_id in repartition_employee]
values['repartition_employee_max'] = max(hours_per_employee) if hours_per_employee else 1
values['repartition_employee'] = repartition_employee
return values
def _plan_get_stat_button(self, timesheet_lines):
stat_buttons = []
stat_buttons.append({
'name': _('Timesheets'),
'res_model': 'account.analytic.line',
'domain': [('id', 'in', timesheet_lines.ids)],
'icon': 'fa fa-calendar',
})
stat_project_ids = timesheet_lines.mapped('project_id').ids
stat_task_domain = [('project_id', 'in', stat_project_ids), '|', ('stage_id', '=', False), ('stage_id.fold', '=', False)]
stat_buttons.append({
'name': _('Tasks'),
'count': request.env['project.task'].search_count(stat_task_domain),
'res_model': 'project.task',
'domain': stat_task_domain,
'icon': 'fa fa-tasks',
})
return stat_buttons
@http.route('/timesheet/plan/action', type='json', auth="user")
def plan_stat_button(self, domain, res_model='account.analytic.line'):
action = {
'type': 'ir.actions.act_window',
'view_id': False,
'view_mode': 'tree,form',
'view_type': 'list',
'domain': domain,
}
if res_model == 'account.analytic.line':
ts_view_tree_id = request.env.ref('hr_timesheet.hr_timesheet_line_tree').id
ts_view_form_id = request.env.ref('hr_timesheet.hr_timesheet_line_form').id
action = {
'name': _('Timesheets'),
'type': 'ir.actions.act_window',
'res_model': res_model,
'view_mode': 'tree,form',
'view_type': 'tree',
'views': [[ts_view_tree_id, 'list'], [ts_view_form_id, 'form']],
'domain': domain,
}
elif res_model == 'project.task':
action = request.env.ref('project.action_view_task').read()[0]
action.update({
'name': _('Tasks'),
'domain': domain,
'context': request.env.context, # erase original context to avoid default filter
})
return action