[IMP] project: Improve global UX (back2basics)
Improve the onboarding of new users and clean a few screens. Task ID: 2237996 closes odoo/odoo#50284 Related: odoo/enterprise#10536 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com> Co-authored-by: dmonzonis <mod@odoo.com>
This commit is contained in:
committed by
Yannick Tivisse
co-authored by
dmonzonis
parent
09ac18776f
commit
dadcec593d
@@ -24,9 +24,4 @@
|
||||
<field name="value">15</field>
|
||||
</record>
|
||||
|
||||
<!-- Sample project -->
|
||||
<record forcecreate="False" id="project.project_project_data" model="project.project">
|
||||
<field name="allow_timesheets" eval="False" />
|
||||
<field name="allow_timesheet_timer" eval="False" />
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -24,6 +24,10 @@ class Project(models.Model):
|
||||
help="Use a timer to record timesheets on tasks")
|
||||
|
||||
timesheet_ids = fields.One2many('account.analytic.line', 'project_id', 'Associated Timesheets')
|
||||
timesheet_encode_uom_id = fields.Many2one('uom.uom', related='company_id.timesheet_encode_uom_id')
|
||||
total_timesheet_time = fields.Integer(
|
||||
compute='_compute_total_timesheet_time',
|
||||
help="Total number of time (in the proper UoM) recorded in the project, rounded to the unit.")
|
||||
|
||||
_sql_constraints = [
|
||||
('timer_only_when_timesheet', "CHECK((allow_timesheets = 'f' AND allow_timesheet_timer = 'f') OR (allow_timesheets = 't'))", 'The timesheet timer can only be activated on project allowing timesheets.'),
|
||||
@@ -45,6 +49,18 @@ class Project(models.Model):
|
||||
for project in self:
|
||||
project.allow_timesheet_timer = project.allow_timesheets
|
||||
|
||||
@api.depends('timesheet_ids')
|
||||
def _compute_total_timesheet_time(self):
|
||||
for project in self:
|
||||
total_time = 0.0
|
||||
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.unit_amount * 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_timesheet_time = int(round(total_time))
|
||||
|
||||
@api.model
|
||||
def name_create(self, name):
|
||||
""" Create a project with name_create should generate analytic account creation """
|
||||
|
||||
@@ -2,6 +2,7 @@ odoo.define('hr_timesheet.timesheet_uom_timer', function (require) {
|
||||
"use strict";
|
||||
|
||||
const fieldRegistry = require('web.field_registry');
|
||||
const fieldUtils = require('web.field_utils');
|
||||
const TimesheetUom = require('hr_timesheet.timesheet_uom');
|
||||
const { _lt } = require('web.core');
|
||||
const session = require('web.session');
|
||||
@@ -16,8 +17,8 @@ const FieldTimesheetTimeTimer = TimesheetUom.FieldTimesheetTime.extend({
|
||||
this.isTimerRunning = this.record.data.is_timer_running;
|
||||
},
|
||||
|
||||
_render: function () {
|
||||
this._super.apply(this, arguments);
|
||||
_render: async function () {
|
||||
await this._super.apply(this, arguments);
|
||||
const my_timesheets = this.record.getContext().my_timesheet_display_timer;
|
||||
const display_timer = this.record.data.display_timer;
|
||||
if (my_timesheets && display_timer) {
|
||||
@@ -99,6 +100,26 @@ if (widgetName === 'float_toggle') {
|
||||
}
|
||||
fieldRegistry.add('timesheet_uom_timer', FieldTimesheetUom);
|
||||
|
||||
// bind the formatter and parser method, and tweak the options
|
||||
const _tweak_options = function(options) {
|
||||
if (!_.contains(options, 'factor')) {
|
||||
options.factor = session.timesheet_uom_factor;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
fieldUtils.format.timesheet_uom_timer = function(value, field, options) {
|
||||
options = _tweak_options(options || {});
|
||||
const formatter = fieldUtils.format[FieldTimesheetUom.prototype.formatType];
|
||||
return formatter(value, field, options);
|
||||
};
|
||||
|
||||
fieldUtils.parse.timesheet_uom_timer = function(value, field, options) {
|
||||
options = _tweak_options(options || {});
|
||||
const parser = fieldUtils.parse[FieldTimesheetUom.prototype.formatType];
|
||||
return parser(value, field, options);
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
FieldTimesheetTimeTimer,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="act_hr_timesheet_line_by_project" model="ir.actions.act_window">
|
||||
<field name="name">Activities</field>
|
||||
<field name="name">Timesheets</field>
|
||||
<field name="res_model">account.analytic.line</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="timesheet_view_tree_user"/>
|
||||
@@ -39,7 +39,19 @@
|
||||
<field name="priority">24</field>
|
||||
<field name="arch" type="xml">
|
||||
<div name="button_box" position="inside">
|
||||
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-calendar" string="Timesheets" attrs="{'invisible': [('allow_timesheets', '=', False)]}" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
<button class="oe_stat_button" name="%(act_hr_timesheet_line_by_project)d" type="action" icon="fa-calendar" attrs="{'invisible': [('allow_timesheets', '=', False)]}" groups="hr_timesheet.group_hr_timesheet_user">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<div class="oe_inline">
|
||||
<span class="o_stat_value mr-1">
|
||||
<field name="total_timesheet_time" widget="statinfo" nolabel="1"/>
|
||||
</span>
|
||||
<span class="o_stat_value">
|
||||
<field name="timesheet_encode_uom_id" class="o_stat_text" options="{'no_open' : True}"/>
|
||||
</span>
|
||||
</div>
|
||||
<span class="o_stat_text">Recorded</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<xpath expr="//div[@id='rating_settings']/.." position="before">
|
||||
<div class="row mt16 o_settings_container">
|
||||
@@ -65,17 +77,17 @@
|
||||
<field name="inherit_id" ref="hr_timesheet.project_invoice_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='timesheet_settings']" position="after">
|
||||
<div class="col-lg-6 o_setting_box" attrs="{'invisible': [('allow_timesheets', '=', False)]}">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="allow_timesheet_timer"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="allow_timesheet_timer"/>
|
||||
<div class="text-muted">
|
||||
Track your time using a timer
|
||||
</div>
|
||||
<div class="col-lg-6 o_setting_box" attrs="{'invisible': [('allow_timesheets', '=', False)]}" id="timer_setting">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="allow_timesheet_timer"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="allow_timesheet_timer"/>
|
||||
<div class="text-muted">
|
||||
Track your time using a timer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -193,10 +205,13 @@
|
||||
<field name="inherit_id" ref="project.view_task_tree2" />
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="planned_hours" widget="float_time" sum="Initially Planned Hours" optional="show"/>
|
||||
<field name="company_id" position="after">
|
||||
<field name="allow_subtasks" invisible="1"/>
|
||||
<field name="planned_hours" widget="float_time" sum="Initially Planned Hours" optional="hide"/>
|
||||
<field name="effective_hours" widget="float_time" sum="Effective Hours" optional="show"/>
|
||||
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" optional="show" decoration-danger="progress >= 100" decoration-warning="progress >= 80 and progress < 100"/>
|
||||
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" optional="hide" decoration-danger="progress >= 100" decoration-warning="progress >= 80 and progress < 100"/>
|
||||
<field name="subtask_effective_hours" widget="float_time" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide"/>
|
||||
<field name="total_hours_spent" widget="float_time" attrs="{'invisible' : [('allow_subtasks', '=', False)]}" optional="hide"/>
|
||||
<field name="progress" widget="progressbar" optional="show" groups="hr_timesheet.group_hr_timesheet_user"/>
|
||||
</field>
|
||||
</field>
|
||||
|
||||
@@ -7,7 +7,7 @@ class Rating(models.Model):
|
||||
_inherit = 'rating.rating'
|
||||
|
||||
# Adding information for comment a rating message
|
||||
publisher_comment = fields.Text("Publisher Comment")
|
||||
publisher_comment = fields.Text("Publisher comment")
|
||||
publisher_id = fields.Many2one('res.partner', 'Commented by',
|
||||
ondelete='set null', readonly=True)
|
||||
publisher_datetime = fields.Datetime("Commented on", readonly=True)
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<field name="model">rating.rating</field>
|
||||
<field name="inherit_id" ref="rating.rating_rating_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='is_internal']" position="after">
|
||||
<field name="publisher_comment" string="Publisher comment"/>
|
||||
<xpath expr="//field[@name='rating_text']" position="after">
|
||||
<field name="publisher_comment" string="Comment"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -83,206 +83,4 @@
|
||||
<field name="parent_id" ref="mt_task_rating"/>
|
||||
<field name="relation_field">project_id</field>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_project_data" model="project.project">
|
||||
<field name="name">Start here to discover Odoo</field>
|
||||
<field name="privacy_visibility">followers</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="alias_name">discover</field>
|
||||
<field name="alias_model_id" ref="model_project_task"/>
|
||||
<field name="alias_contact">everyone</field>
|
||||
<field name="alias_defaults">{'project_id': 1}</field>
|
||||
<field name="alias_force_thread_id">0</field>
|
||||
<field name="alias_parent_model_id" ref="model_project_project"/>
|
||||
<field name="rating_status">stage</field>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_stage_data_0" model="project.task.type">
|
||||
<field name="sequence">1</field>
|
||||
<field name="name">First Steps</field>
|
||||
<field name="project_ids" eval="[(4, ref('project_project_data'))]"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_stage_data_1" model="project.task.type">
|
||||
<field name="sequence">2</field>
|
||||
<field name="name">Getting Better</field>
|
||||
<field name="project_ids" eval="[(4, ref('project_project_data'))]"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_stage_data_2" model="project.task.type">
|
||||
<field name="sequence">3</field>
|
||||
<field name="name">Wow... You're Amazing!</field>
|
||||
<field name="project_ids" eval="[(4, ref('project_project_data'))]"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_0" model="project.task">
|
||||
<field name="sequence">1</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="active" eval="False"/>
|
||||
<field name="name">Welcome to Odoo</field>
|
||||
<field name="description" type="xml">
|
||||
<p>Welcome!</p>
|
||||
<p>This project has the objective to show you all the main features from the project app.</p>
|
||||
<p>Each card will teach you tips and tricks to easily manage your projects and tasks.</p>
|
||||
</field>
|
||||
<field name="color">2</field>
|
||||
<field name="stage_id" ref="project_stage_data_0"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_1" model="project.task">
|
||||
<field name="sequence">5</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Try to play with the filters</field>
|
||||
<field name="description" type="xml">
|
||||
<p>Go back to the tasks view to play with the filters. Try to identify unassigned tasks or tasks that are overdue, for instance.</p>
|
||||
<p>Hint: the filters and the search bar are available in the top right corner of your screen.</p>
|
||||
</field>
|
||||
<field name="stage_id" ref="project_stage_data_1"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_5" model="project.task">
|
||||
<field name="sequence">2</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Try to drag & drop this task wherever your want</field>
|
||||
<field name="description">... or try changing the status of your task from here.</field>
|
||||
<field name="kanban_state">done</field>
|
||||
<field name="stage_id" ref="project_stage_data_0"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_2" model="project.task">
|
||||
<field name="sequence">3</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Star this task to prioritize it</field>
|
||||
<field name="description" type="xml"><p>Click on the top left icon to prioritize this task and go back to the kanban view.</p> <p>Your task is now at the top of the stage.</p></field>
|
||||
<field name="stage_id" ref="project_stage_data_0"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_4" model="project.task">
|
||||
<field name="sequence">3</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Use the chatter to collaborate with your team</field>
|
||||
<field name="stage_id" ref="project_stage_data_1"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="msg_task_4" model="mail.message">
|
||||
<field name="subject">Converse with your customers and colleagues</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="author_id" ref="base.partner_root"/>
|
||||
<field name="res_id" ref="project_task_data_4"/>
|
||||
<field name="body">Use this chatter to send emails. Add new people in the followers list to make them aware about the main changes about this task.</field>
|
||||
<field name="message_type">email</field>
|
||||
<field name="subtype_id" ref="mail.mt_comment"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="msg_task_4_1" model="mail.message">
|
||||
<field name="subject">Converse with your customers and colleagues</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="author_id" ref="base.partner_root"/>
|
||||
<field name="res_id" ref="project_task_data_4"/>
|
||||
<field name="body">Use @ mentions to ping a colleague or # mentions to contact an entire team. The people following this task won't be notified of the note you are logging unless you specifically tag them.</field>
|
||||
<field name="message_type">comment</field>
|
||||
<field name="subtype_id" ref="mail.mt_note"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_10" model="project.task">
|
||||
<field name="sequence">4</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Organize your work by using activities.</field>
|
||||
<field name="stage_id" ref="project_stage_data_1"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="mail_activity_task_4" model="mail.activity">
|
||||
<field name="res_id" ref="project_task_data_10"/>
|
||||
<field name="res_model_id" ref="model_project_task"/>
|
||||
<field name="activity_type_id" ref="mail.mail_activity_data_todo" />
|
||||
<field name="date_deadline" eval="datetime.now()+timedelta(days=5)"/>
|
||||
<field name="summary">Use activities to organize your work. Try marking this activity as done.</field>
|
||||
<field name="create_uid" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_tag_data" model="project.tags">
|
||||
<field name="name">Need Assistance</field>
|
||||
<field name="color" eval="5"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_6" model="project.task">
|
||||
<field name="sequence">1</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Use tags to categorize your tasks</field>
|
||||
<field name="kanban_state">blocked</field>
|
||||
<field name="stage_id" ref="project_stage_data_1"/>
|
||||
<field name="description" type="xml"><p>Tags will be represented by colored dots on your task's card.</p>
|
||||
<p>Click on a tag to define its color.</p>
|
||||
<p>Tip: you can also define a color for your task from the tasks view.</p></field>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('project.project_tag_data')])]"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_12" model="project.task">
|
||||
<field name="sequence">1</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="color">3</field>
|
||||
<field name="name">Try to customize this card. Change its background.</field>
|
||||
<field name="description">Use the edit icon on the card from the tasks view to customize its background.</field>
|
||||
<field name="stage_id" ref="project_stage_data_2"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_13" model="project.task">
|
||||
<field name="sequence">4</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Set this task as being 'Ready for next stage'</field>
|
||||
<field name="description" type="xml"><p>You can change this task's state by clicking on the small circle on the card, or here, next to the task's title.</p>
|
||||
<p>Kanban states are perfect to let your colleagues know that your task is ready to be proceeded or that you need help on something.</p></field>
|
||||
<field name="stage_id" ref="project_stage_data_0"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="False" id="project_task_data_9" model="project.task">
|
||||
<field name="sequence">2</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">You want to add a stage? Add a new column!</field>
|
||||
<field name="stage_id" ref="project_stage_data_2"/>
|
||||
<field name="description" type="xml"><p>Click on the last column of your tasks view to create a stage.</p><p>Feel free to adapt the steps to your business workflow by drag & dropping your stages in the right places.</p> <p>Tip: You can also fold the stages you don't need to see permanently.</p></field>
|
||||
</record>
|
||||
|
||||
<record forcecreate="false" id="project_task_data_11" model="project.task">
|
||||
<field name="sequence">2</field>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="priority">0</field>
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">You can set a deadline on a task</field>
|
||||
<field name="description" type="xml"><p>Deadlines are useful to get things done on time.</p><p>You will get visual clues when the due date is close or due.</p></field>
|
||||
<field name="stage_id" ref="project_stage_data_1"/>
|
||||
<field name="date_deadline" eval="datetime.now()-timedelta(days=7)"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="false" id="project_task_data_15" model="project.task">
|
||||
<field name="sequence">5</field>
|
||||
<field name="priority">0</field>
|
||||
<field name="user_id" eval="False" />
|
||||
<field name="project_id" ref="project.project_project_data"/>
|
||||
<field name="name">Assign yourself to this task</field>
|
||||
<field name="stage_id" ref="project_stage_data_0"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -133,36 +133,6 @@ class Project(models.Model):
|
||||
action['context'] = "{'default_res_model': '%s','default_res_id': %d}" % (self._name, self.id)
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def activate_sample_project(self):
|
||||
""" Unarchives the sample project 'project.project_project_data' and
|
||||
reloads the project dashboard """
|
||||
# Unarchive sample project
|
||||
project = self.env.ref('project.project_project_data', False)
|
||||
if project:
|
||||
project.write({'active': True})
|
||||
|
||||
cover_image = self.env.ref('project.msg_task_data_14_attach', False)
|
||||
cover_task = self.env.ref('project.project_task_data_14', False)
|
||||
if cover_image and cover_task:
|
||||
cover_task.write({'displayed_image_id': cover_image.id})
|
||||
|
||||
# Change the help message on the action (no more activate project)
|
||||
action = self.env.ref('project.open_view_project_all', False)
|
||||
action_data = None
|
||||
if action:
|
||||
action.sudo().write({
|
||||
"help": _('''<p class="o_view_nocontent_smiling_face">
|
||||
Create a new project</p>''')
|
||||
})
|
||||
action_data = action.read()[0]
|
||||
|
||||
action_config = self.env.ref('project.open_view_project_all_config', False)
|
||||
if action_config:
|
||||
action_config.sudo().write({'help': action.help})
|
||||
# Reload the dashboard
|
||||
return action_data
|
||||
|
||||
def _compute_is_favorite(self):
|
||||
for project in self:
|
||||
project.is_favorite = self.env.user in project.favorite_user_ids
|
||||
@@ -183,6 +153,7 @@ class Project(models.Model):
|
||||
return [(6, 0, [self.env.uid])]
|
||||
|
||||
name = fields.Char("Name", index=True, required=True, tracking=True)
|
||||
description = fields.Text()
|
||||
active = fields.Boolean(default=True,
|
||||
help="If the active field is set to False, it will allow you to hide the project without removing it.")
|
||||
sequence = fields.Integer(default=10, help="Gives the sequence order when displaying a list of Projects.")
|
||||
@@ -204,15 +175,14 @@ class Project(models.Model):
|
||||
tasks = fields.One2many('project.task', 'project_id', string="Task Activities")
|
||||
resource_calendar_id = fields.Many2one(
|
||||
'resource.calendar', string='Working Time',
|
||||
default=lambda self: self.env.company.resource_calendar_id.id,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
|
||||
help="Timetable working hours to adjust the gantt diagram report")
|
||||
related='company_id.resource_calendar_id')
|
||||
type_ids = fields.Many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', string='Tasks Stages')
|
||||
task_count = fields.Integer(compute='_compute_task_count', string="Task Count")
|
||||
task_ids = fields.One2many('project.task', 'project_id', string='Tasks',
|
||||
domain=['|', ('stage_id.fold', '=', False), ('stage_id', '=', False)])
|
||||
color = fields.Integer(string='Color Index')
|
||||
user_id = fields.Many2one('res.users', string='Project Manager', default=lambda self: self.env.user, tracking=True)
|
||||
alias_enabled = fields.Boolean(string='Use email alias', compute='_compute_alias_enabled', readonly=False)
|
||||
alias_id = fields.Many2one('mail.alias', string='Alias', ondelete="restrict", required=True,
|
||||
help="Internal email associated with this project. Incoming emails are automatically synchronized "
|
||||
"with Tasks (or optionally Issues if the Issue Tracker module is installed).")
|
||||
@@ -271,6 +241,10 @@ class Project(models.Model):
|
||||
defaults['allow_subtasks'] = self.env.user.has_group('project.group_subtask_project')
|
||||
return defaults
|
||||
|
||||
def _compute_alias_enabled(self):
|
||||
for project in self:
|
||||
project.alias_enabled = project.alias_domain and project.alias_id.alias_name
|
||||
|
||||
@api.depends('allowed_internal_user_ids', 'allowed_portal_user_ids')
|
||||
def _compute_allowed_users(self):
|
||||
for project in self:
|
||||
@@ -580,8 +554,13 @@ class Task(models.Model):
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
|
||||
partner_is_company = fields.Boolean(related='partner_id.is_company', readonly=True)
|
||||
commercial_partner_id = fields.Many2one(related='partner_id.commercial_partner_id')
|
||||
partner_email = fields.Char(related='partner_id.email', string='Customer Email')
|
||||
partner_phone = fields.Char(related='partner_id.phone')
|
||||
partner_email = fields.Char(
|
||||
compute='_compute_partner_email', inverse='_inverse_partner_email',
|
||||
string='Email', readonly=False, store=True)
|
||||
partner_phone = fields.Char(
|
||||
compute='_compute_partner_phone', inverse='_inverse_partner_phone',
|
||||
string="Phone", readonly=False, store=True)
|
||||
ribbon_message = fields.Char('Ribbon message', compute='_compute_ribbon_message')
|
||||
partner_city = fields.Char(related='partner_id.city', readonly=False)
|
||||
manager_id = fields.Many2one('res.users', string='Project Manager', related='project_id.user_id', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, default=_default_company_id)
|
||||
@@ -600,7 +579,7 @@ class Task(models.Model):
|
||||
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
|
||||
allow_subtasks = fields.Boolean('project.project', related="project_id.allow_subtasks", readonly=True)
|
||||
subtask_count = fields.Integer("Sub-task count", compute='_compute_subtask_count')
|
||||
email_from = fields.Char(string='Email', help="These people will receive email.", index=True,
|
||||
email_from = fields.Char(string='Email From', help="These people will receive email.", index=True,
|
||||
compute='_compute_email_from', store="True", readonly=False)
|
||||
allowed_user_ids = fields.Many2many('res.users', string="Visible to", groups='project.group_project_manager', compute='_compute_allowed_user_ids', store=True, readonly=False)
|
||||
project_privacy_visibility = fields.Selection(related='project_id.privacy_visibility', string="Project Visibility")
|
||||
@@ -612,6 +591,43 @@ class Task(models.Model):
|
||||
# customer portal: include comment and incoming emails in communication history
|
||||
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
||||
|
||||
@api.depends('partner_id.email')
|
||||
def _compute_partner_email(self):
|
||||
for task in self:
|
||||
if task.partner_id and task.partner_id.email != task.partner_email:
|
||||
task.partner_email = task.partner_id.email
|
||||
|
||||
def _inverse_partner_email(self):
|
||||
for task in self:
|
||||
if task.partner_id and task.partner_email != task.partner_id.email:
|
||||
task.partner_id.email = task.partner_email
|
||||
|
||||
@api.depends('partner_id.phone')
|
||||
def _compute_partner_phone(self):
|
||||
for task in self:
|
||||
if task.partner_id and task.partner_phone != task.partner_id.phone:
|
||||
task.partner_phone = task.partner_id.phone
|
||||
|
||||
def _inverse_partner_phone(self):
|
||||
for task in self:
|
||||
if task.partner_id and task.partner_phone != task.partner_id.phone:
|
||||
task.partner_id.phone = task.partner_phone
|
||||
|
||||
@api.depends('partner_email', 'partner_phone', 'partner_id')
|
||||
def _compute_ribbon_message(self):
|
||||
for task in self:
|
||||
will_write_email = task.partner_id and task.partner_email != task.partner_id.email
|
||||
will_write_phone = task.partner_id and task.partner_phone != task.partner_id.phone
|
||||
|
||||
if will_write_email and will_write_phone:
|
||||
task.ribbon_message = _('By saving this change, the customer email and phone number will also be updated.')
|
||||
elif will_write_email:
|
||||
task.ribbon_message = _('By saving this change, the customer email will also be updated.')
|
||||
elif will_write_phone:
|
||||
task.ribbon_message = _('By saving this change, the customer phone number will also be updated.')
|
||||
else:
|
||||
task.ribbon_message = False
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
@@ -1086,7 +1102,7 @@ class ProjectTags(models.Model):
|
||||
def _get_default_color(self):
|
||||
return randint(1, 11)
|
||||
|
||||
name = fields.Char('Tag Name', required=True)
|
||||
name = fields.Char('Name', required=True)
|
||||
color = fields.Integer(string='Color Index', default=_get_default_color)
|
||||
|
||||
_sql_constraints = [
|
||||
|
||||
@@ -23,32 +23,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Custom reports (aka filters) -->
|
||||
<record id="filter_task_report_task_pipe" model="ir.filters">
|
||||
<field name="name">Task Pipe</field>
|
||||
<field name="model_id">report.project.task.user</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['project_id'], 'col_group_by': ['stage_id'], 'measures': ['nbr']}</field>
|
||||
</record>
|
||||
<record id="filter_task_report_workload" model="ir.filters">
|
||||
<field name="name">Workload</field>
|
||||
<field name="model_id">report.project.task.user</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['project_id'], 'measures': ['planned_hours']}</field>
|
||||
</record>
|
||||
<record id="filter_task_report_responsible" model="ir.filters">
|
||||
<field name="name">By Responsible</field>
|
||||
<field name="model_id">report.project.task.user</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['project_id', 'user_id']}</field>
|
||||
</record>
|
||||
<record id="filter_task_report_cumulative_flow" model="ir.filters">
|
||||
<field name="name">Cumulative Flow</field>
|
||||
<field name="model_id">report.project.task.user</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['stage_id', 'state'], 'measures': ['nbr','planned_hours']}</field>
|
||||
</record>
|
||||
|
||||
<record id="view_task_project_user_search" model="ir.ui.view">
|
||||
<field name="name">report.project.task.user.search</field>
|
||||
<field name="model">report.project.task.user</field>
|
||||
@@ -65,7 +39,6 @@
|
||||
<field name="stage_id"/>
|
||||
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="New" name="new" domain="[('stage_id.sequence', '<=', 1)]"/>
|
||||
<group expand="0" string="Extended Filters">
|
||||
<field name="priority"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
@@ -74,8 +47,6 @@
|
||||
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
|
||||
<filter string="Assigned to" name="User" context="{'group_by': 'user_id'}"/>
|
||||
<filter string="Stage" name="Stage" context="{'group_by': 'stage_id'}"/>
|
||||
<filter string="Assignment Date" name="assignation_month" context="{'group_by': 'date_assign:month'}"/>
|
||||
<filter string="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
odoo.define('project.project_rating_reporting', function (require) {
|
||||
'use strict';
|
||||
|
||||
const core = require('web.core');
|
||||
const _t = core._t;
|
||||
|
||||
const viewRegistry = require('web.view_registry');
|
||||
|
||||
const PivotController = require('web.PivotController');
|
||||
const PivotView = require('web.PivotView');
|
||||
|
||||
const GraphController = require('web.GraphController');
|
||||
const GraphView = require('web.GraphView');
|
||||
|
||||
var ProjectPivotController = PivotController.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var measures = JSON.parse(JSON.stringify(this.measures));
|
||||
if ('res_id' in measures) {
|
||||
measures.res_id.string = _t('Task');
|
||||
}
|
||||
if ('parent_res_id' in measures) {
|
||||
measures.parent_res_id.string = _t('Project');
|
||||
}
|
||||
if ('rating' in measures) {
|
||||
measures.rating.string = _t('Rating Value (/5)');
|
||||
}
|
||||
this.measures = measures;
|
||||
},
|
||||
});
|
||||
|
||||
var ProjectPivotView = PivotView.extend({
|
||||
config: _.extend({}, PivotView.prototype.config, {
|
||||
Controller: ProjectPivotController,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('project_rating_pivot', ProjectPivotView);
|
||||
|
||||
var ProjectGraphController = GraphController.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
_.each(this.measures, measure => {
|
||||
if (measure.fieldName === 'res_id') {
|
||||
measure.description = _t('Task');
|
||||
} else if (measure.fieldName === 'parent_res_id') {
|
||||
measure.description = _t('Project');
|
||||
} else if (measure.fieldName === 'rating') {
|
||||
measure.description = _t('Rating Value (/5)');
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
var ProjectGraphView = GraphView.extend({
|
||||
config: _.extend({}, GraphView.prototype.config, {
|
||||
Controller: ProjectGraphController,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('project_rating_graph', ProjectGraphView);
|
||||
|
||||
});
|
||||
@@ -4,6 +4,7 @@
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/project/static/src/css/project.css"/>
|
||||
<script type="text/javascript" src="/project/static/src/js/project_kanban.js"></script>
|
||||
<script type="text/javascript" src="/project/static/src/js/project_rating_reporting.js"></script>
|
||||
<script type="text/javascript" src="/project/static/src/js/project_task_kanban_examples.js"></script>
|
||||
<script type="text/javascript" src="/project/static/src/js/tours/project.js"></script>
|
||||
<link rel="stylesheet" type="text/scss" href="/project/static/src/scss/project_dashboard.scss"/>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<separator/>
|
||||
<filter string="Starred" name="starred" domain="[('priority', 'in', [1, 2])]"/>
|
||||
<filter string="Late Tasks" name="late" domain="[('date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Rated tasks" name="rating_task" domain="[('rating_last_value', '!=', 0.0)]" groups="project.group_project_rating"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
|
||||
<separator/>
|
||||
@@ -41,14 +42,11 @@
|
||||
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter invisible="1" string="Future Activities" name="activities_upcoming_all"
|
||||
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter string="Rated tasks" name="rating_task" domain="[('rating_last_value', '!=', 0.0)]" groups="project.group_project_rating"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Stage" name="stage" context="{'group_by': 'stage_id'}"/>
|
||||
<filter string="Assigned to" name="user" context="{'group_by': 'user_id'}"/>
|
||||
<filter string="Project" name="project" context="{'group_by': 'project_id'}"/>
|
||||
<filter string="Creation Date" name="group_create_date" context="{'group_by': 'create_date'}"/>
|
||||
<filter string="Company" name="company" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
@@ -281,11 +279,12 @@
|
||||
</h1>
|
||||
<div name="options_active">
|
||||
<div>
|
||||
<label for="label_tasks" class="oe_inline" string="Name of the tasks :"/>
|
||||
<label for="label_tasks" class="oe_inline" string="Name of the tasks:"/>
|
||||
<field name="label_tasks" class="oe_inline oe_input_align"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<field name="description" placeholder="Describe your project..."/>
|
||||
<notebook>
|
||||
<page name="settings" string="Settings">
|
||||
<group>
|
||||
@@ -293,6 +292,16 @@
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_id" string="Project Manager" widget="many2one_avatar_user" attrs="{'readonly':[('active','=',False)]}" domain="[('share', '=', False)]"/>
|
||||
<field name="partner_id" string="Customer"/>
|
||||
<div name="alias_def" colspan="2" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_enabled" class="font-weight-bold" string="Create tasks by sending an email to"/>
|
||||
<field name="alias_enabled"/>
|
||||
<span attrs="{'invisible': [('alias_enabled', '=', False)]}">
|
||||
<field name="alias_name" class="oe_inline" attrs="{'required': [('alias_enabled', '=', True)]}" />
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1" />
|
||||
</span>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline oe_edit_only" string="Accept Emails From" attrs="{'invisible': ['|', ('alias_domain', '=', False), ('alias_enabled', '=', False)]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="analytic_account_id" domain="['|', ('company_id', '=', company_id), ('company_id', '=', False)]" context="{'default_partner_id': partner_id}" groups="analytic.group_analytic_accounting"/>
|
||||
@@ -345,25 +354,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<group name="misc">
|
||||
<group string="Time Scheduling">
|
||||
<field name="resource_calendar_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page name="emails" string="Emails" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<group name="group_alias">
|
||||
<label for="alias_name" string="Email Alias"/>
|
||||
<div name="alias_def">
|
||||
<field name="alias_id" class="oe_read_only oe_inline"
|
||||
string="Email Alias" required="0"/>
|
||||
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<field name="alias_contact" class="oe_inline oe_edit_only"
|
||||
string="Accept Emails From"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
<div class="oe_chatter">
|
||||
@@ -385,6 +375,7 @@
|
||||
<filter string="My Favorites" name="my_projects" domain="[('favorite_user_ids', 'in', uid)]"/>
|
||||
<separator/>
|
||||
<filter string="Followed" name="followed_by_me" domain="[('message_is_follower', '=', True)]"/>
|
||||
<filter string="My Projects" name="my_projects" domain="[('user_id', '=', uid)]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
@@ -399,17 +390,16 @@
|
||||
<field name="name">project.project.tree</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree decoration-muted="active == False" string="Projects" delete="0">
|
||||
<tree decoration-muted="active == False" string="Projects" delete="0" multi_edit="1">
|
||||
<field name="sequence" optional="show" widget="handle"/>
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="name" string="Project Name"/>
|
||||
<field name="name" string="Name"/>
|
||||
<field name="user_id" optional="show" string="Project Manager" widget="many2one_avatar_user"/>
|
||||
<field name="partner_id" optional="show" string="Contact"/>
|
||||
<field name="partner_id" optional="show" string="Customer"/>
|
||||
<field name="analytic_account_id" optional="hide"/>
|
||||
<field name="privacy_visibility" optional="hide"/>
|
||||
<field name="subtask_project_id" optional="hide"/>
|
||||
<field name="resource_calendar_id" optional="hide"/>
|
||||
<field name="label_tasks" optional="hide"/>
|
||||
<field name="company_id" optional="show" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
@@ -453,16 +443,19 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Project">
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" class="o_project_name oe_inline"
|
||||
string="Project Name" placeholder="e.g. Office Party"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<label for="alias_name" string="Choose a Project Email" attrs="{'invisible': [('alias_domain', '=', False)]}"/>
|
||||
<div name="alias_def" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
|
||||
</div>
|
||||
</group>
|
||||
<field name="name" class="o_project_name oe_inline"
|
||||
string="Project Name" placeholder="e.g. Office Party"/>
|
||||
<field name="user_id" invisible="1"/>
|
||||
</group>
|
||||
<div name="alias_def" colspan="2" attrs="{'invisible': [('alias_domain', '=', False)]}">
|
||||
<label for="alias_enabled" class="oe_inline" string="Create tasks by sending an email to"/>
|
||||
<field name="alias_enabled"/>
|
||||
<span attrs="{'invisible': [('alias_enabled', '=', False)]}">
|
||||
<field name="alias_name" class="oe_inline" attrs="{'required': [('alias_enabled', '=', True)]}" placeholder="e.g. office-party"/>
|
||||
@
|
||||
<field name="alias_domain" class="oe_inline" readonly="1" />
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
@@ -571,13 +564,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="ir_actions_server_project_sample" model="ir.actions.server">
|
||||
<field name="name">Project: Activate Sample Project</field>
|
||||
<field name="model_id" ref="project.model_project_project"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">action = model.activate_sample_project()</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_project_all" model="ir.actions.act_window">
|
||||
<field name="name">Projects</field>
|
||||
<field name="res_model">project.project</field>
|
||||
@@ -588,9 +574,9 @@
|
||||
<field name="target">main</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new project
|
||||
Create a project
|
||||
</p><p>
|
||||
Or <a type="action" name="%(project.ir_actions_server_project_sample)d" tabindex="-1">activate a sample project</a> to play with.
|
||||
Projects regroup tasks on the same topic and each have their own dashboard.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
@@ -606,10 +592,10 @@
|
||||
<field name="search_view_id" ref="view_project_project_filter"/>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
Create a new project
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a project
|
||||
</p><p>
|
||||
Or <a type="action" name="%(project.ir_actions_server_project_sample)d" tabindex="-1">activate a sample project</a> to play with.
|
||||
Projects regroup tasks on the same topic and each have their own dashboard.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
@@ -628,6 +614,9 @@
|
||||
attrs="{'invisible' : [('user_id', '!=', False)]}"/>
|
||||
<field name="stage_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '=', False)]}"/>
|
||||
</header>
|
||||
<div class="text-center alert alert-primary" role="alert" attrs="{'invisible': ['|', ('ribbon_message', '=', False), ('ribbon_message', '=', '')]}">
|
||||
<field name="ribbon_message"/>
|
||||
</div>
|
||||
<sheet string="Task">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
|
||||
@@ -651,7 +640,7 @@
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="partner_id" class="o_task_customer_field"/>
|
||||
<field name="partner_phone" widget="phone" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||
<field name="partner_email" string="Email" widget="email" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||
<field name="partner_email" widget="email" attrs="{'invisible': [('partner_id', '=', False)]}"/>
|
||||
<field name="legend_blocked" invisible="1"/>
|
||||
<field name="legend_normal" invisible="1"/>
|
||||
<field name="legend_done" invisible="1"/>
|
||||
@@ -660,7 +649,7 @@
|
||||
<field name="project_id" required="1" domain="[('active', '=', True), ('company_id', '=', company_id)]"/>
|
||||
<field name="user_id"
|
||||
class="o_task_user_field"
|
||||
options='{"no_open": True}' domain="[('share', '=', False)]"/>
|
||||
domain="[('share', '=', False)]"/>
|
||||
<field
|
||||
name="parent_id"
|
||||
domain="[('parent_id', '=', False)]"
|
||||
@@ -852,8 +841,9 @@
|
||||
<!-- YTI TODO user_invisible and seq_visible seem to be useless -->
|
||||
<field name="project_id" invisible="context.get('user_invisible', False)" optional="show" readonly="1"/>
|
||||
<field name="partner_id" optional="hide"/>
|
||||
<field name="parent_id" groups="project.group_subtask_project" optional="show"/>
|
||||
<field name="parent_id" groups="project.group_subtask_project" optional="hide"/>
|
||||
<field name="user_id" invisible="context.get('user_invisible', False)" optional="show" widget="many2one_avatar_user"/>
|
||||
<field name="company_id" groups="base.group_multi_company" optional="show"/>
|
||||
<field name="activity_ids" widget="list_activity" optional="show"/>
|
||||
<field name="date_deadline" optional="hide" widget="remaining_days" attrs="{'invisible': [('is_closed', '=', True)]}"/>
|
||||
<field name="stage_id" invisible="context.get('set_visible',False)" optional="show" readonly="1"/>
|
||||
@@ -955,7 +945,7 @@
|
||||
<record id="action_view_all_task" model="ir.actions.act_window">
|
||||
<field name="name">Tasks</field>
|
||||
<field name="res_model">project.task</field>
|
||||
<field name="view_mode">kanban,tree,form,calendar,pivot,graph,activity</field>
|
||||
<field name="view_mode">tree,kanban,form,calendar,pivot,graph,activity</field>
|
||||
<field name="context">{'search_default_my_tasks': 1, 'all_task': 0}</field>
|
||||
<field name="search_view_id" ref="view_task_search_form"/>
|
||||
<field name="help" type="html">
|
||||
@@ -979,7 +969,7 @@
|
||||
<field name="act_window_id" ref="action_view_task"/>
|
||||
</record>
|
||||
|
||||
<menuitem name="All Tasks" id="menu_project_management" parent="menu_main_pm"
|
||||
<menuitem name="Tasks" id="menu_project_management" parent="menu_main_pm"
|
||||
action="action_view_all_task" sequence="2" groups="base.group_no_one,group_project_user"/>
|
||||
|
||||
<record id="project_task_action_from_partner" model="ir.actions.act_window">
|
||||
@@ -1040,6 +1030,7 @@
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="color" widget="color_picker"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
@@ -1052,6 +1043,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Tags" editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="color" widget="color_picker"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,5 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="rating_rating_view_tree_project" model="ir.ui.view">
|
||||
<field name="name">rating.rating.tree.project</field>
|
||||
<field name="model">rating.rating</field>
|
||||
<field name="inherit_id" ref="rating.rating_rating_view_tree"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="res_name" position="attributes">
|
||||
<attribute name="string">Task</attribute>
|
||||
</field>
|
||||
<field name="parent_res_name" position="attributes">
|
||||
<attribute name="string">Project</attribute>
|
||||
</field>
|
||||
<field name="rated_partner_id" position="attributes">
|
||||
<attribute name="string">Assigned to</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_view_form_project" model="ir.ui.view">
|
||||
<field name="name">rating.rating.form.project</field>
|
||||
<field name="model">rating.rating</field>
|
||||
<field name="inherit_id" ref="rating.rating_rating_view_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="res_name" position="attributes">
|
||||
<attribute name="string">Task</attribute>
|
||||
</field>
|
||||
<field name="resource_ref" position="attributes">
|
||||
<attribute name="string">Task</attribute>
|
||||
</field>
|
||||
<field name="parent_ref" position="attributes">
|
||||
<attribute name="string">Project</attribute>
|
||||
</field>
|
||||
<field name="parent_res_name" position="attributes">
|
||||
<attribute name="string">Project</attribute>
|
||||
</field>
|
||||
<field name="rated_partner_id" position="attributes">
|
||||
<attribute name="string">Assigned to</attribute>
|
||||
</field>
|
||||
<div name="rating_image_container" position="replace">
|
||||
<field name="rating_text" string="Rating"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_view_pivot" model="ir.ui.view">
|
||||
<field name="name">rating.rating.view.pivot.project</field>
|
||||
<field name="model">rating.rating</field>
|
||||
<field name="inherit_id" ref="rating.rating_rating_view_pivot"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//pivot" position="attributes">
|
||||
<attribute name="js_class">project_rating_pivot</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_view_graph" model="ir.ui.view">
|
||||
<field name="name">rating.rating.view.graph.project</field>
|
||||
<field name="model">rating.rating</field>
|
||||
<field name="inherit_id" ref="rating.rating_rating_view_graph"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//graph" position="attributes">
|
||||
<attribute name="js_class">project_rating_graph</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_view_search_project" model="ir.ui.view">
|
||||
<field name="name">rating.rating.search.project</field>
|
||||
@@ -32,8 +100,36 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_view_project_rating_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_view_project_rating"/>
|
||||
<field name="view_id" ref="rating_rating_view_tree_project"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_view_project_rating_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_view_project_rating"/>
|
||||
<field name="view_id" ref="rating_rating_view_form_project"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_view_project_rating_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_view_project_rating"/>
|
||||
<field name="view_id" ref="rating_rating_view_pivot"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_view_project_rating_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_view_project_rating"/>
|
||||
<field name="view_id" ref="rating_rating_view_graph"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_task" model="ir.actions.act_window">
|
||||
<field name="name">Customer Ratings</field>
|
||||
<field name="name">Customer Ratings on Task</field>
|
||||
<field name="res_model">rating.rating</field>
|
||||
<field name="view_mode">kanban,tree,pivot,graph,form</field>
|
||||
<field name="domain">[('res_model', '=', 'project.task'), ('res_id', '=', active_id), ('consumed', '=', True)]</field>
|
||||
@@ -42,11 +138,39 @@
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No customer ratings yet
|
||||
</p><p>
|
||||
Customer ratings on tasks. If you have no rating, change your project Settings to activate it.
|
||||
Let's wait for your customers to manifest themselves.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_task_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_task"/>
|
||||
<field name="view_id" ref="rating_rating_view_tree_project"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_task_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_task"/>
|
||||
<field name="view_id" ref="rating_rating_view_form_project"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_task_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_task"/>
|
||||
<field name="view_id" ref="rating_rating_view_pivot"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_task_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_task"/>
|
||||
<field name="view_id" ref="rating_rating_view_graph"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_project_report" model="ir.actions.act_window">
|
||||
<field name="name">Customer Ratings</field>
|
||||
<field name="res_model">rating.rating</field>
|
||||
@@ -57,10 +181,38 @@
|
||||
<p class="o_view_nocontent_empty_folder">
|
||||
No customer ratings yet
|
||||
</p><p>
|
||||
Customer ratings on tasks. If you have no rating, change your project Settings to activate it.
|
||||
Let's wait for your customers to manifest themselves.
|
||||
</p>
|
||||
</field>
|
||||
<field name="context">{}</field>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_project_report_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_project_report"/>
|
||||
<field name="view_id" ref="rating_rating_view_tree_project"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_project_report_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_project_report"/>
|
||||
<field name="view_id" ref="rating_rating_view_form_project"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_project_report_pivot" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">pivot</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_project_report"/>
|
||||
<field name="view_id" ref="rating_rating_view_pivot"/>
|
||||
</record>
|
||||
|
||||
<record id="rating_rating_action_project_report_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="40"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="act_window_id" ref="rating_rating_action_project_report"/>
|
||||
<field name="view_id" ref="rating_rating_view_graph"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_project_forecast"/>
|
||||
<div class="text-muted" name="project_forecast_msg">
|
||||
Schedule your teams across projects and estimate deadlines more accurately.
|
||||
Plan resource allocation accross projets and tasks, and estimate deadlines more accurately
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="module_hr_timesheet"/>
|
||||
<div class="text-muted">
|
||||
Track time spent on tasks.
|
||||
Track time spent on projects and tasks
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,17 +31,28 @@ class Rating(models.Model):
|
||||
def _default_access_token(self):
|
||||
return uuid.uuid4().hex
|
||||
|
||||
@api.model
|
||||
def _selection_target_model(self):
|
||||
return [(model.model, model.name) for model in self.env['ir.model'].search([])]
|
||||
|
||||
create_date = fields.Datetime(string="Submitted on")
|
||||
res_name = fields.Char(string='Resource name', compute='_compute_res_name', store=True, help="The name of the rated resource.")
|
||||
res_model_id = fields.Many2one('ir.model', 'Related Document Model', index=True, ondelete='cascade', help='Model of the followed resource')
|
||||
res_model = fields.Char(string='Document Model', related='res_model_id.model', store=True, index=True, readonly=True)
|
||||
res_id = fields.Integer(string='Document', required=True, help="Identifier of the rated object", index=True)
|
||||
resource_ref = fields.Reference(
|
||||
string='Resource Ref', selection='_selection_target_model',
|
||||
compute='_compute_resource_ref', readonly=True)
|
||||
parent_res_name = fields.Char('Parent Document Name', compute='_compute_parent_res_name', store=True)
|
||||
parent_res_model_id = fields.Many2one('ir.model', 'Parent Related Document Model', index=True, ondelete='cascade')
|
||||
parent_res_model = fields.Char('Parent Document Model', store=True, related='parent_res_model_id.model', index=True, readonly=False)
|
||||
parent_res_id = fields.Integer('Parent Document', index=True)
|
||||
parent_ref = fields.Reference(
|
||||
string='Parent Ref', selection='_selection_target_model',
|
||||
compute='_compute_parent_ref', readonly=True)
|
||||
rated_partner_id = fields.Many2one('res.partner', string="Rated person", help="Owner of the rated resource")
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', help="Author of the rating")
|
||||
rating = fields.Float(string="Rating Number", group_operator="avg", default=0, help="Rating value: 0=Unhappy, 5=Happy")
|
||||
rating = fields.Float(string="Rating Value", group_operator="avg", default=0, help="Rating value: 0=Unhappy, 5=Happy")
|
||||
rating_image = fields.Binary('Image', compute='_compute_rating_image')
|
||||
rating_text = fields.Selection([
|
||||
('satisfied', 'Satisfied'),
|
||||
@@ -50,13 +61,29 @@ class Rating(models.Model):
|
||||
('no_rating', 'No Rating yet')], string='Rating', store=True, compute='_compute_rating_text', readonly=True)
|
||||
feedback = fields.Text('Comment', help="Reason of the rating")
|
||||
message_id = fields.Many2one(
|
||||
'mail.message', string="Linked message",
|
||||
'mail.message', string="Message",
|
||||
index=True, ondelete='cascade',
|
||||
help="Associated message when posting a review. Mainly used in website addons.")
|
||||
is_internal = fields.Boolean('Employee Only', readonly=False, related='message_id.is_internal', store=True)
|
||||
is_internal = fields.Boolean('Visible Internally Only', readonly=False, related='message_id.is_internal', store=True)
|
||||
access_token = fields.Char('Security Token', default=_default_access_token, help="Access token to set the rating of the value")
|
||||
consumed = fields.Boolean(string="Filled Rating", help="Enabled if the rating has been filled.")
|
||||
|
||||
@api.depends('res_model', 'res_id')
|
||||
def _compute_resource_ref(self):
|
||||
for rating in self:
|
||||
if rating.res_model and rating.res_model in self.env:
|
||||
rating.resource_ref = '%s,%s' % (rating.res_model, rating.res_id or 0)
|
||||
else:
|
||||
rating.resource_ref = None
|
||||
|
||||
@api.depends('parent_res_model', 'parent_res_id')
|
||||
def _compute_parent_ref(self):
|
||||
for rating in self:
|
||||
if rating.parent_res_model and rating.parent_res_model in self.env:
|
||||
rating.parent_ref = '%s,%s' % (rating.parent_res_model, rating.parent_res_id or 0)
|
||||
else:
|
||||
rating.parent_ref = None
|
||||
|
||||
@api.depends('parent_res_model', 'parent_res_id')
|
||||
def _compute_parent_res_name(self):
|
||||
for rating in self:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<field name="partner_id"/>
|
||||
<field name="rating_text" decoration-danger="rating_text == 'highly_dissatisfied'" decoration-warning="rating_text == 'not_satisfied'" decoration-success="rating_text == 'satisfied'"/>
|
||||
<field name="feedback"/>
|
||||
<field name="create_date" string="Submitted on"/>
|
||||
<field name="create_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -23,17 +23,12 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Rating" create="false">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" type="object" name="action_open_rated_object" icon="fa-file">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_text">Resource</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="res_name" string="Document"/>
|
||||
<field name="parent_res_name" string="Parent Holder"/>
|
||||
<field name="resource_ref" string="Document"/>
|
||||
<field name="res_name" string="Document" invisible="1"/>
|
||||
<field name="parent_ref" string="Parent Holder"/>
|
||||
<field name="parent_res_name" string="Parent Holder" invisible="1"/>
|
||||
<field name="rated_partner_id"/>
|
||||
<field name="rating" invisible="1"/>
|
||||
<field name="is_internal"/>
|
||||
@@ -46,7 +41,7 @@
|
||||
<strong><field name="rating_text"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<field name="create_date" string="Submitted on"/>
|
||||
<field name="create_date"/>
|
||||
<field name="feedback" attrs="{'invisible': [('feedback','=',False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='analytic_account_id']" position="after">
|
||||
<field name="sale_order_id" optional="hide" widget="sale_order_many2one"/>
|
||||
<field name="sale_line_id" optional="hide" widget="sale_order_many2one"/>
|
||||
<field name="sale_line_id" optional="hide" widget="sale_order_many2one" readonly="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -56,10 +56,6 @@
|
||||
<xpath expr="//field[@name='stage_id']" position="before">
|
||||
<field name="sale_order_id" string="Sale Order" filter_domain="['|', ('sale_order_id', 'ilike', self), ('sale_line_id', 'ilike', self)]"/>
|
||||
</xpath>
|
||||
<filter name="inactive" position="before">
|
||||
<filter string="To Invoice" name="to_invoice" domain="[('task_to_invoice', '=', True)]" groups="sales_team.group_sale_salesman" />
|
||||
<separator/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -13,16 +13,4 @@
|
||||
}</field>
|
||||
</record>
|
||||
|
||||
<record id="ir_filter_project_profitability_report_manager_and_product" model="ir.filters">
|
||||
<field name="name">Product by Customer</field>
|
||||
<field name="model_id">project.profitability.report</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{
|
||||
'group_by': ['partner_id', 'product_id'],
|
||||
'graph_measure': 'amount_untaxed_to_invoice',
|
||||
'graph_mode': 'bar',
|
||||
'graph_groupbys': ['partner_id', 'product_id']
|
||||
}</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -396,24 +396,19 @@ class Project(models.Model):
|
||||
|
||||
def _plan_get_stat_button(self):
|
||||
stat_buttons = []
|
||||
if len(self) == 1:
|
||||
edit_project = self.env.ref('project.edit_project')
|
||||
stat_buttons.append({
|
||||
'name': _('Project'),
|
||||
'icon': 'fa fa-puzzle-piece',
|
||||
'action': _to_action_data('project.project', res_id=self.id,
|
||||
views=[[edit_project.id, 'form']])
|
||||
})
|
||||
ts_tree = self.env.ref('hr_timesheet.hr_timesheet_line_tree')
|
||||
ts_form = self.env.ref('hr_timesheet.hr_timesheet_line_form')
|
||||
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'),
|
||||
domain=[('id', 'in', self.ids)])
|
||||
|
||||
stat_buttons.append({
|
||||
'name': _('Timesheets'),
|
||||
'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')],
|
||||
)
|
||||
'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
|
||||
@@ -439,7 +434,7 @@ class Project(models.Model):
|
||||
)
|
||||
})
|
||||
stat_buttons.append({
|
||||
'name': _("Late Tasks"),
|
||||
'name': [_("Tasks"), _("Late")],
|
||||
'count': self.env['project.task'].search_count(late_tasks_domain),
|
||||
'icon': 'fa fa-tasks',
|
||||
'action': _to_action_data(
|
||||
@@ -449,7 +444,7 @@ class Project(models.Model):
|
||||
),
|
||||
})
|
||||
stat_buttons.append({
|
||||
'name': _("Tasks in Overtime"),
|
||||
'name': [_("Tasks"), _("in Overtime")],
|
||||
'count': self.env['project.task'].search_count(overtime_tasks_domain),
|
||||
'icon': 'fa fa-tasks',
|
||||
'action': _to_action_data(
|
||||
@@ -495,6 +490,25 @@ class Project(models.Model):
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class SaleOrder(models.Model):
|
||||
# override domain
|
||||
project_id = fields.Many2one(domain="[('billable_type', 'in', ('no', 'task_rate')), ('analytic_account_id', '!=', False), ('company_id', '=', company_id)]")
|
||||
timesheet_encode_uom_id = fields.Many2one('uom.uom', related='company_id.timesheet_encode_uom_id')
|
||||
timesheet_total_duration = fields.Float("Timesheet Total Duration", compute='_compute_timesheet_total_duration', help="Total recorded duration, expressed in the encoding UoM")
|
||||
timesheet_total_duration = fields.Integer("Timesheet Total Duration", compute='_compute_timesheet_total_duration', help="Total recorded duration, expressed in the encoding UoM, and rounded to the unit")
|
||||
|
||||
@api.depends('analytic_account_id.line_ids')
|
||||
def _compute_timesheet_ids(self):
|
||||
@@ -32,7 +32,13 @@ class SaleOrder(models.Model):
|
||||
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)
|
||||
sale_order.timesheet_total_duration = sum([timesheet.unit_amount for timesheet in timesheets])
|
||||
total_time = 0.0
|
||||
for timesheet in timesheets:
|
||||
# Timesheets may be stored in a different unit of measure, so first we convert all of them to the reference unit
|
||||
total_time += timesheet.unit_amount * 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
|
||||
|
||||
def action_view_project_ids(self):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -18,9 +18,9 @@ class ProfitabilityAnalysis(models.Model):
|
||||
user_id = fields.Many2one('res.users', string='Project Manager', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
|
||||
# cost
|
||||
timesheet_unit_amount = fields.Float("Timesheet Unit Amount", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
timesheet_unit_amount = fields.Float("Timesheet Duration", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
timesheet_cost = fields.Float("Timesheet Cost", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_cost = fields.Float("Other Cost", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_cost = fields.Float("Other Costs", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
# sale revenue
|
||||
order_confirmation_date = fields.Datetime('Sales Order Confirmation Date', readonly=True)
|
||||
sale_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', readonly=True)
|
||||
@@ -30,189 +30,216 @@ class ProfitabilityAnalysis(models.Model):
|
||||
amount_untaxed_to_invoice = fields.Float("Untaxed Amount To Invoice", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
amount_untaxed_invoiced = fields.Float("Untaxed Amount Invoiced", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_to_invoice = fields.Float("Untaxed Amount to Re-invoice", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_invoiced = fields.Float("Untaxed Re-invoiced Amount", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_invoiced = fields.Float("Untaxed Amount Re-invoiced", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
other_revenues = fields.Float("Other Revenues", digits=(16, 2), readonly=True, group_operator="sum",
|
||||
help="All revenues that are not from timesheets and that are linked to the analytic account of the project.")
|
||||
margin = fields.Float("Margin", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, self._table)
|
||||
query = """
|
||||
CREATE VIEW %s AS (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY P.id, SOL.id) AS id,
|
||||
P.id AS project_id,
|
||||
P.user_id AS user_id,
|
||||
SOL.id AS sale_line_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
P.partner_id AS partner_id,
|
||||
C.id AS company_id,
|
||||
C.currency_id AS currency_id,
|
||||
S.id AS sale_order_id,
|
||||
S.date_order AS order_confirmation_date,
|
||||
SOL.product_id AS product_id,
|
||||
SOL.qty_delivered_method AS sale_qty_delivered_method,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status != 'no'
|
||||
THEN
|
||||
CASE
|
||||
WHEN T.expense_policy = 'sales_price'
|
||||
THEN (SOL.price_reduce / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END) * SOL.qty_invoiced
|
||||
ELSE -COST_SUMMARY.expense_cost
|
||||
END
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_invoiced,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, COST_SUMMARY.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_invoiced,
|
||||
COST_SUMMARY.timesheet_unit_amount AS timesheet_unit_amount,
|
||||
COST_SUMMARY.timesheet_cost AS timesheet_cost,
|
||||
COST_SUMMARY.expense_cost AS expense_cost,
|
||||
COST_SUMMARY.other_revenues AS other_revenues
|
||||
FROM project_project P
|
||||
JOIN res_company C ON C.id = P.company_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
project_id,
|
||||
analytic_account_id,
|
||||
sale_line_id,
|
||||
SUM(timesheet_unit_amount) AS timesheet_unit_amount,
|
||||
SUM(timesheet_cost) AS timesheet_cost,
|
||||
SUM(expense_cost) AS expense_cost,
|
||||
SUM(other_revenues) AS other_revenues,
|
||||
SUM(downpayment_invoiced) AS downpayment_invoiced
|
||||
FROM (
|
||||
sub.id as id,
|
||||
sub.project_id as project_id,
|
||||
sub.user_id as user_id,
|
||||
sub.sale_line_id as sale_line_id,
|
||||
sub.analytic_account_id as analytic_account_id,
|
||||
sub.partner_id as partner_id,
|
||||
sub.company_id as company_id,
|
||||
sub.currency_id as currency_id,
|
||||
sub.sale_order_id as sale_order_id,
|
||||
sub.order_confirmation_date as order_confirmation_date,
|
||||
sub.product_id as product_id,
|
||||
sub.sale_qty_delivered_method as sale_qty_delivered_method,
|
||||
sub.expense_amount_untaxed_to_invoice as expense_amount_untaxed_to_invoice,
|
||||
sub.expense_amount_untaxed_invoiced as expense_amount_untaxed_invoiced,
|
||||
sub.amount_untaxed_to_invoice as amount_untaxed_to_invoice,
|
||||
sub.amount_untaxed_invoiced as amount_untaxed_invoiced,
|
||||
sub.timesheet_unit_amount as timesheet_unit_amount,
|
||||
sub.timesheet_cost as timesheet_cost,
|
||||
sub.expense_cost as expense_cost,
|
||||
sub.other_revenues as other_revenues,
|
||||
(sub.expense_amount_untaxed_to_invoice + sub.expense_amount_untaxed_invoiced + sub.amount_untaxed_to_invoice +
|
||||
sub.amount_untaxed_invoiced + sub.other_revenues + sub.timesheet_cost + sub.expense_cost)
|
||||
as margin
|
||||
FROM (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY P.id, SOL.id) AS id,
|
||||
P.id AS project_id,
|
||||
P.user_id AS user_id,
|
||||
SOL.id AS sale_line_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
P.partner_id AS partner_id,
|
||||
C.id AS company_id,
|
||||
C.currency_id AS currency_id,
|
||||
S.id AS sale_order_id,
|
||||
S.date_order AS order_confirmation_date,
|
||||
SOL.product_id AS product_id,
|
||||
SOL.qty_delivered_method AS sale_qty_delivered_method,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status != 'no'
|
||||
THEN
|
||||
CASE
|
||||
WHEN T.expense_policy = 'sales_price'
|
||||
THEN (SOL.price_reduce / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END) * SOL.qty_invoiced
|
||||
ELSE -COST_SUMMARY.expense_cost
|
||||
END
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_invoiced,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, COST_SUMMARY.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_invoiced,
|
||||
COST_SUMMARY.timesheet_unit_amount AS timesheet_unit_amount,
|
||||
COST_SUMMARY.timesheet_cost AS timesheet_cost,
|
||||
COST_SUMMARY.expense_cost AS expense_cost,
|
||||
COST_SUMMARY.other_revenues AS other_revenues
|
||||
FROM project_project P
|
||||
JOIN res_company C ON C.id = P.company_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
TS.so_line AS sale_line_id,
|
||||
SUM(TS.unit_amount) AS timesheet_unit_amount,
|
||||
SUM(TS.amount) AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM account_analytic_line TS, project_project P
|
||||
WHERE TS.project_id IS NOT NULL AND P.id = TS.project_id AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, TS.so_line
|
||||
project_id,
|
||||
analytic_account_id,
|
||||
sale_line_id,
|
||||
SUM(timesheet_unit_amount) AS timesheet_unit_amount,
|
||||
SUM(timesheet_cost) AS timesheet_cost,
|
||||
SUM(expense_cost) AS expense_cost,
|
||||
SUM(other_revenues) AS other_revenues,
|
||||
SUM(downpayment_invoiced) AS downpayment_invoiced
|
||||
FROM (
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
TS.so_line AS sale_line_id,
|
||||
SUM(TS.unit_amount) AS timesheet_unit_amount,
|
||||
SUM(TS.amount) AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM account_analytic_line TS, project_project P
|
||||
WHERE TS.project_id IS NOT NULL AND P.id = TS.project_id AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, TS.so_line
|
||||
|
||||
UNION
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
SUM(AAL.amount) AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount > 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
SUM(AAL.amount) AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount > 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
|
||||
UNION
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
SUM(AAL.amount) AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount < 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
SUM(AAL.amount) AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount < 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
|
||||
UNION
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
|
||||
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
|
||||
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
|
||||
WHERE MY_SOLS.is_downpayment = 't'
|
||||
GROUP BY P.id, MY_SOLS.id
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
|
||||
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
|
||||
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
|
||||
WHERE MY_SOLS.is_downpayment = 't'
|
||||
GROUP BY P.id, MY_SOLS.id
|
||||
|
||||
UNION
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
OLIS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
|
||||
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
|
||||
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
|
||||
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
|
||||
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, OLIS.id
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
OLIS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
|
||||
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
|
||||
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
|
||||
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
|
||||
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, OLIS.id
|
||||
|
||||
UNION
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_project P ON SOL.project_id = P.id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_project P ON SOL.project_id = P.id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_task T ON SOL.task_id = T.id
|
||||
INNER JOIN project_project P ON P.id = T.project_id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
) SUB_COST_SUMMARY
|
||||
GROUP BY project_id, analytic_account_id, sale_line_id
|
||||
) COST_SUMMARY ON COST_SUMMARY.project_id = P.id
|
||||
LEFT JOIN sale_order_line SOL ON COST_SUMMARY.sale_line_id = SOL.id
|
||||
LEFT JOIN sale_order S ON SOL.order_id = S.id
|
||||
LEFT JOIN product_product PP on (SOL.product_id=PP.id)
|
||||
LEFT JOIN product_template T on (PP.product_tmpl_id=T.id)
|
||||
WHERE P.active = 't' AND P.analytic_account_id IS NOT NULL
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_task T ON SOL.task_id = T.id
|
||||
INNER JOIN project_project P ON P.id = T.project_id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
) SUB_COST_SUMMARY
|
||||
GROUP BY project_id, analytic_account_id, sale_line_id
|
||||
) COST_SUMMARY ON COST_SUMMARY.project_id = P.id
|
||||
LEFT JOIN sale_order_line SOL ON COST_SUMMARY.sale_line_id = SOL.id
|
||||
LEFT JOIN sale_order S ON SOL.order_id = S.id
|
||||
LEFT JOIN product_product PP on (SOL.product_id=PP.id)
|
||||
LEFT JOIN product_template T on (PP.product_tmpl_id=T.id)
|
||||
WHERE P.active = 't' AND P.analytic_account_id IS NOT NULL
|
||||
) AS sub
|
||||
)
|
||||
""" % self._table
|
||||
self._cr.execute(query)
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<field name="product_id"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<filter string="My Project" name="my_project" domain="[('user_id','=', uid)]"/>
|
||||
<filter string="My Projects" name="my_project" domain="[('user_id','=', uid)]"/>
|
||||
<group expand="1" string="Group By">
|
||||
<filter string="Project" name="group_by_project" context="{'group_by':'project_id'}"/>
|
||||
<filter string="Project Manager" name="group_by_user_id" context="{'group_by':'user_id'}"/>
|
||||
|
||||
@@ -85,6 +85,9 @@ $canceled-color: gray;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
.progress {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
>.o_timesheet_plan_badge {
|
||||
margin-bottom: 10px;
|
||||
|
||||
@@ -37,12 +37,27 @@
|
||||
>
|
||||
<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']">
|
||||
<span class="o_stat_value" t-if="stat_button.get('count')">
|
||||
<t t-esc="stat_button['count']"/>
|
||||
</span>
|
||||
<span class="o_stat_text">
|
||||
<t t-esc="stat_button['name']"/>
|
||||
</span>
|
||||
<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-if="'count' in stat_button">
|
||||
<t t-esc="stat_button['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>
|
||||
@@ -238,38 +253,43 @@
|
||||
<t t-foreach="repartition_employee" t-as="employee_id">
|
||||
<t t-set="employee" t-value="repartition_employee[employee_id]"/>
|
||||
<tr>
|
||||
<td style="width: 15%">
|
||||
<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%">
|
||||
<td class="text-right pr-5" style="width: 10%; vertical-align: middle;">
|
||||
<t t-esc="employee['total']" t-options="{'widget': 'float_time'}"/>
|
||||
</td>
|
||||
<td>
|
||||
<div t-if="repartition_employee_max" class="progress" t-attf-style="width: {{max(0, employee['total'] / repartition_employee_max * 100)}}%">
|
||||
<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 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>
|
||||
<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 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>
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
<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"/>
|
||||
</div>
|
||||
<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='emails']" position="after">
|
||||
<xpath expr="//page[@name='settings']" position="after">
|
||||
<page name="billing_employee_rate" string="Invoicing" attrs="{'invisible': [('billable_type', '=', 'no')]}">
|
||||
<group>
|
||||
<field name="display_create_order" invisible="1"/>
|
||||
@@ -114,10 +117,10 @@
|
||||
</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>
|
||||
<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>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
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="timesheet_uom"/>
|
||||
<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>
|
||||
|
||||
@@ -884,7 +884,7 @@ tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_p
|
||||
},
|
||||
tour.stepUtils.mobileModifier(tour.stepUtils.autoExpandMoreButtons('.o_control_panel .breadcrumb:contains("the_flow.project")')),
|
||||
{
|
||||
trigger: '.oe_button_box .oe_stat_button:has(span:contains("Timesheets"))',
|
||||
trigger: '.oe_button_box .oe_stat_button:has(span:contains("Recorded"))',
|
||||
extra_trigger: '.o_form_readonly',
|
||||
content: _t('See Timesheets'),
|
||||
position: 'bottom',
|
||||
|
||||
Reference in New Issue
Block a user