Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import timesheets_analysis_report

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="timesheet_sale_page">
<t t-set="show_project" t-value="true"/>
<t t-set="show_task" t-value="true"/>
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div class="page">
<t t-foreach="docs" t-as="doc">
<t t-set="doc_name" t-value="doc.name"/>
<t t-if="with_order_id" t-set="doc_name" t-value="str(doc.order_id.name) +' - '+ str(doc_name)"/>
<t t-elif="doc_name == '/'" t-set="doc_name" t-value="'Draft'"/>
<div class="oe_structure"/>
<div class="row mt8">
<div class="col-12">
<t t-if="doc.timesheet_ids">
<h2>
<br/>
<span>Timesheets for the <t t-out="doc_name"/> <t t-out="record_name"/>
</span>
</h2>
<t t-set='lines' t-value='doc.timesheet_ids'/>
<t t-call="hr_timesheet.timesheet_table"/>
</t>
</div>
</div>
</t>
</div>
</t>
</t>
</template>
<!-- Sale Order Timesheet Report for given timesheets -->
<template id="report_timesheet_sale_order">
<t t-set="record_name">Sales Order Item</t>
<t t-set="with_order_id" t-value="true"/>
<t t-set="docs" t-value="docs.order_line"/>
<t t-call="sale_timesheet.timesheet_sale_page"/>
</template>
<record id="timesheet_report_sale_order" model="ir.actions.report">
<field name="name">Timesheets</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">sale_timesheet.report_timesheet_sale_order</field>
<field name="binding_model_id" ref="model_sale_order"/>
</record>
<!-- Invoice Timesheet Report for given timesheets -->
<template id="report_timesheet_account_move">
<t t-set="record_name">Invoice</t>
<t t-call="sale_timesheet.timesheet_sale_page"/>
</template>
<record id="timesheet_report_account_move" model="ir.actions.report">
<field name="name">Timesheets</field>
<field name="model">account.move</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">sale_timesheet.report_timesheet_account_move</field>
<field name="binding_model_id" ref="model_account_move"/>
</record>
</odoo>

View file

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from odoo.addons.sale_timesheet.models.account import TIMESHEET_INVOICE_TYPES
class TimesheetsAnalysisReport(models.Model):
_inherit = "timesheets.analysis.report"
order_id = fields.Many2one("sale.order", string="Sales Order", readonly=True)
so_line = fields.Many2one("sale.order.line", string="Sales Order Item", readonly=True)
timesheet_invoice_type = fields.Selection(TIMESHEET_INVOICE_TYPES, string="Billable Type", readonly=True)
timesheet_invoice_id = fields.Many2one("account.move", string="Invoice", readonly=True, help="Invoice created from the timesheet")
timesheet_revenues = fields.Float("Timesheet Revenues", readonly=True, help="Number of hours spent multiplied by the unit price per hour/day.")
margin = fields.Float("Margin", readonly=True, help="Timesheets revenues minus the costs")
billable_time = fields.Float("Billable Hours", readonly=True, help="Number of hours/days linked to a SOL.")
non_billable_time = fields.Float("Non-billable Hours", readonly=True, help="Number of hours/days not linked to a SOL.")
@property
def _table_query(self):
return """
SELECT A.*,
(timesheet_revenues + A.amount) AS margin,
(A.unit_amount - billable_time) AS non_billable_time
FROM (
%s %s %s
) A
""" % (self._select(), self._from(), self._where())
@api.model
def _select(self):
return super()._select() + """,
A.order_id AS order_id,
A.so_line AS so_line,
A.timesheet_invoice_type AS timesheet_invoice_type,
A.timesheet_invoice_id AS timesheet_invoice_id,
CASE WHEN A.order_id IS NULL THEN 0 ELSE A.unit_amount * SOL.price_unit * sol_product_uom.factor / a_product_uom.factor END AS timesheet_revenues,
CASE WHEN A.order_id IS NULL THEN 0 ELSE A.unit_amount END AS billable_time
"""
@api.model
def _from(self):
return super()._from() + """
LEFT JOIN sale_order_line SOL ON A.so_line = SOL.id
LEFT JOIN uom_uom sol_product_uom ON sol_product_uom.id=SOL.product_uom
INNER JOIN uom_uom a_product_uom ON a_product_uom.id=A.product_uom_id"""

View file

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="timesheets_analysis_report_pivot_inherit" model="ir.ui.view">
<field name="name">timesheets.analysis.report.pivot</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_pivot_employee"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<record id="timesheets_analysis_report_graph_inherit" model="ir.ui.view">
<field name="name">timesheets.analysis.report.graph</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_pivot_employee"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<!--TO DO: Remove in master and update existing inherit_id-->
<record id="timesheets_analysis_report_graph_timesheet_grid" model="ir.ui.view">
<field name="name">timesheets.analysis.report.graph</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_graph_employee"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<record id="timesheets_analysis_report_pivot_project_inherit" model="ir.ui.view">
<field name="name">timesheets.analysis.report.pivot.project</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_pivot_project"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<record id="timesheets_analysis_report_graph_project_inherit" model="ir.ui.view">
<field name="name">timesheets.analysis.report.graph.project</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_graph_project"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<record id="timesheets_analysis_report_pivot_task_inherit" model="ir.ui.view">
<field name="name">timesheets.analysis.report.pivot.task</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_pivot_task"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<record id="timesheets_analysis_report_graph_task_inherit" model="ir.ui.view">
<field name="name">timesheets.analysis.report.graph.task</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.timesheets_analysis_report_graph_task"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='unit_amount']" position="after">
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</xpath>
</field>
</record>
<record id="timesheets_analysis_report_pivot_invoice_type" model="ir.ui.view">
<field name="name">timesheets.analysis.report.pivot</field>
<field name="model">timesheets.analysis.report</field>
<field name="arch" type="xml">
<pivot string="Timesheets Analysis" sample="1" disable_linking="True">
<field name="date" interval="month" type="row"/>
<field name="timesheet_invoice_type" type="col"/>
<field name="amount" string="Timesheet Costs"/>
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
</pivot>
</field>
</record>
<record id="timesheets_analysis_report_graph_invoice_type" model="ir.ui.view">
<field name="name">timesheets.analysis.report.graph</field>
<field name="model">timesheets.analysis.report</field>
<field name="arch" type="xml">
<graph string="Timesheets" sample="1" js_class="hr_timesheet_graphview" disable_linking="True">
<field name="amount" string="Timesheet Costs"/>
<field name="unit_amount" type="measure" widget="timesheet_uom"/>
<field name="billable_time" widget="timesheet_uom"/>
<field name="non_billable_time" widget="timesheet_uom"/>
<field name="timesheet_invoice_type" type="row"/>
</graph>
</field>
</record>
<record id="hr_timesheet_report_search_sale_timesheet" model="ir.ui.view">
<field name="name">timesheets.analysis.report.search</field>
<field name="model">timesheets.analysis.report</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_report_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='task_id']" position="after">
<field name="order_id" string="Sales Order" groups="sales_team.group_sale_salesman"/>
<field name="so_line" groups="sales_team.group_sale_salesman"/>
</xpath>
<xpath expr="//filter[@name='month']" position="before">
<filter name="billable_fixed" string="Billed at a Fixed Price" domain="[('timesheet_invoice_type', '=', 'billable_fixed')]"
groups="sales_team.group_sale_salesman"/>
<filter name="billable_time" string="Billed on Timesheets" domain="[('timesheet_invoice_type', '=', 'billable_time')]"
groups="sales_team.group_sale_salesman"/>
<filter name="billable_milestones" string="Billed on Milestones" domain="[('timesheet_invoice_type', '=', 'billable_milestones')]"
groups="sales_team.group_sale_salesman"/>
<filter name="billable_manual" string="Billed Manually" domain="[('timesheet_invoice_type', '=', 'billable_manual')]"
groups="sales_team.group_sale_salesman"/>
<filter name="non_billable" string="Non-Billable" domain="[('timesheet_invoice_type', '=', 'non_billable')]"
groups="sales_team.group_sale_salesman"/>
<separator/>
</xpath>
<xpath expr="//filter[@name='groupby_employee']" position="after">
<filter string="Sales Order" name="groupby_sale_order" domain="[]"
context="{'group_by': 'order_id'}" groups="sales_team.group_sale_salesman"/>
<filter string="Sales Order Item" name="groupby_sale_order_item" domain="[]"
context="{'group_by': 'so_line'}" groups="sales_team.group_sale_salesman"/>
<filter string="Invoice" name="groupby_invoice" domain="[]"
context="{'group_by': 'timesheet_invoice_id'}" groups="sales_team.group_sale_salesman"/>
<filter string="Billable Type" name="groupby_timesheet_invoice_type" domain="[]"
context="{'group_by': 'timesheet_invoice_type'}" groups="sales_team.group_sale_salesman"/>
</xpath>
</field>
</record>
<record id="timesheet_action_billing_report" model="ir.actions.act_window">
<field name="name">Timesheets by Billing Type</field>
<field name="res_model">timesheets.analysis.report</field>
<field name="domain">[('project_id', '!=', False)]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No data yet!
</p>
<p>Review your timesheets by billing type and make sure your time is billable.</p>
</field>
<field name="search_view_id" ref="hr_timesheet.hr_timesheet_report_search"/>
<field name="view_mode">pivot,graph</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="timesheets_analysis_report_pivot_invoice_type"/>
<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="timesheets_analysis_report_graph_invoice_type"/>
<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"/>
</odoo>