mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-10-25 15:30:05 -05:00
[ADD]added project_billing_options module
This commit is contained in:
1 parent
a0283481a0
commit
1d9a8f3125
15 files changed
+619
No files matched your search
@@ -0,0 +1,4 @@
|
||||
# coding: utf-8
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,21 @@
|
||||
# coding: utf-8
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': 'Project Billing Options',
|
||||
'summary': 'Manage Timesheets and Calculate Profitability '
|
||||
'based on Invoicing Factor',
|
||||
'author': 'FlectraHQ',
|
||||
'depends': ['project', 'hr_timesheet', 'sale_timesheet', 'analytic'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/project_inherit.xml',
|
||||
'views/hr_timesheet_invoice_factor_view.xml',
|
||||
'menu/menu.xml'
|
||||
],
|
||||
'demo': [
|
||||
'demo/demodata_invoicable_factor.xml'
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<flectra>
|
||||
<data noupdate="1">
|
||||
<record id="demo_invoicable_factor1" model="hr_timesheet_invoice.factor">
|
||||
<field name="name">100% Invoicable</field>
|
||||
<field name="factor">100</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_invoicable_factor2" model="hr_timesheet_invoice.factor">
|
||||
<field name="name">50% Invoicable</field>
|
||||
<field name="factor">50</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_invoicable_factor3" model="hr_timesheet_invoice.factor">
|
||||
<field name="name">25% Invoicable</field>
|
||||
<field name="factor">25</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</flectra>
|
||||
@@ -0,0 +1,7 @@
|
||||
<flectra>
|
||||
<data>
|
||||
|
||||
<menuitem id="menu_invoicing_factor_sub" name="Invoicing Factor" parent="project.menu_project_config"
|
||||
action="action_hr_timesheet_invoice_factor" sequence="6"/>
|
||||
</data>
|
||||
</flectra>
|
||||
@@ -0,0 +1,5 @@
|
||||
# coding: utf-8
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_timesheet_invoice_factor
|
||||
from . import project
|
||||
@@ -0,0 +1,178 @@
|
||||
# coding: utf-8
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra import models, fields, api
|
||||
from flectra.tools import float_round
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
DEFAULT_MONTH_RANGE = 3
|
||||
|
||||
|
||||
class HrTimesheetInvoiceFactor(models.Model):
|
||||
_name = "hr_timesheet_invoice.factor"
|
||||
_description = "Invoice Rate"
|
||||
|
||||
name = fields.Char('Name', translate=True)
|
||||
factor = fields.Float(string='Invoicable Factor',
|
||||
required=True, help="Invoicable in percentage")
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
_description = "Account Analytic Line"
|
||||
|
||||
invoicable_id = fields.Many2one('hr_timesheet_invoice.factor',
|
||||
string="Invoicable(%)")
|
||||
calculated_hours_invoice = fields.Float(string="Calculated Hours",
|
||||
compute_sudo=True, store=True,
|
||||
compute="_compute_calculated_hours")
|
||||
|
||||
@api.depends('unit_amount', 'invoicable_id')
|
||||
def _compute_calculated_hours(self):
|
||||
for rec in self:
|
||||
if rec.invoicable_id.factor != 0.0:
|
||||
calculated_hours = (rec.unit_amount * rec.invoicable_id.factor / 100)
|
||||
rec.update({
|
||||
'calculated_hours_invoice': calculated_hours,
|
||||
})
|
||||
else:
|
||||
for a in rec:
|
||||
a.calculated_hours_invoice = a.unit_amount
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
_description = "Project Project"
|
||||
|
||||
def _plan_prepare_values(self):
|
||||
res = super(Project, self)._plan_prepare_values()
|
||||
uom_hour = self.env.ref('uom.product_uom_hour')
|
||||
hour_rounding = uom_hour.rounding
|
||||
company_uom = self.env.company.timesheet_encode_uom_id
|
||||
is_uom_day = company_uom == self.env.ref('uom.product_uom_day')
|
||||
billable_types = ['non_billable', 'non_billable_project',
|
||||
'billable_time', 'non_billable_timesheet',
|
||||
'billable_fixed']
|
||||
canceled_hours_domain = [('project_id', 'in', self.ids),
|
||||
('timesheet_invoice_type', '!=', False),
|
||||
('so_line.state', '=', 'cancel')]
|
||||
|
||||
dashboard_values = {
|
||||
'time': dict.fromkeys(billable_types + ['total'], 0.0),
|
||||
'rates': dict.fromkeys(billable_types + ['total'], 0.0),
|
||||
'profit': {
|
||||
'invoiced': 0.0,
|
||||
'to_invoice': 0.0,
|
||||
'cost': 0.0,
|
||||
'total': 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
total_canceled_hours = sum(
|
||||
self.env['account.analytic.line'].search(
|
||||
canceled_hours_domain).mapped('calculated_hours_invoice'))
|
||||
canceled_hours = float_round(total_canceled_hours,
|
||||
precision_rounding=hour_rounding)
|
||||
dashboard_values['time']['total'] += canceled_hours
|
||||
dashboard_values_total_time = dashboard_values['time']['total']
|
||||
res['dashboard']['time'].update({
|
||||
'canceled': canceled_hours,
|
||||
'total': dashboard_values_total_time
|
||||
})
|
||||
|
||||
dashboard_domain = [('project_id', 'in', self.ids),
|
||||
('timesheet_invoice_type', '!=', False),
|
||||
('calculated_hours_invoice', '!=', False), '|',
|
||||
('so_line', '=', False), ('so_line.state', '!=', 'cancel')]
|
||||
dashboard_data = self.env['account.analytic.line'].read_group(
|
||||
dashboard_domain, ['calculated_hours_invoice',
|
||||
'timesheet_invoice_type'],
|
||||
['timesheet_invoice_type'])
|
||||
|
||||
dashboard_total_hours = sum(
|
||||
[data['calculated_hours_invoice'] for
|
||||
data in dashboard_data]) + total_canceled_hours
|
||||
|
||||
for data in dashboard_data:
|
||||
billable_type = data['timesheet_invoice_type']
|
||||
amount = float_round(data.get('calculated_hours_invoice'),
|
||||
precision_rounding=hour_rounding)
|
||||
if is_uom_day:
|
||||
# convert time from hours to days
|
||||
amount = round(uom_hour._compute_quantity(amount, company_uom,
|
||||
raise_if_failure=False), 2)
|
||||
dashboard_values_total_time += amount
|
||||
res['dashboard']['time'].update({
|
||||
billable_type: amount,
|
||||
'total': dashboard_values_total_time
|
||||
})
|
||||
|
||||
rate = round(data.get(
|
||||
'calculated_hours_invoice') / dashboard_total_hours * 100, 2) \
|
||||
if dashboard_total_hours else 0.0
|
||||
dashboard_values['rates']['total'] += rate
|
||||
dashboard_values_rates_total = dashboard_values['rates']['total']
|
||||
res['dashboard']['rates'].update({
|
||||
billable_type: rate,
|
||||
'total': dashboard_values_rates_total
|
||||
})
|
||||
|
||||
repartition_domain = [('project_id', 'in', self.ids),
|
||||
('employee_id', '!=', False),
|
||||
('timesheet_invoice_type', '!=', False)]
|
||||
cancelled_so_timesheet = self.env['account.analytic.line'].read_group(
|
||||
repartition_domain + [('so_line.state', '=', 'cancel')],
|
||||
['employee_id', 'calculated_hours_invoice'],
|
||||
['employee_id'],
|
||||
lazy=False)
|
||||
repartition_data = self.env['account.analytic.line'].read_group(
|
||||
repartition_domain + ['|', ('so_line', '=', False),
|
||||
('so_line.state', '!=', 'cancel')],
|
||||
['employee_id', 'timesheet_invoice_type', 'calculated_hours_invoice'],
|
||||
['employee_id', 'timesheet_invoice_type'],
|
||||
lazy=False)
|
||||
repartition_data += [{**canceled, 'timesheet_invoice_type': 'canceled'}
|
||||
for canceled in cancelled_so_timesheet]
|
||||
emp_data = {}
|
||||
for data in repartition_data:
|
||||
data_timesheet_invoice_type = float_round(
|
||||
data.get('calculated_hours_invoice', 0.0),
|
||||
precision_rounding=hour_rounding)
|
||||
emp_data[int(data.get('employee_id')[0])] = {
|
||||
'type': data.get('timesheet_invoice_type'),
|
||||
'time': data_timesheet_invoice_type}
|
||||
|
||||
for key, value in res['repartition_employee'].items():
|
||||
res['repartition_employee'][key].update({
|
||||
emp_data[key]['type']: emp_data[key]['time']})
|
||||
|
||||
res['repartition_employee'][key]['total'] = sum(
|
||||
[value[inv_type] for inv_type in [*billable_types, 'canceled']])
|
||||
|
||||
return res
|
||||
|
||||
def _table_rows_sql_query(self):
|
||||
initial_date = fields.Date.from_string(fields.Date.today())
|
||||
ts_months = sorted(
|
||||
[fields.Date.to_string(initial_date - relativedelta(months=i, day=1))
|
||||
for i in range(0, DEFAULT_MONTH_RANGE)]) # M1, M2, M3
|
||||
# build query
|
||||
query = """
|
||||
SELECT
|
||||
'timesheet' AS type,
|
||||
date_trunc('month', date)::date AS month_date,
|
||||
E.id AS employee_id,
|
||||
S.order_id AS sale_order_id,
|
||||
A.so_line AS sale_line_id,
|
||||
SUM(A.calculated_hours_invoice) AS number_hours
|
||||
FROM account_analytic_line A
|
||||
JOIN hr_employee E ON E.id = A.employee_id
|
||||
LEFT JOIN sale_order_line S ON S.id = A.so_line
|
||||
WHERE A.project_id IS NOT NULL
|
||||
AND A.project_id IN %s
|
||||
AND A.date < %s
|
||||
GROUP BY date_trunc('month', date)::date, S.order_id, A.so_line, E.id
|
||||
"""
|
||||
last_ts_month = fields.Date.to_string(
|
||||
fields.Date.from_string(ts_months[-1]) + relativedelta(months=1))
|
||||
query_params = (tuple(self.ids), last_ts_month)
|
||||
return query, query_params
|
||||
@@ -0,0 +1,201 @@
|
||||
# coding: utf-8
|
||||
# Part of Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra import models, fields, api, _
|
||||
from flectra.osv import expression
|
||||
from flectra.addons.web.controllers.main import clean_action
|
||||
import json
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
_inherit = 'project.task'
|
||||
_description = "Project Task"
|
||||
|
||||
timesheet_invoice_type = fields.Selection([
|
||||
('billable_time', 'Billed on Timesheets'),
|
||||
('billable_fixed', 'Billed at a Fixed price'),
|
||||
('non_billable', 'Non Billable Tasks'),
|
||||
('non_billable_timesheet', 'Non Billable Timesheet'),
|
||||
('non_billable_project', 'No task found')],
|
||||
string="Billable Type")
|
||||
invoicable_id = fields.Many2one('hr_timesheet_invoice.factor',
|
||||
string="Invoicable(%)")
|
||||
total_computed_hours = fields.Float(compute="_compute_total_calculated_hours",
|
||||
help="Time spent on this task,"
|
||||
" excluding its sub-tasks.")
|
||||
|
||||
@api.onchange('project_id')
|
||||
def _compute_timesheet_invoice_type(self):
|
||||
values = {
|
||||
'timesheet_invoice_type': self.project_id.timesheet_invoice_type,
|
||||
'invoicable_id': self.project_id.invoicable_id,
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
@api.depends('timesheet_ids.unit_amount')
|
||||
def _compute_total_calculated_hours(self):
|
||||
for task in self:
|
||||
task.total_computed_hours = round(sum(
|
||||
task.timesheet_ids.mapped('calculated_hours_invoice')), 2)
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
_description = "Account Analytic Line"
|
||||
|
||||
invoicable_id = fields.Many2one('hr_timesheet_invoice.factor', string="Invoicable",
|
||||
readonly=False)
|
||||
timesheet_invoice_type = fields.Selection([
|
||||
('billable_time', 'Billed on Timesheets'),
|
||||
('billable_fixed', 'Billed at a Fixed price'),
|
||||
('non_billable', 'Non Billable Tasks'),
|
||||
('non_billable_timesheet', 'Non Billable Timesheet'),
|
||||
('non_billable_project', 'No task found')],
|
||||
string="Billable Type", compute=False)
|
||||
task_id = fields.Many2one(
|
||||
'project.task', 'Task', compute='_compute_task_id',
|
||||
store=True, readonly=False, index=True,
|
||||
domain="[('company_id', '=', company_id),"
|
||||
" ('project_id.allow_timesheets', '=', True),"
|
||||
" ('project_id', '=?', project_id)]")
|
||||
|
||||
@api.onchange('task_id')
|
||||
def _onchange_timesheet_ids(self):
|
||||
values = {
|
||||
'timesheet_invoice_type': self.task_id.timesheet_invoice_type,
|
||||
'invoicable_id': self.task_id.invoicable_id,
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
def _timesheet_postprocess_values(self, values):
|
||||
res = super(AccountAnalyticLine, self)._timesheet_postprocess_values(values)
|
||||
result = {id_: {} for id_ in self.ids}
|
||||
sudo_self = self.sudo()
|
||||
if any(field_name in values for field_name in ['calculated_hours_invoice',
|
||||
'employee_id', 'account_id']):
|
||||
for timesheet in sudo_self:
|
||||
cost = timesheet.employee_id.timesheet_cost or 0.0
|
||||
amount = -timesheet.calculated_hours_invoice * cost
|
||||
amount_converted = timesheet.employee_id.currency_id._convert(
|
||||
amount, timesheet.account_id.currency_id, self.env.company,
|
||||
timesheet.date)
|
||||
result[timesheet.id].update({
|
||||
'amount': amount_converted,
|
||||
})
|
||||
res.update(result)
|
||||
return result
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
_description = 'Sales Order'
|
||||
|
||||
@api.depends('timesheet_ids', 'company_id.timesheet_encode_uom_id')
|
||||
def _compute_timesheet_total_duration(self):
|
||||
for sale_order in self:
|
||||
timesheets = sale_order.timesheet_ids if self.user_has_groups(
|
||||
'hr_timesheet.group_hr_timesheet_approver') \
|
||||
else sale_order.timesheet_ids.filtered(
|
||||
lambda t: t.user_id.id == self.env.uid)
|
||||
total_time = 0.0
|
||||
for timesheet in timesheets.filtered(lambda t: not t.non_allow_billable):
|
||||
# Timesheets may be stored in a different unit of measure,
|
||||
# so first we convert all of them to the reference unit
|
||||
total_time += timesheet.calculated_hours_invoice * timesheet. \
|
||||
product_uom_id.factor_inv
|
||||
# Now convert to the proper unit of measure
|
||||
total_time *= sale_order.timesheet_encode_uom_id.factor
|
||||
sale_order.timesheet_total_duration = total_time
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = 'sale.order.line'
|
||||
_description = 'Sales Order Line'
|
||||
|
||||
def _get_delivered_quantity_by_analytic(self, additional_domain):
|
||||
res = super(SaleOrderLine, self)._get_delivered_quantity_by_analytic(
|
||||
additional_domain)
|
||||
domain = expression.AND([[('so_line', 'in', self.ids)], additional_domain])
|
||||
data = self.env['account.analytic.line'].read_group(
|
||||
domain,
|
||||
['so_line', 'calculated_hours_invoice', 'product_uom_id'],
|
||||
['product_uom_id', 'so_line'], lazy=False
|
||||
)
|
||||
for item in data:
|
||||
res.update({
|
||||
int(item['so_line'][0]): item['calculated_hours_invoice']
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
_description = "Project of Project"
|
||||
|
||||
invoicable_id = fields.Many2one('hr_timesheet_invoice.factor',
|
||||
string="Invoicable(%)")
|
||||
timesheet_invoice_type = fields.Selection([
|
||||
('billable_time', 'Billed on Timesheets'),
|
||||
('billable_fixed', 'Billed at a Fixed price'),
|
||||
('non_billable', 'Non Billable Tasks'),
|
||||
('non_billable_timesheet', 'Non Billable Timesheet'),
|
||||
('non_billable_project', 'No task found')],
|
||||
string="Billable Type")
|
||||
total_calculated_timesheet_time = fields.Integer(
|
||||
compute='_compute_calculated_timesheet_time',
|
||||
help="Total number of time (in the proper UoM) recorded in the project,"
|
||||
" rounded to the unit.")
|
||||
|
||||
@api.depends('timesheet_ids')
|
||||
def _compute_calculated_timesheet_time(self):
|
||||
total_time = 0.0
|
||||
for project in self:
|
||||
for timesheet in project.timesheet_ids:
|
||||
# Timesheets may be stored in a different unit of measure, so first
|
||||
# we convert all of them to the reference unit
|
||||
total_time += timesheet.calculated_hours_invoice * timesheet. \
|
||||
product_uom_id.factor_inv
|
||||
# Now convert to the proper unit of measure set in the settings
|
||||
total_time *= project.timesheet_encode_uom_id.factor
|
||||
project.total_calculated_timesheet_time = int(round(total_time))
|
||||
|
||||
def _plan_get_stat_button(self):
|
||||
res = super(Project, self)._plan_get_stat_button()
|
||||
ts_tree = self.env.ref('hr_timesheet.hr_timesheet_line_tree')
|
||||
ts_form = self.env.ref('hr_timesheet.hr_timesheet_line_form')
|
||||
if self.env.company.timesheet_encode_uom_id == self.env.ref(
|
||||
'uom.product_uom_day'):
|
||||
timesheet_labels = [_('Calculated'), _('Days')]
|
||||
else:
|
||||
timesheet_labels = [_('Calculated'), _('Hours')]
|
||||
res.append({
|
||||
'name': timesheet_labels,
|
||||
'count': sum(self.mapped('total_calculated_timesheet_time')),
|
||||
'icon': 'fa fa-calendar',
|
||||
'action': _to_action_data(
|
||||
'account.analytic.line',
|
||||
domain=[('project_id', 'in', self.ids)],
|
||||
views=[(ts_tree.id, 'list'), (ts_form.id, 'form')])
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
def _to_action_data(model=None,
|
||||
*, action=None, views=None, res_id=None, domain=None, context=None):
|
||||
# pass in either action or (model, views)
|
||||
if action:
|
||||
assert model is None and views is None
|
||||
act = clean_action(action.read()[0], env=action.env)
|
||||
model = act['res_model']
|
||||
views = act['views']
|
||||
# FIXME: search-view-id, possibly help?
|
||||
descr = {
|
||||
'data-model': model,
|
||||
'data-views': json.dumps(views),
|
||||
}
|
||||
if context is not None: # otherwise copy action's?
|
||||
descr['data-context'] = json.dumps(context)
|
||||
if res_id:
|
||||
descr['data-res-id'] = res_id
|
||||
elif domain:
|
||||
descr['data-domain'] = json.dumps(domain)
|
||||
return descr
|
||||
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_hr_timesheet_invoice_factor_user,access_hr_timesheet_invoice_factor_user,model_hr_timesheet_invoice_factor,base.group_user,1,0,0,0
|
||||
access_hr_timesheet_invoice_factor_manager,access_hr_timesheet_invoice_factor_manager,model_hr_timesheet_invoice_factor,project.group_project_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,68 @@
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<div class="oe_span12">
|
||||
<h2 class="oe_slogan">Project Billing Options</h2>
|
||||
<h4 class="oe_slogan">Manage Timesheets and Calculate Profitability based on Invoicing Factor</h4>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Project</h2>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">In Project, we have to select Billable Type &
|
||||
Invoiceable(%) for working invoicing factor.</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture" src="project.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Task</h2>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture" src="task_only.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">In Task, Billable Type & Invoicable(%) data will automatically filled based on the data which is assigned in project.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Timesheet</h2>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">You can create the timesheet of the project and here you can select the Invoiceable(%) based on which Timesheet's calculated hours
|
||||
are computed & only that hours will be invoiced.</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture oe_screenshot" src="task.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Project Overview</h2>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture" src="project_overview.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">It's an Overview of project.It shows recorded hours for this project & as per recorded
|
||||
hours, it will display the cost & profit of the project.It shows invoiced,To Invoice,& Timesheet Cost to find
|
||||
Profitability and other data.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,20 @@
|
||||
<flectra>
|
||||
|
||||
<record id="hr_timesheet_invoice_factor_view" model="ir.ui.view">
|
||||
<field name="name">hr_timesheet_invoice.factor.form</field>
|
||||
<field name="model">hr_timesheet_invoice.factor</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Invoice factor" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="factor"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_timesheet_invoice_factor" model="ir.actions.act_window">
|
||||
<field name="name">Invoicing Factor</field>
|
||||
<field name="res_model">hr_timesheet_invoice.factor</field>
|
||||
<field name="view_mode">tree</field>
|
||||
</record>
|
||||
|
||||
</flectra>
|
||||
@@ -0,0 +1,93 @@
|
||||
<flectra>
|
||||
<record id="project_inherit_form" model="ir.ui.view">
|
||||
<field name="name">project.task.form.inherit</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<field name="sale_line_id" position="after">
|
||||
<field name="timesheet_invoice_type"/>
|
||||
<field name="invoicable_id"/>
|
||||
</field>
|
||||
|
||||
<xpath expr="//group[@name='project_hours']//span[1]" position="replace">
|
||||
<span>
|
||||
<label class="font-weight-bold" for="total_computed_hours" string="Calculated Hours Spent"
|
||||
attrs="{'invisible': [('encode_uom_in_days', '=', True)]}"/>
|
||||
<label class="font-weight-bold" for="total_computed_hours" string="Calculated Days Spent"
|
||||
attrs="{'invisible': [('encode_uom_in_days', '=', False)]}"/>
|
||||
</span>
|
||||
</xpath>
|
||||
|
||||
<field name="effective_hours" position="replace">
|
||||
<field name="total_computed_hours" widget="timesheet_uom" nolabel="1"/>
|
||||
</field>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_account_analytic_line_inherit" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.tree.inherit</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_line_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="timesheet_invoice_type" readonly="0"/>
|
||||
<field name="invoicable_id"/>
|
||||
<field name="calculated_hours_invoice" widget="float_time"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- project task view -->
|
||||
<record id="view_account_analytic_line_inherit" model="ir.ui.view">
|
||||
<field name="name">project.task.form.inherited</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.view_task_form2_inherited"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page/field[@name='timesheet_ids']/tree/field[@name='unit_amount']"
|
||||
position="after">
|
||||
<field name="invoicable_id" string='Invoicable(%)'/>
|
||||
<field name="calculated_hours_invoice" widget="float_time"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//page/field[@name='timesheet_ids']/tree/field[@name='unit_amount']" position="after">
|
||||
<field name="timesheet_invoice_type" readonly="0"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- project view -->
|
||||
<record id="project_project_form_inherit_view" model="ir.ui.view">
|
||||
<field name="name">project.project.inherit.form</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.edit_project"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//div[@name='options_active']" position="after">
|
||||
<group>
|
||||
<field name="timesheet_invoice_type" readonly="0"/>
|
||||
<field name="invoicable_id"/>
|
||||
</group>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet_line_tree_inherited" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.tree.hr_timesheet.inherited</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='unit_amount']" position="after">
|
||||
<field name="invoicable_id"/>
|
||||
<field name="calculated_hours_invoice" widget="float_time" optional="show" sum="Total Calculated Hours"
|
||||
decoration-danger="calculated_hours_invoice > 24"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</flectra>
|
||||
Reference in new issue
Block a user