From 12cd2f546e4f8c6482a70bcb81776515edbaddd1 Mon Sep 17 00:00:00 2001 From: Pawan Kumar Date: Mon, 14 Dec 2020 16:21:06 +0530 Subject: [PATCH] change sale_timesheet qweb file in xpath --- .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 210 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 193 bytes .../__pycache__/controllers.cpython-36.pyc | Bin 0 -> 160 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 283 bytes .../__pycache__/crm_lead.cpython-36.pyc | Bin 0 -> 453 bytes .../models/__pycache__/models.cpython-36.pyc | Bin 0 -> 150 bytes .../models/__pycache__/project.cpython-36.pyc | Bin 0 -> 787 bytes .../project_overview.cpython-36.pyc | Bin 0 -> 5801 bytes cor_custom/models/project_overview.py | 387 +------------- cor_custom/views/hr_timesheet_templates.xml | 470 +----------------- 10 files changed, 13 insertions(+), 844 deletions(-) create mode 100644 cor_custom/__pycache__/__init__.cpython-36.pyc create mode 100644 cor_custom/controllers/__pycache__/__init__.cpython-36.pyc create mode 100644 cor_custom/controllers/__pycache__/controllers.cpython-36.pyc create mode 100644 cor_custom/models/__pycache__/__init__.cpython-36.pyc create mode 100644 cor_custom/models/__pycache__/crm_lead.cpython-36.pyc create mode 100644 cor_custom/models/__pycache__/models.cpython-36.pyc create mode 100644 cor_custom/models/__pycache__/project.cpython-36.pyc create mode 100644 cor_custom/models/__pycache__/project_overview.cpython-36.pyc diff --git a/cor_custom/__pycache__/__init__.cpython-36.pyc b/cor_custom/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2ca7f5dad7e0aa4741977cd979df0d8af3279e3 GIT binary patch literal 210 zcmXr!<>lIZ>O#Cb0|UcjAcg}bAj<)Wi)DaB3PTEG4nr-mKMKJ-{%)tzr zEH4>>N;Da7aVO{Jl@#UYlIZ>O#B`0|UcjAcg}bAj<)Wiv@s03PTEG4nr1ps*(6 zE$-y}ypp2)oSf96Vn0o$TMR`YjVl?7Sb!9m_~oUaUr?f-pOT+%Xac5mlS_+B@^f_) zQ&RHtiuIH8i*!MlIZ>O%Z$CI*Jb3`l?x$aVnYViq8g!Vt`$$>_I|p$H_5Ab$Dk=NFXd=cnZ7 z8=8PA-Q?2ZlKfoV#FUi$ykh<2{32bDEQk>gkpeRFN{aGxa#D*x@^H3ZLFFwDo80`A O(wtN~kTu0X%m4uV7%UI~ literal 0 HcmV?d00001 diff --git a/cor_custom/models/__pycache__/__init__.cpython-36.pyc b/cor_custom/models/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2651ed4b9d326251a1530c7cc35f2a44881552a4 GIT binary patch literal 283 zcmX}mu?oU45C-6+X=@P$U!y|}V#gvbt}bp}f;1T_jaQOZ@sWHYp{tXx;N)Eu6YkGP zj&NDe=hGs6s2gMKiS=IrYK!G_49zrenB^tsJrqqCBNgPQuVp+lv=j8At~^(Z_I{9`50ryX@3A5oJLjv literal 0 HcmV?d00001 diff --git a/cor_custom/models/__pycache__/crm_lead.cpython-36.pyc b/cor_custom/models/__pycache__/crm_lead.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87c5da1763fe7ebb7d4a42485ab24ba48dfbcfa4 GIT binary patch literal 453 zcmYLFJ5R$f5cVq%Rqe*ez#61BLNHdS5+Hx??}MxJjQZGhwrcNIS>jWr+M>})`H@ng?5;*UR_cCN)IUtB zs&=hy)mawGdU3WctV~c;wdR%BaftTr>moPXE;nGz(Mf+z%bn{?Gc5p2>+;grDY6LE zK;^@%dFWH29hM literal 0 HcmV?d00001 diff --git a/cor_custom/models/__pycache__/models.cpython-36.pyc b/cor_custom/models/__pycache__/models.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18206e2f506784bae4310bafab77e46a0f53af8b GIT binary patch literal 150 zcmXr!<>lIZ>O%YjMh1q*3`hXTXK(=GViq8g!Vt`$$>_I|p$H_5AbvUP=NFXd=cnZ7 z8=8PA-Q?2ZlKfoV#FUi$ykh<2{32bDEQk>gk8S%5Z)i#=Unctph|&I+LJh22pS}WB84AOi|C1|&$;uWZv@x|yU5F^cx_nV#hJUh?l^XccW-_wN26XK_R|UbN zNDR(U-BUE0WgJ*0fM-$%RS06ovq(>>803ya0O38ulOu?*&BUL6XgAuH{iQXPF37>Q z=-)P7ZEm!_$h+Go-5yF@UhAYTTb<|!IB5`HjLD~$wk)(i9#K_7fioF|yg)rib&t{9 zv5MJ8WGjBhlxQOuJTRms*4LOa$Wqz~27b|(jdmNYdsP_M`^BFw9d%uDA2?lKePQ+( z(|^55P1mOejCuBAaI0eLdef|O0Moj(Fm^>K1!@na`{AbJ?{+u-M47hO=;xlpk@_$oI{#9wcocdU)HgB|2eyS9**wz%!l=`&I>jxxO<^G{8y|xeE)h6%l z5yqD0>f@2Aozoc+JTNo*B)+5r@t6lrb&*lIC literal 0 HcmV?d00001 diff --git a/cor_custom/models/__pycache__/project_overview.cpython-36.pyc b/cor_custom/models/__pycache__/project_overview.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8831012054ea2adb64caf4359f7bed0a926cd9c8 GIT binary patch literal 5801 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 [INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted] - for sale_line_id in empty_line_ids: # add service SO line having no timesheet - sale_line_row_key = (map_sol_so.get(sale_line_id), sale_line_id) - sale_line = map_sol.get(sale_line_id) - is_milestone = sale_line.product_id.invoice_policy == 'delivery' and sale_line.product_id.service_type == 'manual' if sale_line else False - rows_sale_line[sale_line_row_key] = [{'label': map_sol_names.get(sale_line_id, _('No Sales Order Line')), 'res_id': sale_line_id, 'res_model': 'sale.order.line', 'type': 'sale_order_line', 'is_milestone': is_milestone}] + default_row_vals[:] - if not is_milestone: - rows_sale_line[sale_line_row_key][-2] = sale_line.product_uom._compute_quantity(sale_line.product_uom_qty, uom_hour, raise_if_failure=False) if sale_line else 0.0 - - rows_sale_line_all_data = {} - if not employees: - employees = self.env['hr.employee'].sudo().search(self.env['account.analytic.line']._domain_employee_id()) - for row_key, row_employee in rows_employee.items(): - sale_order_id, sale_line_id, employee_id = row_key - # sale line row - sale_line_row_key = (sale_order_id, sale_line_id) - if sale_line_row_key not in rows_sale_line: - sale_line = map_sol.get(sale_line_id, self.env['sale.order.line']) - is_milestone = sale_line.product_id.invoice_policy == 'delivery' and sale_line.product_id.service_type == 'manual' if sale_line else False - rows_sale_line[sale_line_row_key] = [{'label': map_sol_names.get(sale_line.id) if sale_line else _('No Sales Order Line'), 'res_id': sale_line_id, 'res_model': 'sale.order.line', 'type': 'sale_order_line', 'is_milestone': is_milestone}] + default_row_vals[:] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted - if not is_milestone: - rows_sale_line[sale_line_row_key][-2] = sale_line.product_uom._compute_quantity(sale_line.product_uom_qty, uom_hour, raise_if_failure=False) if sale_line else 0.0 - - if sale_line_row_key not in rows_sale_line_all_data: - rows_sale_line_all_data[sale_line_row_key] = [0] * len(row_employee) - for index in range(1, len(row_employee)): - if employee_id in employees.ids: - rows_sale_line[sale_line_row_key][index] += row_employee[index] - rows_sale_line_all_data[sale_line_row_key][index] += row_employee[index] - if not rows_sale_line[sale_line_row_key][0].get('is_milestone'): - rows_sale_line[sale_line_row_key][-1] = rows_sale_line[sale_line_row_key][-2] - rows_sale_line_all_data[sale_line_row_key][5] - else: - rows_sale_line[sale_line_row_key][-1] = 0 - - rows_sale_order = {} # so -> [INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted] - for row_key, row_sale_line in rows_sale_line.items(): - sale_order_id = row_key[0] - # sale order row - if sale_order_id not in rows_sale_order: - rows_sale_order[sale_order_id] = [{'label': map_so_names.get(sale_order_id, _('No Sales Order')), 'canceled': map_so_cancel.get(sale_order_id, False), 'res_id': sale_order_id, 'res_model': 'sale.order', 'type': 'sale_order'}] + default_row_vals[:] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted - - for index in range(1, len(row_sale_line)): - rows_sale_order[sale_order_id][index] += row_sale_line[index] - - # group rows SO, SOL and their related employee rows. - timesheet_forecast_table_rows = [] - for sale_order_id, sale_order_row in rows_sale_order.items(): - timesheet_forecast_table_rows.append(sale_order_row) - for sale_line_row_key, sale_line_row in rows_sale_line.items(): - if sale_order_id == sale_line_row_key[0]: - sale_order_row[0]['has_children'] = True - timesheet_forecast_table_rows.append(sale_line_row) - for employee_row_key, employee_row in rows_employee.items(): - if sale_order_id == employee_row_key[0] and sale_line_row_key[1] == employee_row_key[1] and employee_row_key[2] in employees.ids: - sale_line_row[0]['has_children'] = True - timesheet_forecast_table_rows.append(employee_row) - - if is_uom_day: - # convert all values from hours to days - for row in timesheet_forecast_table_rows: - for index in range(1, len(row)): - row[index] = round(uom_hour._compute_quantity(row[index], company_uom, raise_if_failure=False), 2) - # complete table data - return { - 'header': self._table_header(), - 'rows': timesheet_forecast_table_rows - } - def _table_header(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 - - def _to_short_month_name(date): - month_index = fields.Date.from_string(date).month - return babel.dates.get_month_names('abbreviated', locale=get_lang(self.env).code)[month_index] - - header_names = [_('Sales Order'), _('Before')] + [_to_short_month_name(date) for date in ts_months] + [_('Total'), _('Sold'), _('Remaining')] - - result = [] - for name in header_names: - result.append({ - 'label': name, - 'tooltip': '', - }) - # add tooltip for reminaing - result[-1]['tooltip'] = _('What is still to deliver based on sold hours and hours already done. Equals to sold hours - done hours.') - return result - - def _table_row_default(self): - lenght = len(self._table_header()) - return [0.0] * (lenght - 1) # before, M1, M2, M3, Done, Sold, Remaining - - 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.unit_amount) 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 - - def _table_rows_get_employee_lines(self, data_from_db): - initial_date = fields.Date.today() - ts_months = sorted([initial_date - relativedelta(months=i, day=1) for i in range(0, DEFAULT_MONTH_RANGE)]) # M1, M2, M3 - default_row_vals = self._table_row_default() - - # extract employee names - employee_ids = set() - for data in data_from_db: - employee_ids.add(data['employee_id']) - map_empl_names = {empl.id: empl.name for empl in self.env['hr.employee'].sudo().browse(employee_ids)} - - # extract rows data for employee, sol and so rows - rows_employee = {} # (so, sol, employee) -> [INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted] - for data in data_from_db: - sale_line_id = data['sale_line_id'] - sale_order_id = data['sale_order_id'] - # employee row - row_key = (data['sale_order_id'], sale_line_id, data['employee_id']) - if row_key not in rows_employee: - meta_vals = { - 'label': map_empl_names.get(row_key[2]), - 'sale_line_id': sale_line_id, - 'sale_order_id': sale_order_id, - 'res_id': row_key[2], - 'res_model': 'hr.employee', - 'type': 'hr_employee' - } - rows_employee[row_key] = [meta_vals] + default_row_vals[:] # INFO, before, M1, M2, M3, Done, M3, M4, M5, After, Forecasted - - index = False - if data['type'] == 'timesheet': - if data['month_date'] in ts_months: - index = ts_months.index(data['month_date']) + 2 - elif data['month_date'] < ts_months[0]: - index = 1 - rows_employee[row_key][index] += data['number_hours'] - rows_employee[row_key][5] += data['number_hours'] - return rows_employee - - def _table_get_empty_so_lines(self): - """ get the Sale Order Lines having no timesheet but having generated a task or a project """ - so_lines = self.sudo().mapped('sale_line_id.order_id.order_line').filtered(lambda sol: sol.is_service and not sol.is_expense and not sol.is_downpayment) - # include the service SO line of SO sharing the same project - sale_order = self.env['sale.order'].search([('project_id', 'in', self.ids)]) - return set(so_lines.ids) | set(sale_order.mapped('order_line').filtered(lambda sol: sol.is_service and not sol.is_expense).ids), set(so_lines.mapped('order_id').ids) | set(sale_order.ids) - - # -------------------------------------------------- - # Actions: Stat buttons, ... - # -------------------------------------------------- - - def _plan_prepare_actions(self, values): - actions = [] - if len(self) == 1: - task_order_line_ids = [] - # retrieve all the sale order line that we will need later below - if self.env.user.has_group('sales_team.group_sale_salesman') or self.env.user.has_group('sales_team.group_sale_salesman_all_leads'): - task_order_line_ids = self.env['project.task'].read_group([('project_id', '=', self.id), ('sale_line_id', '!=', False)], ['sale_line_id'], ['sale_line_id']) - task_order_line_ids = [ol['sale_line_id'][0] for ol in task_order_line_ids] - - if self.env.user.has_group('sales_team.group_sale_salesman'): - if self.bill_type == 'customer_project' and self.allow_billable and not self.sale_order_id: - actions.append({ - 'label': _("Create a Sales Order"), - 'type': 'action', - 'action_id': 'sale_timesheet.project_project_action_multi_create_sale_order', - 'context': json.dumps({'active_id': self.id, 'active_model': 'project.project'}), - }) - if self.env.user.has_group('sales_team.group_sale_salesman_all_leads'): - to_invoice_amount = values['dashboard']['profit'].get('to_invoice', False) # plan project only takes services SO line with timesheet into account - - sale_order_ids = self.env['sale.order.line'].read_group([('id', 'in', task_order_line_ids)], ['order_id'], ['order_id']) - sale_order_ids = [s['order_id'][0] for s in sale_order_ids] - sale_order_ids = self.env['sale.order'].search_read([('id', 'in', sale_order_ids), ('invoice_status', '=', 'to invoice')], ['id']) - sale_order_ids = list(map(lambda x: x['id'], sale_order_ids)) - - if to_invoice_amount and sale_order_ids: - if len(sale_order_ids) == 1: - actions.append({ - 'label': _("Create Invoice"), - 'type': 'action', - 'action_id': 'sale.action_view_sale_advance_payment_inv', - 'context': json.dumps({'active_ids': sale_order_ids, 'active_model': 'project.project'}), - }) - else: - actions.append({ - 'label': _("Create Invoice"), - 'type': 'action', - 'action_id': 'sale_timesheet.project_project_action_multi_create_invoice', - 'context': json.dumps({'active_id': self.id, 'active_model': 'project.project'}), - }) - return actions - - def _plan_get_stat_button(self): - stat_buttons = [] - num_projects = len(self) - if num_projects == 1: - action_data = _to_action_data('project.project', res_id=self.id, - views=[[self.env.ref('project.edit_project').id, 'form']]) - else: - action_data = _to_action_data(action=self.env.ref('project.open_view_project_all_config').sudo(), - domain=[('id', 'in', self.ids)]) - - stat_buttons.append({ - 'name': _('Project') if num_projects == 1 else _('Projects'), - 'count': num_projects, - 'icon': 'fa fa-puzzle-piece', - 'action': action_data - }) - - # if only one project, add it in the context as default value - tasks_domain = [('project_id', 'in', self.ids)] - tasks_context = self.env.context.copy() - tasks_context.pop('search_default_name', False) - late_tasks_domain = [('project_id', 'in', self.ids), ('date_deadline', '<', fields.Date.to_string(fields.Date.today())), ('date_end', '=', False)] - overtime_tasks_domain = [('project_id', 'in', self.ids), ('overtime', '>', 0), ('planned_hours', '>', 0)] - - # filter out all the projects that have no tasks - task_projects_ids = self.env['project.task'].read_group([('project_id', 'in', self.ids)], ['project_id'], ['project_id']) - task_projects_ids = [p['project_id'][0] for p in task_projects_ids] - - if len(task_projects_ids) == 1: - tasks_context = {**tasks_context, 'default_project_id': task_projects_ids[0]} - stat_buttons.append({ - 'name': _('Tasks'), - 'count': sum(self.mapped('task_count')), - 'icon': 'fa fa-tasks', - 'action': _to_action_data( - action=self.env.ref('project.action_view_task').sudo(), - domain=tasks_domain, - context=tasks_context - ) - }) - stat_buttons.append({ - 'name': [_("Tasks"), _("Late")], - 'count': self.env['project.task'].search_count(late_tasks_domain), - 'icon': 'fa fa-tasks', - 'action': _to_action_data( - action=self.env.ref('project.action_view_task').sudo(), - domain=late_tasks_domain, - context=tasks_context, - ), - }) - stat_buttons.append({ - 'name': [_("Tasks"), _("in Overtime")], - 'count': self.env['project.task'].search_count(overtime_tasks_domain), - 'icon': 'fa fa-tasks', - 'action': _to_action_data( - action=self.env.ref('project.action_view_task').sudo(), - domain=overtime_tasks_domain, - context=tasks_context, - ), - }) - - if self.env.user.has_group('sales_team.group_sale_salesman_all_leads'): - # read all the sale orders linked to the projects' tasks - task_so_ids = self.env['project.task'].search_read([ - ('project_id', 'in', self.ids), ('sale_order_id', '!=', False) - ], ['sale_order_id']) - task_so_ids = [o['sale_order_id'][0] for o in task_so_ids] - - sale_orders = self.mapped('sale_line_id.order_id') | self.env['sale.order'].browse(task_so_ids) - if sale_orders: - stat_buttons.append({ - 'name': _('Sales Orders'), - 'count': len(sale_orders), - 'icon': 'fa fa-dollar', - 'action': _to_action_data( - action=self.env.ref('sale.action_orders').sudo(), - domain=[('id', 'in', sale_orders.ids)], - context={'create': False, 'edit': False, 'delete': False} - ) - }) - - invoice_ids = self.env['sale.order'].search_read([('id', 'in', sale_orders.ids)], ['invoice_ids']) - invoice_ids = list(itertools.chain(*[i['invoice_ids'] for i in invoice_ids])) - invoice_ids = self.env['account.move'].search_read([('id', 'in', invoice_ids), ('move_type', '=', 'out_invoice')], ['id']) - invoice_ids = list(map(lambda x: x['id'], invoice_ids)) - - if invoice_ids: - stat_buttons.append({ - 'name': _('Invoices'), - 'count': len(invoice_ids), - 'icon': 'fa fa-pencil-square-o', - 'action': _to_action_data( - action=self.env.ref('account.action_move_out_invoice_type').sudo(), - domain=[('id', 'in', invoice_ids), ('move_type', '=', 'out_invoice')], - context={'create': False, 'delete': False} - ) - }) - - 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_label = [_('Days'), _('Recorded')] - else: - timesheet_label = [_('Hours'), _('Recorded')] - - stat_buttons.append({ - 'name': timesheet_label, - 'count': sum(self.mapped('total_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 stat_buttons -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 + diff --git a/cor_custom/views/hr_timesheet_templates.xml b/cor_custom/views/hr_timesheet_templates.xml index f02a7d7..c997bba 100755 --- a/cor_custom/views/hr_timesheet_templates.xml +++ b/cor_custom/views/hr_timesheet_templates.xml @@ -1,467 +1,21 @@ - - + Timesheet Plan qweb project.project + - - -
-
-
-
-
- - - -
-

Recorded Days and Profitability

-

Recorded Hours and Profitability

-
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - Days recorded - Hours recorded - -
- - - - ( %) - - Billed on Timesheets -
- - - - ( %) - - Billed at a Fixed price -
- - - - ( %) - - No task found -
- - - - ( %) - - - - Non Billable Tasks - - -
- - - - ( %) - - - - Non Billable Timesheets - - -
- - - - ( %) - - Cancelled -
- - - - - Total
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Profitability -
- - - Invoiced -
- - - To invoice -
- - Other Revenues
- - - Timesheet costs -
- - - Other costs -
- - - Re-invoiced costs -
- - - - Total
-
-
-
-
- -
-

Time by people

-
- -
- -

There are no timesheets for now.

-
- -
- - - - - - - - - - - - - - - - - - - - - - - -
EmployeeHourly RateDays SpentHours Spent - -
- - - - - - - - - - - -
-
- - - - Billed on Timesheets - billable_time - - - Billed at a Fixed price - billable_fixed - - - No task found - non_billable_project - - - Non billable timesheets - non_billable_timesheet - - - Non billable tasks - non_billable - - - Cancelled - canceled - -
-
-
-
-
-
- -
-

Timesheets

-
- - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Timesheets

- -
- - - - - - - - Cancelled - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
-
-
-
-
+ + Hourly Rate + + + + + +
-