# -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. from flectra import api, fields, models, _ from flectra.tools.float_utils import float_round, float_is_zero class AccountInvoice(models.Model): _inherit = "account.invoice" timesheet_ids = fields.One2many('account.analytic.line', 'timesheet_invoice_id', string='Timesheets', readonly=True, copy=False) timesheet_count = fields.Integer("Number of timesheets", compute='_compute_timesheet_count') @api.multi @api.depends('timesheet_ids') def _compute_timesheet_count(self): timesheet_data = self.env['account.analytic.line'].read_group([('timesheet_invoice_id', 'in', self.ids)], ['timesheet_invoice_id'], ['timesheet_invoice_id']) mapped_data = dict([(t['timesheet_invoice_id'][0], t['timesheet_invoice_id_count']) for t in timesheet_data]) for invoice in self: invoice.timesheet_count = mapped_data.get(invoice.id, 0) def action_view_timesheet(self): self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': _('Timesheets'), 'domain': [('project_id', '!=', False)], 'res_model': 'account.analytic.line', 'view_id': False, 'view_mode': 'tree,form', 'view_type': 'form', 'help': _("""
Click to record timesheets.
You can register and track your workings hours by project every day. Every time spent on a project will become a cost and can be re-invoiced to customers if required.
"""), 'limit': 80, 'context': { 'default_project_id': self.id, 'search_default_project_id': [self.id] } } @api.multi def invoice_validate(self): result = super(AccountInvoice, self).invoice_validate() self._compute_timesheet_revenue() return result def _compute_timesheet_revenue(self): for invoice in self: for invoice_line in invoice.invoice_line_ids.filtered(lambda line: line.product_id.type == 'service').sorted(key=lambda inv_line: (inv_line.invoice_id, inv_line.id)): uninvoiced_timesheet_lines = self.env['account.analytic.line'].sudo().search([ ('so_line', 'in', invoice_line.sale_line_ids.ids), ('project_id', '!=', False), ('timesheet_invoice_id', '=', False), ('timesheet_invoice_type', 'in', ['billable_time', 'billable_fixed']) ]) # NOTE JEM : changing quantity (or unit price) of invoice line does not impact the revenue calculation. (FP specs) if uninvoiced_timesheet_lines: # delivered : update revenue with the prorata of number of hours on the timesheet line if invoice_line.product_id.invoice_policy == 'delivery': invoiced_price_per_hour = invoice_line.currency_id.round(invoice_line.price_subtotal / float(sum(uninvoiced_timesheet_lines.mapped('unit_amount')))) # invoicing analytic lines of different currency total_revenue_per_currency = dict.fromkeys(uninvoiced_timesheet_lines.mapped('company_currency_id').ids, 0.0) for index, timesheet_line in enumerate(uninvoiced_timesheet_lines.sorted(key=lambda ts: (ts.date, ts.id))): if index+1 != len(uninvoiced_timesheet_lines): line_revenue = invoice_line.currency_id.compute(invoiced_price_per_hour, timesheet_line.company_currency_id) * timesheet_line.unit_amount total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue else: # last line: add the difference to avoid rounding problem total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()]) line_revenue = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id) - total_revenue timesheet_line.write({ 'timesheet_invoice_id': invoice.id, 'timesheet_revenue': timesheet_line.company_currency_id.round(line_revenue), }) # ordered : update revenue with the prorata of theorical revenue elif invoice_line.product_id.invoice_policy == 'order': zero_timesheet_revenue = uninvoiced_timesheet_lines.filtered(lambda line: line.timesheet_revenue == 0.0) no_zero_timesheet_revenue = uninvoiced_timesheet_lines.filtered(lambda line: line.timesheet_revenue != 0.0) # timesheet with zero theorical revenue keep the same revenue, but become invoiced (invoice_id set) zero_timesheet_revenue.write({'timesheet_invoice_id': invoice.id}) # invoicing analytic lines of different currency total_revenue_per_currency = dict.fromkeys(no_zero_timesheet_revenue.mapped('company_currency_id').ids, 0.0) for index, timesheet_line in enumerate(no_zero_timesheet_revenue.sorted(key=lambda ts: (ts.date, ts.id))): if index+1 != len(no_zero_timesheet_revenue): price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id) price_subtotal_sol = timesheet_line.so_line.currency_id.compute(timesheet_line.so_line.price_subtotal, timesheet_line.company_currency_id) if not float_is_zero(price_subtotal_sol, precision_rounding=timesheet_line.company_currency_id.rounding): line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue else: line_revenue = timesheet_line.timesheet_revenue total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue else: # last line: add the difference to avoid rounding problem last_price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id) total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()]) line_revenue = last_price_subtotal_inv - total_revenue timesheet_line.write({ 'timesheet_invoice_id': invoice.id, 'timesheet_revenue': timesheet_line.company_currency_id.round(line_revenue), })