diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/sale_timesheet/views | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/sale_timesheet/views')
| -rw-r--r-- | addons/sale_timesheet/views/account_invoice_views.xml | 26 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/hr_timesheet_templates.xml | 494 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/hr_timesheet_views.xml | 139 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/product_views.xml | 41 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/project_task_views.xml | 209 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/report_invoice.xml | 10 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/res_config_settings_views.xml | 33 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/sale_order_views.xml | 32 | ||||
| -rw-r--r-- | addons/sale_timesheet/views/sale_timesheet_portal_templates.xml | 161 |
9 files changed, 1145 insertions, 0 deletions
diff --git a/addons/sale_timesheet/views/account_invoice_views.xml b/addons/sale_timesheet/views/account_invoice_views.xml new file mode 100644 index 00000000..9bd3969e --- /dev/null +++ b/addons/sale_timesheet/views/account_invoice_views.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="action_timesheet_from_invoice" model="ir.actions.act_window"> + <field name="name">Timesheet</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">account.analytic.line</field> + <field name="view_mode">tree,form,graph</field> + <field name="context">{}</field> + <field name="domain">[('timesheet_invoice_id', '=', active_id)]</field> + </record> + + <record id="account_invoice_view_form_inherit_sale_timesheet" model="ir.ui.view"> + <field name="name">account.invoice.form.inherit.timesheet</field> + <field name="model">account.move</field> + <field name="inherit_id" ref="account.view_move_form"/> + <field name="arch" type="xml"> + <xpath expr="//div[@name='button_box']" position="inside"> + <button name="%(sale_timesheet.action_timesheet_from_invoice)d" type="action" class="oe_stat_button" icon="fa-calendar" attrs="{'invisible':[('timesheet_count','=', 0)]}"> + <field name="timesheet_count" widget="statinfo" string="Timesheets"/> + </button> + </xpath> + </field> + </record> + +</odoo> diff --git a/addons/sale_timesheet/views/hr_timesheet_templates.xml b/addons/sale_timesheet/views/hr_timesheet_templates.xml new file mode 100644 index 00000000..1715f3c2 --- /dev/null +++ b/addons/sale_timesheet/views/hr_timesheet_templates.xml @@ -0,0 +1,494 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <template id="assets_backend" name="sale timesheet assets" inherit_id="web.assets_backend"> + <xpath expr="." position="inside"> + <link rel="stylesheet" type="text/scss" href="/sale_timesheet/static/src/scss/sale_timesheet.scss"/> + <script type="text/javascript" src="/sale_timesheet/static/src/js/project_overview.js"></script> + <script type="text/javascript" src="/sale_timesheet/static/src/js/sale_project_kanban_controller.js"/> + </xpath> + </template> + + <record id="timesheet_plan" model="ir.ui.view"> + <field name="name">Timesheet Plan</field> + <field name="type">qweb</field> + <field name="model">project.project</field> + <field name="arch" type="xml"> + <qweb js_class="project_overview"> + <nav class="o_qweb_cp_buttons" t-if="actions"> + <button t-foreach="actions" t-as="action" + type="action" class="btn btn-primary" + t-att-name="action['action_id']" + t-att-data-context="action.get('context')" + > + <t t-esc="action['label']"/> + </button> + </nav> + <div class="o_form_view o_form_readonly o_project_plan"> + <div class="o_form_sheet_bg"> + <div class="o_form_sheet o_timesheet_plan_content"> + <div class="o_timesheet_plan_sale_timesheet"> + <div class="o_timesheet_plan_sale_timesheet_dashboard"> + + <div class="o_timesheet_plan_stat_buttons oe_button_box o_not_full"> + <t t-foreach="stat_buttons" t-as="stat_button"> + <a class="btn oe_stat_button" + type="action" + t-att="stat_button['action']" + > + <div t-attf-class="fa fa-fw o_button_icon #{stat_button['icon']}" role="img" aria-label="Statistics" title="Statistics"></div> + <div class="o_field_widget o_stat_info o_readonly_modifier" t-att-title="stat_button['name']"> + <t t-if="not isinstance(stat_button['name'], list)"> + <span class="o_stat_value" t-if="'count' in stat_button"> + <t t-esc="stat_button['count']"/> + </span> + <span class="o_stat_text"> + <t t-esc="stat_button['name']"/> + </span> + </t> + <t t-if="isinstance(stat_button['name'], list)"> + <div class="oe_inline"> + <span class="o_stat_value mr-1"> + <t t-esc="stat_button.get('count')"/> + </span> + <span class="o_stat_value"> + <t t-esc="stat_button['name'][0]"/> + </span> + </div> + <span class="o_stat_text"> + <t t-esc="stat_button['name'][1]"/> + </span> + </t> + </div> + </a> + </t> + </div> + + <div class="o_title"> + <h2 t-if="is_uom_day">Recorded Days and Profitability</h2> + <h2 t-else="">Recorded Hours and Profitability</h2> + </div> + + <t t-set="display_cost" t-value="dashboard['profit']['expense_cost'] != 0.0"/> + <div class="o_profitability_wrapper"> + <div class="o_profitability_section"> + <div> + <table class="table"> + <tbody> + <th> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain)" + data-context='{"pivot_row_groupby": ["date:month"],"pivot_column_groupby": ["timesheet_invoice_type"], "pivot_measures": ["unit_amount"]}' + data-views='[[0, "pivot"], [0, "list"]]' tabindex="-1"> + <span t-if="is_uom_day">Days recorded</span> + <span t-else="">Hours recorded</span> + </a> + </th> + <tr> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-if="is_uom_day" t-esc="dashboard['time']['billable_time']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['billable_time']" t-options="{'widget': 'float_time'}"/> + </td> + <td class="o_timesheet_plan_dashboard_cell"> + (<t t-esc="dashboard['rates']['billable_time']"/> %) + </td> + <td title="Includes the time logged into tasks for which you invoice based on timesheets on tasks."> + Billed on Timesheets + </td> + </tr> + <tr> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-if="is_uom_day" t-esc="dashboard['time']['billable_fixed']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['billable_fixed']" t-options="{'widget': 'float_time'}"/> + </td> + <td class="o_timesheet_plan_dashboard_cell"> + (<t t-esc="dashboard['rates']['billable_fixed']"/> %) + </td> + <td title="Includes the time logged into tasks for which you invoice based on ordered quantities or on milestones."> + Billed at a Fixed price + </td> + </tr> + <tr t-if="dashboard['time']['non_billable_project'] != 0"> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-if="is_uom_day" t-esc="dashboard['time']['non_billable_project']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['non_billable_project']" t-options="{'widget': 'float_time'}"/> + </td> + <td class="o_timesheet_plan_dashboard_cell"> + (<t t-esc="dashboard['rates']['non_billable_project']"/> %) + </td> + <td title="Includes the time logged from the Timesheet module that is linked to a project, but not to a task."> + No task found + </td> + </tr> + <tr> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-if="is_uom_day" t-esc="dashboard['time']['non_billable']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['non_billable']" t-options="{'widget': 'float_time'}"/> + </td> + <td class="o_timesheet_plan_dashboard_cell"> + (<t t-esc="dashboard['rates']['non_billable']"/> %) + </td> + <td> + <a type="action" + data-model="project.task" + data-views='[[false, "list"], [false, "form"]]' + t-att-data-domain="json.dumps([['project_id', 'in', projects.ids], ['sale_line_id', '=', False]])" + > + <span class="btn-link" + style="font-weight:normal;" + title="Includes the time logged into a task which is not linked to any Sales Order."> + Non Billable Tasks + </span> + </a> + </td> + </tr> + <tr t-if="dashboard['time']['non_billable_timesheet'] > 0"> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-if="is_uom_day" t-esc="dashboard['time']['non_billable_timesheet']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['non_billable_timesheet']" t-options="{'widget': 'float_time'}"/> + </td> + <td class="o_timesheet_plan_dashboard_cell"> + (<t t-esc="dashboard['rates']['non_billable_timesheet']"/> %) + </td> + <td> + <a type="action" + data-model="account.analytic.line" + data-views='[[false, "list"], [false, "form"]]' + t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable_timesheet')])" + > + <span class="btn-link" + style="font-weight:normal;"> + Non Billable Timesheets + </span> + </a> + </td> + </tr> + <tr t-if="dashboard['time']['canceled'] > 0"> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-if="is_uom_day" t-esc="dashboard['time']['canceled']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['canceled']" t-options="{'widget': 'float_time'}"/> + </td> + <td class="o_timesheet_plan_dashboard_cell"> + (<t t-esc="dashboard['rates']['canceled']"/> %) + </td> + <td title="Includes the time logged into a task which is linked to a cancelled Sales Order."> + Cancelled + </td> + </tr> + <tr> + <td class="o_timesheet_plan_dashboard_total"> + <b> + <t t-if="is_uom_day" t-esc="dashboard['time']['total']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="dashboard['time']['total']" t-options="{'widget': 'float_time'}"/> + </b> + </td> + <td><b>Total</b></td> + <td></td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="o_profitability_section"> + <div> + <table class="table"> + <tbody> + <th> + <a type="action" data-model="project.profitability.report" t-att-data-domain="json.dumps(profitability_domain)" data-context="{'group_by_no_leaf':1, 'group_by':[], 'sale_show_order_product_name': 1}" data-views='[[0, "pivot"], [0, "graph"]]' tabindex="-1">Profitability</a> + </th> + <tr> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-esc="dashboard['profit']['invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </td> + <td title="Revenues linked to Timesheets already invoiced."> + Invoiced + </td> + </tr> + <tr> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-esc="dashboard['profit']['to_invoice']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </td> + <td title="Revenues linked to Timesheets not yet invoiced."> + To invoice + </td> + </tr> + <tr t-if="dashboard['profit']['other_revenues'] > 0"> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-esc="dashboard['profit']['other_revenues']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </td> + <td title="All revenues that are not from timesheets and that are linked to the analytic account of the project.">Other Revenues</td> + </tr> + <tr> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-esc="dashboard['profit']['cost']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </td> + <td title="This cost is based on the "Timesheet cost" set in the HR Settings of your employees."> + Timesheet costs + </td> + </tr> + <tr t-if="display_cost"> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-esc="dashboard['profit']['expense_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </td> + <td title="Any cost linked to the Analytic Account of the Project."> + Other costs + </td> + </tr> + <tr t-if="display_cost & (dashboard['profit']['expense_amount_untaxed_invoiced'] != 0)"> + <td class="o_timesheet_plan_dashboard_cell"> + <t t-esc="dashboard['profit']['expense_amount_untaxed_invoiced']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </td> + <td title="Costs from expenses that were reinvoiced to your customer (provided that the Analytic Account of the Project was set on the Expense)."> + Re-invoiced costs + </td> + </tr> + <tr> + <td class="o_timesheet_plan_dashboard_total"> + <b> + <t t-esc="dashboard['profit']['total']" t-options='{"widget": "monetary", "display_currency": currency}'/> + </b> + </td> + <td><b>Total</b></td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </div> + + <div class="o_title"> + <h2>Time by people</h2> + </div> + + <div class="o_timesheet_plan_sale_timesheet_people_time"> + <t t-if="not repartition_employee"> + <p>There are no timesheets for now.</p> + </t> + <t t-if="repartition_employee"> + <div class="table-responsive"> + <table class="table"> + <thead> + <tr> + <th></th> + <th>Employee</th> + <th t-if="is_uom_day" class="text-nowrap text-right pr-5">Days Spent</th> + <th t-else="" class="text-nowrap text-right pr-5">Hours Spent</th> + <td> + <div class="float-right o_timesheet_plan_badge"> + <span class="badge badge-pill o_progress_billable_time"> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','billable_time')])" tabindex="-1">Billed on Timesheets</a> + </span> + <span class="badge badge-pill o_progress_billable_fixed"> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','billable_fixed')])" tabindex="-1">Billed at a Fixed price</a> + </span> + <span t-if="dashboard['time']['non_billable_project'] != 0" class="badge badge-pill o_progress_non_billable_project"> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable_project')])" tabindex="-1">No task found</a> + </span> + <span t-if="dashboard['time']['non_billable_timesheet'] != 0" class="badge badge-pill o_progress_non_billable_timesheet"> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable_timesheet')])" tabindex="-1">Non billable timesheets</a> + </span> + <span class="badge badge-pill o_progress_non_billable"> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('timesheet_invoice_type','=','non_billable')])" tabindex="-1">Non billable tasks</a> + </span> + <!-- only show the canceled pill if there were timesheets on canceled so --> + <t t-if="sum([employee.get('canceled', 0.0) for employee in repartition_employee.values()]) > 0"> + <span class="badge badge-pill o_progress_canceled"> + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain + [('so_line.state', '=', 'cancel')])" tabindex="-1">Cancelled</a> + </span> + </t> + </div> + </td> + </tr> + </thead> + <tbody> + <t t-foreach="repartition_employee" t-as="employee_id"> + <t t-set="employee" t-value="repartition_employee[employee_id]"/> + <tr> + <td style="width: 3%; vertical-align: middle;"> + <img class="img rounded-circle mr-2 mb-2" t-attf-src="/web/image?model=hr.employee&field=image_128&id=#{employee['employee_id']}" t-att-title="employee['employee_name']" t-att-alt="employee['employee_name']" width="25" height="25"/> + </td> + <td style="width: 15%; vertical-align: middle;" > + <a type="action" data-model="account.analytic.line" t-att-data-domain="json.dumps(timesheet_domain)" t-att-data-context="json.dumps({'search_default_employee_id': employee_id})" data-views="[[0, "list"]]" tabindex="-1"> + <t t-esc="employee['employee_name']"/> + </a> + </td> + <td class="text-right pr-5" style="width: 10%; vertical-align: middle;"> + <t t-if="is_uom_day" t-esc="employee['total']" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="employee['total']" t-options="{'widget': 'float_time'}"/> + </td> + <td style="vertical-align:middle"> + <div class="border rounded"> + <div t-if="repartition_employee_max" class="progress" t-attf-style="width: {{max(0, employee['total'] / repartition_employee_max * 100)}}%; margin-bottom: 0em;"> + + <t t-set="total" t-value="employee['total'] or 1.0" /> + <t t-call="sale_timesheet.progressbar"> + <t t-set="label">Billed on Timesheets</t> + <t t-set="key" t-translation="off">billable_time</t> + </t> + <t t-call="sale_timesheet.progressbar"> + <t t-set="label">Billed at a Fixed price</t> + <t t-set="key" t-translation="off">billable_fixed</t> + </t> + <t t-call="sale_timesheet.progressbar"> + <t t-set="label">No task found</t> + <t t-set="key" t-translation="off">non_billable_project</t> + </t> + <t t-call="sale_timesheet.progressbar"> + <t t-set="label">Non billable timesheets</t> + <t t-set="key" t-translation="off">non_billable_timesheet</t> + </t> + <t t-call="sale_timesheet.progressbar"> + <t t-set="label">Non billable tasks</t> + <t t-set="key" t-translation="off">non_billable</t> + </t> + <t t-call="sale_timesheet.progressbar"> + <t t-set="label">Cancelled</t> + <t t-set="key" t-translation="off">canceled</t> + </t> + </div> + </div> + </td> + </tr> + </t> + </tbody> + </table> + </div> + </t> + </div> + + <div class="o_title"> + <h2>Timesheets</h2> + </div> + + <!-- NOTE: this template to display a table works whatever the length of the rows, as project_timesheet_forecast_sale extends the table to add forecasts --> + <div class="o_project_plan_project_timesheet_forecast"> + <t t-if="timesheet_forecast_table and timesheet_forecast_table['rows']"> + <div class="table-responsive"> + <table class="table"> + <thead> + <tr> + <th></th> + <th colspan="5" id="table_plan_title" class="o_right_bordered"><h3>Timesheets</h3></th> + <th colspan="2" id="table_plan_total"></th> + </tr> + <tr> + <t t-foreach="timesheet_forecast_table['header']" t-as="header_val"> + <th t-att-class="'o_right_bordered' if header_val_index in [5,10] else ''"> + <span t-att-title="header_val['tooltip']"><t t-esc="header_val['label']"/></span> + </th> + </t> + </tr> + </thead> + <tbody> + <t t-set="row_is_milestone" t-value="False"/> + <t t-set="current_order" t-value="False"/> + <t t-set="current_order_line" t-value="False"/> + <t t-foreach="timesheet_forecast_table['rows']" t-as="row"> + <t t-set="row_type" t-value="row[0].get('type')"/> + <t t-if="row_type == 'sale_order_line'"> + <t t-set="row_is_milestone" t-value="row[0].get('is_milestone')"/> + </t> + <t t-if="row_type == 'sale_order'"> + <t t-set="current_order" t-value="False"/> + <t t-set="current_order_line" t-value="False"/> + </t> + <t t-if="row_type == 'sale_order_line'"> + <t t-set="current_order_line" t-value="False"/> + </t> + <t t-set="foldable" t-value="row[0].get('has_children')"/> + <tr t-att-class="'o_timesheet_forecast_' + row_type + ' sale_order_' + str(current_order) + ' sale_order_line_' + str(current_order_line)" + t-att-style="'display: none;' if row_type not in ('sale_order', 'sale_order_line') else ''"> + <t t-foreach="row" t-as="row_value"> + <td t-att-class="'o_right_bordered' if row_value_index in [5,10] else '' + ' text-center' if row_value_index != 0 else ''"> + <t t-if="row_value_index == 0"> + <span t-if="foldable" t-att-class="('fa fa-caret-down' if row_type == 'sale_order' else 'fa fa-caret-right') + (' project_overview_foldable' if foldable else '')" + style="cursor: pointer;" t-att-data-model="row[0].get('res_model')" t-att-data-res-id="row[0].get('res_id')"/> + <t t-if="row_type == 'sale_order'"> + <t t-if="env.user.has_group('sales_team.group_sale_salesman')"> + <a type="action" t-att-data-model="row_value['res_model']" t-att-data-res-id="row_value['res_id']" t-att-class="'o_timesheet_plan_redirect' if row_value['res_id'] else ''"> + <t t-esc="row_value.get('label')"/> + </a> + </t> + <t t-else=""> + <t t-esc="row_value.get('label')"/> + </t> + <span t-if="row_value.get('canceled')" class="badge badge-pill o_canceled_tag"> + Cancelled + </span> + </t> + <t t-if="row_type != 'sale_order'"> + <t t-if="not row_is_milestone"> + <span><t t-esc="row_value.get('label')"/></span> + </t> + <t t-if="row_is_milestone"> + <span><i><t t-esc="row_value.get('label')"/></i></span> + </t> + </t> + </t> + <t t-if="row_value_index != 0"> + <t t-if="row_value_index < len(row)-2"> + <t t-if="row_is_milestone"> + <i t-att-class="'text-muted' if not row_value else ''"> + <t t-if="is_uom_day" t-esc="row_value" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="row_value" t-options="{'widget': 'float_time'}"/> + </i> + </t> + <t t-else=""> + <span t-att-class="'text-muted' if not row_value else ''"> + <t t-if="is_uom_day" t-esc="row_value" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="row_value" t-options="{'widget': 'float_time'}"/> + </span> + </t> + </t> + <t t-else=""> + <t t-if="not row_is_milestone and not row[0].get('type') == 'hr_employee'"> + <span t-att-class="'text-muted' if not row_value else ''"> + <t t-if="is_uom_day" t-esc="row_value" t-options="{'widget': 'timesheet_uom'}"/> + <t t-else="" t-esc="row_value" t-options="{'widget': 'float_time'}"/> + </span> + </t> + </t> + </t> + </td> + </t> + </tr> + <t t-if="row_type == 'sale_order_line'"> + <t t-set="current_order_line" t-value="row[0].get('res_id')"/> + </t> + <t t-if="row_type == 'sale_order'"> + <t t-set="current_order" t-value="row[0].get('res_id')"/> + </t> + </t> + </tbody> + </table> + </div> + </t> + </div> + + </div> + </div> + </div> + </div> + </qweb> + </field> + </record> + + <template id="progressbar" name="project overview progressbar segments"> + <t t-set="amount" t-value="employee[key]"/> + <t t-if="amount > 0"> + <t t-set="title"> + <t t-esc="label"/>: <t t-if="is_uom_day" t-esc="amount" t-options="{'widget': 'timesheet_uom'}"/><t t-else="" t-esc="amount" t-options="{'widget': 'float_time'}"/> + </t> + <a t-attf-class="progress-bar o_progress_{{key}}" + t-attf-style="width: {{amount / total * 100}}%" + type="action" data-model="account.analytic.line" + t-att-data-domain="employee['__domain_' + key]" + > + <span t-att-title="title" style="font-size: 0px; width: 100%; height: 100%;"> + <t t-esc="label" /> + </span> + </a> + </t> + </template> + +</odoo> diff --git a/addons/sale_timesheet/views/hr_timesheet_views.xml b/addons/sale_timesheet/views/hr_timesheet_views.xml new file mode 100644 index 00000000..782d2c07 --- /dev/null +++ b/addons/sale_timesheet/views/hr_timesheet_views.xml @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="timesheet_view_search" model="ir.ui.view"> + <field name="name">account.analytic.line.search</field> + <field name="model">account.analytic.line</field> + <field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='department_id']" position="after"> + <field name="timesheet_invoice_type"/> + </xpath> + <xpath expr="//filter[@name='month']" position="before"> + <filter name="billable_timesheet" string="Billable" domain="[('so_line', '!=', False), ('non_allow_billable', '=', False)]"/> + <filter name="non_billable_timesheet" string="Non Billable" domain="['|', ('so_line', '=', False), ('non_allow_billable', '=', True)]"/> + <separator/> + <filter name="billable_time" string="Billed on Timesheets" domain="[('timesheet_invoice_type', '=', 'billable_time')]"/> + <filter name="billable_fixed" string="Billed at a Fixed Price" domain="[('timesheet_invoice_type', '=', 'billable_fixed')]"/> + <filter name="non_billable" string="Non Billable Tasks" domain="[('timesheet_invoice_type', '=', 'non_billable')]"/> + <separator/> + </xpath> + <xpath expr="//filter[@name='groupby_department']" position="before"> + <filter string="Billable Type" name="groupby_timesheet_invoice_type" domain="[]" context="{'group_by': 'timesheet_invoice_type'}"/> + </xpath> + </field> + </record> + + <record id="timesheet_view_pivot_revenue" model="ir.ui.view"> + <field name="name">account.analytic.line.pivot.revenue</field> + <field name="model">account.analytic.line</field> + <field name="arch" type="xml"> + <pivot string="Timesheet" sample="1"> + <field name="employee_id" type="row"/> + <field name="date" interval="month" type="col"/> + <field name="unit_amount" type="measure"/> + </pivot> + </field> + </record> + + <record id="timesheet_view_tree_sale" model="ir.ui.view"> + <field name="name">account.analytic.line.view.tree.with.allow.billable</field> + <field name="model">account.analytic.line</field> + <field name="inherit_id" ref="hr_timesheet.timesheet_view_tree_user"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='user_id']" position="after"> + <field name="non_allow_billable" attrs="{'invisible': [('non_allow_billable', '=', False)]}" optional="hide"/> + </xpath> + </field> + </record> + + <!-- + Timesheet from Sales Order + --> + <record id="timesheet_action_from_sales_order" model="ir.actions.act_window"> + <field name="name">Timesheets</field> + <field name="res_model">account.analytic.line</field> + <field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/> + <field name="domain">[('project_id', '!=', False)]</field> + </record> + + <record id="timesheet_action_from_sales_order_tree" model="ir.actions.act_window.view"> + <field name="sequence" eval="4"/> + <field name="view_mode">tree</field> + <field name="view_id" ref="hr_timesheet.timesheet_view_tree_user"/> + <field name="act_window_id" ref="timesheet_action_from_sales_order"/> + </record> + + <record id="timesheet_action_from_sales_order_form" model="ir.actions.act_window.view"> + <field name="sequence" eval="5"/> + <field name="view_mode">form</field> + <field name="view_id" ref="hr_timesheet.timesheet_view_form_user"/> + <field name="act_window_id" ref="timesheet_action_from_sales_order"/> + </record> + + <!-- + Reporting + --> + <record id="timesheet_action_billing_report" model="ir.actions.act_window"> + <field name="name">Timesheets By Billing Type</field> + <field name="res_model">account.analytic.line</field> + <field name="view_mode">pivot,graph</field> + <field name="domain">[('project_id', '!=', False)]</field> + <field name="context">{ + 'search_default_groupby_date': 1, + }</field> + <field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/> + </record> + + <record id="view_hr_timesheet_line_pivot_billing_rate" model="ir.ui.view"> + <field name="name">account.analytic.line.pivot.billing.rate</field> + <field name="model">account.analytic.line</field> + <field name="arch" type="xml"> + <pivot string="Timesheet" sample="1"> + <field name="timesheet_invoice_type" type="col"/> + <field name="unit_amount" type="measure" widget="timesheet_uom"/> + <field name="amount" string="Timesheet Costs"/> + </pivot> + </field> + </record> + + <record id="timesheet_action_view_report_by_billing_rate_pivot" model="ir.actions.act_window.view"> + <field name="sequence" eval="5"/> + <field name="view_mode">pivot</field> + <field name="view_id" ref="view_hr_timesheet_line_pivot_billing_rate"/> + <field name="act_window_id" ref="timesheet_action_billing_report"/> + </record> + + <record id="timesheet_action_view_report_by_billing_rate_graph" model="ir.actions.act_window.view"> + <field name="sequence" eval="6"/> + <field name="view_mode">graph</field> + <field name="view_id" ref="hr_timesheet.view_hr_timesheet_line_graph"/> + <field name="act_window_id" ref="timesheet_action_billing_report"/> + </record> + + <menuitem id="menu_timesheet_billing_analysis" + parent="hr_timesheet.menu_timesheets_reports_timesheet" + action="timesheet_action_billing_report" + name="By Billing Type" + sequence="40"/> + + <!-- + Plan + --> + <record id="timesheet_action_plan_pivot" model="ir.actions.act_window"> + <field name="name">Timesheet</field> + <field name="res_model">account.analytic.line</field> + <field name="view_mode">pivot,tree,form</field> + <field name="domain">[('project_id', '!=', False)]</field> + <field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/> + </record> + + <record id="timesheet_action_from_plan" model="ir.actions.act_window"> + <field name="name">Timesheet</field> + <field name="res_model">account.analytic.line</field> + <field name="view_mode">tree,form</field> + <field name="domain">[('project_id', '!=', False)]</field> + <field name="search_view_id" ref="hr_timesheet.hr_timesheet_line_search"/> + </record> + +</odoo> diff --git a/addons/sale_timesheet/views/product_views.xml b/addons/sale_timesheet/views/product_views.xml new file mode 100644 index 00000000..d238c6ea --- /dev/null +++ b/addons/sale_timesheet/views/product_views.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="view_product_timesheet_form" model="ir.ui.view"> + <field name="name">product.template.timesheet.form</field> + <field name="model">product.template</field> + <field name="inherit_id" ref="sale.product_template_form_view_invoice_policy"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='invoice_policy']" position="attributes"> + <attribute name="invisible">False</attribute> + <attribute name="attrs">{'invisible': [('type','==','service')]}</attribute> + </xpath> + <xpath expr="//field[@name='service_type']" position="after"> + <field name="service_policy" widget="radio" attrs="{'invisible': [('type','!=','service')]}"/> + </xpath> + </field> + </record> + + <record id="product_template_view_search_sale_timesheet" model="ir.ui.view"> + <field name="name">product.template.search.timesheet</field> + <field name="model">product.template</field> + <field name="inherit_id" ref="product.product_template_search_view"/> + <field name="mode">primary</field> + <field name="arch" type="xml"> + <xpath expr="//filter[@name='consumable']" position="after"> + <separator/> + <filter string="Time-based services" name="product_time_based" domain="[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')]"/> + <filter string="Fixed price services" name="product_service_fixed" domain="[('type', '=', 'service'), ('invoice_policy', '=', 'order'), ('service_type', '=', 'timesheet')]"/> + <filter string="Milestone services" name="product_service_milestone" domain="[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'manual')]"/> + </xpath> + </field> + </record> + + <record id="product_template_action_default_services" model="ir.actions.act_window"> + <field name="name">Products</field> + <field name="res_model">product.template</field> + <field name="view_mode">tree,form</field> + <field name="search_view_id" ref="sale_timesheet.product_template_view_search_sale_timesheet"/> + <field name="context">{'search_default_services': 1, 'default_type': 'service'}</field> + </record> + +</odoo> diff --git a/addons/sale_timesheet/views/project_task_views.xml b/addons/sale_timesheet/views/project_task_views.xml new file mode 100644 index 00000000..7291b612 --- /dev/null +++ b/addons/sale_timesheet/views/project_task_views.xml @@ -0,0 +1,209 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <record id="project_project_view_form" model="ir.ui.view"> + <field name="name">project.project.form.inherit</field> + <field name="model">project.project</field> + <field name="inherit_id" ref="hr_timesheet.project_invoice_form"/> + <field name="arch" type="xml"> + <div class="oe_button_box" position="inside"> + <button string="Project Overview" class="oe_stat_button" type="object" name="action_view_timesheet" icon="fa-puzzle-piece" attrs="{'invisible': [('allow_billable', '=', False)]}"/> + </div> + <xpath expr="//button[@name='action_view_so']" position="attributes"> + <attribute name="attrs">{'invisible': ['|', '|', ('allow_billable', '=', False), ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_project')]}</attribute> + </xpath> + <xpath expr="//header" position="inside"> + <button name="action_make_billable" string="Create Sales Order" type="object" attrs="{'invisible': [('display_create_order', '=', False)]}" groups="sales_team.group_sale_salesman"/> + </xpath> + <xpath expr="//page[@name='settings']" position="after"> + <page name="billing_employee_rate" string="Invoicing" attrs="{'invisible': [('allow_billable', '=', False)]}"> + <group> + <group> + <field name="display_create_order" invisible="1"/> + <field name="bill_type" widget="radio"/> + <field name="pricing_type" attrs="{'invisible': ['|', ('allow_billable', '=', False), ('bill_type', '!=', 'customer_project')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" widget="radio"/> + <field name="timesheet_product_id" string="Service" attrs="{'invisible': ['|', '|', ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), ('bill_type', '!=', 'customer_task')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}" context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}"/> + <field name="sale_order_id" attrs="{'invisible': [('bill_type', '!=', 'customer_project')], 'readonly': [('sale_order_id', '!=', False)]}" force_save="1" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/> + <field name="sale_line_id" string="Default Sales Order Item" attrs="{'invisible': [('bill_type', '!=', 'customer_project')]}" options="{'no_create': True, 'no_edit': True, 'delete': False, 'no_open': True}"/> + </group> + </group> + <field name="sale_line_employee_ids" attrs="{'invisible': ['|', ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'employee_rate')]}"> + <tree editable="top"> + <field name="company_id" invisible="1"/> + <field name="project_id" invisible="1"/> + <field name="employee_id" options="{'no_create': True}"/> + <field name="timesheet_product_id" attrs="{'column_invisible': [('parent.sale_order_id', '!=', False)]}" invisible="1"/> + <field name="sale_line_id" attrs="{'required': True}" options="{'no_create': True}" domain="[('order_id','=',parent.sale_order_id), ('is_service', '=', True)]"/> + <field name="price_unit" widget="monetary" force_save="1" options="{'currency_field': 'currency_id'}"/> + <field name="currency_id" invisible="1"/> + </tree> + </field> + </page> + </xpath> + <xpath expr="//div[@id='timesheet_settings']/.." position="after"> + <div class="row mt16 o_settings_container"> + <div class="col-lg-6 o_setting_box" id="allow_billable_container"> + <div class="o_setting_left_pane"> + <field name="allow_billable"/> + </div> + <div class="o_setting_right_pane"> + <label for="allow_billable"/> + <div class="text-muted" id="allow_billable_setting"> + Invoice your time and material to customers + </div> + </div> + </div> + </div> + </xpath> + </field> + </record> + + <record id="project_project_view_form_salesman" model="ir.ui.view"> + <field name="name">project.project.form.inherit.salesman</field> + <field name="model">project.project</field> + <field name="inherit_id" ref="sale_timesheet.project_project_view_form"/> + <field name="groups_id" eval="[(4, ref('sales_team.group_sale_salesman'))]"/> + <field name="arch" type="xml"> + <field name="sale_order_id" position="attributes"> + <attribute name="options">{'no_create': True, 'no_edit': True, 'delete': False}</attribute> + </field> + <field name="sale_line_id" position="attributes"> + <attribute name="options">{'no_create': True, 'no_edit': True, 'delete': False}</attribute> + </field> + </field> + </record> + + <record id="project_project_view_form_simplified_inherit" model="ir.ui.view"> + <field name="name">project.project.view.form.simplified.inherit</field> + <field name="model">project.project</field> + <field name="inherit_id" ref="hr_timesheet.project_project_view_form_simplified_inherit_timesheet"/> + <field name="arch" type="xml"> + <field name="allow_timesheets" position="after"> + <field name="company_id" invisible="1"/> + <field name="allow_billable"/> + </field> + </field> + </record> + + <record id="project_project_view_kanban_inherit_sale_timesheet" model="ir.ui.view"> + <field name="name">project.project.kanban.inherit.sale.timesheet</field> + <field name="model">project.project</field> + <field name="inherit_id" ref="hr_timesheet.view_project_kanban_inherited"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='allow_timesheets']" position="after"> + <field name="allow_billable"/> + <field name="warning_employee_rate" invisible="1"/> + </xpath> + <xpath expr="//div[hasclass('o_primary')]//span" position="inside"> + <t t-if="1 == 0"> + <i class="ml-2 fa fa-exclamation-triangle text-danger small" role="img" title="Some of the employees who are recording time on this project are not linked to any Sales Order Item. This means that their time will be considered as non-billable." aria-label="Some of the employees who are recording time on this project are not linked to any ales Order Item. This means that their time will be considered as non-billable."/> + </t> + </xpath> + <xpath expr="//a[@name='action_view_account_analytic_line']" position="attributes"> + <attribute name="t-if">record.analytic_account_id.raw_value and !record.allow_timesheets.raw_value and record.allow_billable.raw_value</attribute> + </xpath> + <xpath expr="//a[@t-if='record.allow_timesheets.raw_value']" position="replace"> + <a t-if="record.allow_timesheets.raw_value and record.allow_billable.raw_value" name="action_view_timesheet" type="object" class="o_project_kanban_box o_project_timesheet_box" groups="project.group_project_manager"> + <div> + <span class="o_label">Overview</span> + </div> + </a> + </xpath> + </field> + </record> + + <record id="view_sale_service_inherit_form2" model="ir.ui.view"> + <field name="name">sale.service.form.view.inherit</field> + <field name="model">project.task</field> + <field name="groups_id" eval="[(4, ref('base.group_user'))]"/> + <field name="inherit_id" ref="project.view_task_form2"/> + <field name="arch" type="xml"> + <xpath expr="//header" position='inside'> + <field name="allow_billable" invisible="1"/> + <field name="display_create_order" invisible="1"/> + <button name="action_make_billable" string="Create Sales Order" type="object" attrs="{'invisible': [('display_create_order', '=', False)]}" groups="sales_team.group_sale_salesman" invisible="1"/> + </xpath> + <xpath expr="//field[@name='email_cc']" position="after"> + <field name="analytic_account_id" groups="base.group_no_one"/> + </xpath> + </field> + </record> + + <record id="project_task_view_form_inherit_sale_timesheet" model="ir.ui.view"> + <field name="name">project.task.form.inherit.timesheet</field> + <field name="model">project.task</field> + <field name="inherit_id" ref="project.view_task_form2"/> + <field name="groups_id" eval="[(6,0, (ref('hr_timesheet.group_hr_timesheet_user'),))]"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='timesheet_ids']/tree" position="attributes"> + <attribute name="decoration-muted">timesheet_invoice_id != False</attribute> + </xpath> + <xpath expr="//field[@name='user_id']" position="after"> + <field name="is_project_map_empty" invisible="1"/> + <field name="has_multi_sol" invisible="1"/> + </xpath> + <xpath expr="//field[@name='partner_phone']" position="after"> + <field name="bill_type" invisible="1"/> + <field name="pricing_type" invisible="1"/> + <field name="timesheet_product_id" invisible="1"/> + <field name="non_allow_billable" attrs="{'invisible': ['|', '|', '|', ('allow_billable', '=', False), ('allow_timesheets', '=', False), ('pricing_type', '!=', 'employee_rate'), ('bill_type', '=', 'customer_task')]}" invisible="1"/> + </xpath> + <xpath expr="//field[@name='timesheet_ids']/tree/field[@name='unit_amount']" position="before"> + <field name="timesheet_invoice_id" invisible="1"/> + <field name="so_line" readonly="1" attrs="{'column_invisible': [('parent.allow_billable', '=', False)]}" context="{'with_remaining_hours': True}" optional="hide"/> + </xpath> + <xpath expr="//field[@name='timesheet_ids']/tree" position="inside"> + <field name="non_allow_billable" attrs="{'column_invisible': ['|', '&', '&', '|', ('parent.bill_type', '!=', 'customer_project'), ('parent.pricing_type', '!=', 'employee_rate'), ('parent.timesheet_product_id', '=', False), ('parent.sale_line_id', '=', False), '&', ('parent.bill_type', '=', 'customer_project'), ('parent.pricing_type', '=', 'employee_rate')]}" invisible="1"/> + </xpath> + <xpath expr="//field[@name='remaining_hours']" position="after"> + <field name="remaining_hours_available" invisible="1"/> + <span id="remaining_hours_so_label" attrs="{'invisible': ['|', '|', '|', '|', ('allow_billable', '=', False), ('sale_order_id', '=', False), ('partner_id', '=', False), ('sale_line_id', '=', False), ('remaining_hours_available', '=', False)]}"> + <label class="font-weight-bold" for="remaining_hours_so" string="Remaining Hours on SO" + attrs="{'invisible': ['|', ('encode_uom_in_days', '=', True), ('remaining_hours_so', '<', 0)]}"/> + <label class="font-weight-bold" for="remaining_hours_so" string="Remaining Days on SO" + attrs="{'invisible': ['|', ('encode_uom_in_days', '=', False), ('remaining_hours_so', '<', 0)]}"/> + <label class="font-weight-bold text-danger" for="remaining_hours_so" string="Remaining Hours on SO" + attrs="{'invisible': ['|', ('encode_uom_in_days', '=', True), ('remaining_hours_so', '>=', 0)]}"/> + <label class="font-weight-bold text-danger" for="remaining_hours_so" string="Remaining Days on SO" + attrs="{'invisible': ['|', ('encode_uom_in_days', '=', False), ('remaining_hours_so', '>=', 0)]}"/> + </span> + <field name="remaining_hours_so" nolabel="1" widget="timesheet_uom" attrs="{'invisible': ['|', '|', '|', '|', ('allow_billable', '=', False), ('sale_order_id', '=', False), ('partner_id', '=', False), ('sale_line_id', '=', False), ('remaining_hours_available', '=', False)]}"></field> + </xpath> + </field> + </record> + + <record id="view_task_form2_inherit_sale_timesheet" model="ir.ui.view"> + <field name="name">view.task.form2.inherit</field> + <field name="model">project.task</field> + <field name="inherit_id" ref="project.view_task_form2"/> + <field name="arch" type="xml"> + <xpath expr="//field[@name='sale_line_id']" position="attributes"> + <attribute name="context">{'with_remaining_hours': True}</attribute> + <attribute name="attrs"> + {'invisible': ['|', ('allow_billable', '=', False), ('partner_id', '=', False)]} + </attribute> + </xpath> + <xpath expr="//field[@name='sale_order_id']" position="attributes"> + <attribute name="invisible">1</attribute> + </xpath> + </field> + </record> + + <record id="quick_create_task_form_sale_timesheet" model="ir.ui.view"> + <field name="name">project.task.form.inherit.timesheet</field> + <field name="model">project.task</field> + <field name="inherit_id" ref="project.quick_create_task_form"/> + <field name="arch" type="xml"> + <field name="project_id" position="after"> + <field name="timesheet_product_id" invisible="1"/> + </field> + </field> + </record> + + <record id="project_timesheet_action_client_timesheet_plan" model="ir.actions.act_window"> + <field name="name">Overview</field> + <field name="res_model">project.project</field> + <field name="view_mode">qweb</field> + </record> + +</odoo> diff --git a/addons/sale_timesheet/views/report_invoice.xml b/addons/sale_timesheet/views/report_invoice.xml new file mode 100644 index 00000000..dbf89fc2 --- /dev/null +++ b/addons/sale_timesheet/views/report_invoice.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + <template id="report_invoice_document" inherit_id="account.report_invoice_document"> + <xpath expr="//div[hasclass('page')]/h2" position="after"> + <a t-if="report_type == 'html' and o.move_type == 'out_invoice' and o.state in ('draft', 'posted') and o.timesheet_count > 0" target="_blank" t-att-href="'/my/timesheets?search_in=invoice_id&search=%s' % o.id">View Timesheets</a> + </xpath> + </template> + </data> +</odoo> diff --git a/addons/sale_timesheet/views/res_config_settings_views.xml b/addons/sale_timesheet/views/res_config_settings_views.xml new file mode 100644 index 00000000..ffefb17b --- /dev/null +++ b/addons/sale_timesheet/views/res_config_settings_views.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + + <record id="res_config_settings_view_form" model="ir.ui.view"> + <field name="name">res.config.settings.view.form.inherit.sale.timesheet</field> + <field name="model">res.config.settings</field> + <field name="priority" eval="1"/> + <field name="inherit_id" ref="hr_timesheet.res_config_settings_view_form"/> + <field name="arch" type="xml"> + <xpath expr="//div[@name='section_leaves']" position="before"> + <h2>Billing</h2> + <div name="timesheet_billing" class="row mt16 o_settings_container"> + <div class="col-12 col-lg-6 o_setting_box" id="time_billing_setting"> + <div class="o_setting_right_pane"> + <span class="o_form_label">Time Billing</span> + <div class="text-muted"> + Sell services and invoice time spent + </div> + <div class="content-group" name="msg_module_sale_timesheet"> + <div class="mt8"> + <div> + <button name="%(sale_timesheet.product_template_action_default_services)d" string="Configure your services" type="action" class="btn-link" icon="fa-arrow-right"/> + </div> + </div> + </div> + </div> + </div> + </div> + </xpath> + </field> + </record> + +</odoo> diff --git a/addons/sale_timesheet/views/sale_order_views.xml b/addons/sale_timesheet/views/sale_order_views.xml new file mode 100644 index 00000000..261e207a --- /dev/null +++ b/addons/sale_timesheet/views/sale_order_views.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="view_order_form_inherit_sale_timesheet" model="ir.ui.view"> + <field name="name">sale.order.form.sale.timesheet</field> + <field name="model">sale.order</field> + <field name="inherit_id" ref="sale_project.view_order_form_inherit_sale_project"/> + <field name="arch" type="xml"> + <data> + <xpath expr="//button[@name='action_view_project_ids']" position="attributes"> + <attribute name="string">Project Overview</attribute> + </xpath> + <xpath expr="//button[@name='action_view_invoice']" position="before"> + <field name="timesheet_count" invisible="1" /> + <button type="object" + name="action_view_timesheet" + class="oe_stat_button" + icon="fa-clock-o" + attrs="{'invisible': [('timesheet_count', '=', 0)]}" + groups="hr_timesheet.group_hr_timesheet_user"> + <div class="o_field_widget o_stat_info"> + <span class="o_stat_value"> + <field name="timesheet_total_duration" class="mr4" widget="statinfo" nolabel="1"/> + <field name="timesheet_encode_uom_id" options="{'no_open' : True}"/> + </span> + <span class="o_stat_text">Recorded</span> + </div> + </button> + </xpath> + </data> + </field> + </record> +</odoo> diff --git a/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml b/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml new file mode 100644 index 00000000..76d6d52f --- /dev/null +++ b/addons/sale_timesheet/views/sale_timesheet_portal_templates.xml @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + + <template id="assets_frontend" name="report timesheet assets" inherit_id="web.assets_frontend"> + <xpath expr="." position="inside"> + <link rel="stylesheet" type="text/scss" href="/sale_timesheet/static/src/scss/sale_timesheet_portal.scss"/> + </xpath> + </template> + + <!-- TODO: [XBO] Remove me in master --> + <template id="portal_invoice_page_inherit_timesheet" inherit_id="account.portal_invoice_page"> + <xpath expr="//t[@t-call='portal.portal_record_sidebar']//div[hasclass('o_download_pdf')]" position="after"> + <t t-if="1 == 0"> + <li t-if="timesheets" class="list-group-item flex-grow-1" > + <a href="#accordion">Timesheets</a> + </li> + </t> + </xpath> + + <xpath expr="//div[@id='invoice_content']//div[hasclass('o_portal_html_view')]" position="after"> + <t t-if="1 == 0"> + <div t-if="timesheets" class="container"> + <div id="accordion" class="o_timesheet_accordion mt-4"> + <div class="card mb-0"> + <div class="card-header"> + <h5 class="mb0"> + <a class="card-title" data-toggle="collapse" href="#collapseTimesheet"> + Timesheets + </a> + </h5> + </div> + <div id="collapseTimesheet" class="card-body show" data-parent="#accordion"> + <t t-set="nr_tasks" t-value="len(timesheets.mapped('task_id'))"/> + <t t-set="nr_projects" t-value="len(timesheets.mapped('project_id'))"/> + <table class="table table-sm"> + <thead> + <tr> + <th>Date</th> + <th>Employee</th> + <th t-if="nr_projects > 1">Project</th> + <th t-if="nr_tasks > 0">Task</th> + <th>Description</th> + <th t-if="timesheets[0]._is_timesheet_encode_uom_day()" class="text-right">Duration (days)</th> + <th t-else="" class="text-right">Duration (hours)</th> + </tr> + </thead> + <tr t-foreach="timesheets" t-as="timesheet"> + <td><t t-esc="timesheet.date" t-options='{"widget": "date"}'/></td> + <td><t t-esc="timesheet.employee_id.name"/></td> + <td t-if="nr_projects > 1"><span t-field="timesheet.project_id"/></td> + <td t-if="nr_tasks > 0"><span t-field="timesheet.task_id"/></td> + <td><t t-esc="timesheet.name"/></td> + <td class="text-right"> + <span t-if="timesheet._is_timesheet_encode_uom_day()" t-esc="timesheet._get_timesheet_time_day()" t-options='{"widget": "timesheet_uom"}'/> + <span t-else="" t-field="timesheet.unit_amount" t-options='{"widget": "float_time"}'/> + </td> + </tr> + </table> + </div> + </div> + </div> + </div> + </t> + </xpath> + </template> + + <template id="sale_order_portal_content_inherit" inherit_id="sale.sale_order_portal_content"> + <xpath expr="//td[@id='product_name']" position="inside"> + <a t-if="timesheets and len(timesheets.filtered(lambda t: t.so_line == line)) > 0" t-att-href="'/my/timesheets?search_in=sol_id&search=%s' % line.id">View Timesheets</a> + </xpath> + </template> + + <!-- TODO: [XBO] Remove me in master --> + <template id="sale_order_portal_template_inherit" inherit_id="sale.sale_order_portal_template"> + <xpath expr="//t[@t-call='portal.portal_record_sidebar']//div[hasclass('o_download_pdf')]" position="after"> + <t t-if="1 == 0"> + <li t-if="timesheets" class="list-group-item flex-grow-1" > + <a href="#accordion">Timesheets</a> + </li> + </t> + </xpath> + + <xpath expr="//div[@id='sale_order_communication']" position="before"> + <t t-if="1 == 0"> + <div t-if="timesheets" class="container"> + <div id="accordion" class="o_timesheet_accordion mt-4"> + <div class="card mb-0"> + <div class="card-header"> + <h5 class="mb0"> + <a class="card-title" data-toggle="collapse" href="#collapseTimesheet"> + Timesheets + </a> + </h5> + </div> + <div id="collapseTimesheet" class="card-body show" data-parent="#accordion"> + <t t-set="nr_tasks" t-value="len(timesheets.mapped('task_id'))"/> + <t t-set="nr_projects" t-value="len(timesheets.mapped('project_id'))"/> + <table class="table table-sm"> + <thead> + <tr> + <th>Date</th> + <th>Employee</th> + <th t-if="nr_projects > 1">Project</th> + <th t-if="nr_tasks > 0">Task</th> + <th>Description</th> + <th t-if="timesheets[0]._is_timesheet_encode_uom_day()" class="text-right">Duration (days)</th> + <th t-else="" class="text-right">Duration (hours)</th> + </tr> + </thead> + <tr t-foreach="timesheets" t-as="timesheet"> + <td><t t-esc="timesheet.date" t-options='{"widget": "date"}'/></td> + <td><t t-esc="timesheet.employee_id.name"/></td> + <td t-if="nr_projects > 1"><span t-field="timesheet.project_id"/></td> + <td t-if="nr_tasks > 0"><span t-field="timesheet.task_id"/></td> + <td><t t-esc="timesheet.name"/></td> + <td class="text-right"> + <span t-if="timesheet._is_timesheet_encode_uom_day()" t-esc="timesheet._get_timesheet_time_day()" t-options='{"widget": "timesheet_uom"}'/> + <span t-else="" t-field="timesheet.unit_amount" t-options='{"widget": "float_time"}'/> + </td> + </tr> + </table> + </div> + </div> + </div> + </div> + </t> + </xpath> + </template> + + <template id="portal_my_timesheets_inherit" inherit_id="hr_timesheet.portal_my_timesheets"> + <xpath expr="//thead/tr[contains(@t-attf-class, 'thead-light')]" position="inside"> + <t t-elif="groupby == 'sol'"> + <t t-set="sol" t-value="timesheets[0].so_line"/> + <th colspan="5"> + <t t-if="sol"> + <em class="font-weight-normal text-muted">Timesheets for sales order item:</em> + <span t-field="sol.display_name"/> + <t t-if="sol.remaining_hours_available"> + <span class="text-muted font-weight-normal">(<span t-field="sol.product_uom_qty" t-options='{"widget": "float_time"}'></span> <span t-field="sol.product_uom.display_name"></span> Ordered, <span t-field="sol.remaining_hours" t-options='{"widget": "float_time"}'></span> <span t-field="sol.product_uom.display_name"></span> Remaining)</span> + </t> + </t> + </th> + <th colspan="1" class="text-right text-muted font-weight-normal"> + <t t-if="is_uom_day"> + Total: <span t-esc="timesheets[0]._convert_hours_to_days(hours_spent)" t-options='{"widget": "timesheet_uom"}'/> + </t> + <t t-else=""> + Total: <span t-esc="hours_spent" t-options='{"widget": "float_time"}'/> + </t> + </th> + </t> + </xpath> + <xpath expr="//thead/tr/th[@t-if='is_uom_day']" position="before"> + <th t-if="not groupby == 'sol'">Sale Order Item</th> + </xpath> + <xpath expr="//tbody//td[hasclass('text-right')]" position="before"> + <td t-if="not groupby == 'sol'"><span t-field="timesheet.so_line" t-att-title="timesheet.so_line.display_name"></span></td> + </xpath> + </template> + +</odoo> |
