[MERGE][IMP] mail, various: add automatic activities generation to ease business flows

This merge make several application automatically generate activities based
on business requirements in order to ease the daily job of people using it.

In this merge

 * methods allowing to create, reschedule, mark as done or unlink activites
   are added in mail.activity.mixin;
 * automated activities are flagged using the newly-added automated field
   to distinguish them from other activities;
 * mail.activity.mixin is added to some models to ensure activities are
   activated;
 * several main applications use this API to integrate activities automation
   in their flow: hr_holidays (leave and allocation), hr_expense (expense
   report), fleet (vehicle contract), maintenance (maintenance request),
   sale (sale order);

Please see sub-commits for more details about the mixin update as well as
the integration in the various applications.

This merge is linked to task ID 39578. Closes #20799 .
This commit is contained in:
Thibault Delavallée
2018-03-27 16:39:14 +02:00
39 changed files with 706 additions and 225 deletions
+2
View File
@@ -35,8 +35,10 @@ Main Features
'views/fleet_vehicle_views.xml',
'views/fleet_vehicle_cost_views.xml',
'views/fleet_board_view.xml',
'views/mail_activity_views.xml',
'data/fleet_cars_data.xml',
'data/fleet_data.xml',
'data/mail_data.xml',
],
'demo': ['data/fleet_demo.xml'],
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="1">
<record id="mail_act_fleet_contract_to_renew" model="mail.activity.type">
<field name="name">Contract to Renew</field>
<field name="icon">fa-car</field>
<field name="res_model_id" ref="fleet.model_fleet_vehicle_log_contract"/>
</record>
</data></odoo>
+7 -12
View File
@@ -71,7 +71,7 @@ class FleetVehicleCost(models.Model):
class FleetVehicleLogContract(models.Model):
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherits = {'fleet.vehicle.cost': 'cost_id'}
_name = 'fleet.vehicle.log.contract'
_description = 'Contract information on a vehicle'
@@ -95,7 +95,8 @@ class FleetVehicleLogContract(models.Model):
name = fields.Text(compute='_compute_contract_name', store=True)
active = fields.Boolean(default=True)
start_date = fields.Date('Contract Start Date', default=fields.Date.context_today,
user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.user, index=True)
start_date = fields.Date('Contract Start Date', default=fields.Date.context_today,
help='Date when the coverage of the contract begins')
expiration_date = fields.Date('Contract Expiration Date', default=lambda self:
self.compute_next_year_date(fields.Date.context_today(self)),
@@ -262,17 +263,12 @@ class FleetVehicleLogContract(models.Model):
date_today = fields.Date.from_string(fields.Date.today())
in_fifteen_days = fields.Date.to_string(date_today + relativedelta(days=+15))
nearly_expired_contracts = self.search([('state', '=', 'open'), ('expiration_date', '<', in_fifteen_days)])
res = {}
for contract in nearly_expired_contracts:
if contract.vehicle_id.id in res:
res[contract.vehicle_id.id] += 1
else:
res[contract.vehicle_id.id] = 1
Vehicle = self.env['fleet.vehicle']
for vehicle, value in res.items():
Vehicle.browse(vehicle).message_post(body=_('%s contract(s) will expire soon and should be renewed and/or closed!') % value)
nearly_expired_contracts.write({'state': 'diesoon'})
for contract in nearly_expired_contracts.filtered(lambda contract: contract.user_id):
contract.activity_schedule(
'fleet.mail_act_fleet_contract_to_renew', contract.expiration_date,
user_id=contract.user_id.id)
expired_contracts = self.search([('state', '!=', 'expired'), ('expiration_date', '<',fields.Date.today() )])
expired_contracts.write({'state': 'expired'})
@@ -283,7 +279,6 @@ class FleetVehicleLogContract(models.Model):
now_running_contracts = self.search([('state', '=', 'futur'), ('start_date', '<=', fields.Date.today())])
now_running_contracts.write({'state': 'open'})
@api.model
def run_scheduler(self):
self.scheduler_manage_auto_costs()
self.scheduler_manage_contract_expiration()
@@ -19,3 +19,4 @@ fleet_vehicle_log_services_access_right,fleet_vehicle_log_services_access_right,
fleet_vehicle_log_contract_access_right,fleet_vehicle_log_contract_access_right,model_fleet_vehicle_log_contract,fleet_group_manager,1,1,1,1
fleet_service_type_access_right,fleet_service_type_access_right,model_fleet_service_type,fleet_group_manager,1,1,1,1
access_fleet_vehicle_cost,access_fleet_vehicle_cost,model_fleet_vehicle_cost,fleet_group_manager,1,1,1,1
access_mail_activity_type_fleet_manager,mail.activity.type.fleet.manager,mail.model_mail_activity_type,fleet.fleet_group_manager,1,1,1,1
Can't render this file because it contains an unexpected character in line 6 and column 59.
@@ -187,6 +187,7 @@
<group string="Contract details">
<field name="vehicle_id"/>
<field name="cost_subtype_id" required="1" domain="['|',('category','=','contract'),('category','=','both')]"/>
<field name="user_id"/>
<field name="amount" string="Activation Cost" help="Cost that is paid only once at the creation of the contract"/>
<label for="cost_generated"/>
<div>
@@ -250,6 +251,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
</div>
</form>
@@ -336,6 +338,15 @@
<filter string="In Progress" name="open" domain="[('state', '=', 'open')]"/>
<filter string="Expired" name="expired" domain="[('state', '=', 'expired')]"/>
<filter string="Expiring Soon" name="expire_soon" domain="[('state', '=', 'diesoon')]"/>
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<group expand="0" string="Group By">
<filter string="Vehicle" name="vehicle" context="{'group_by':'vehicle_id'}"/>
</group>
+1 -1
View File
@@ -434,6 +434,6 @@
</field>
</record>
<menuitem id="fleet_vehicle_tag_menu" parent="fleet_configuration" action="fleet_vehicle_tag_action" sequence="112"/>
<menuitem id="fleet_vehicle_tag_menu" parent="fleet_configuration" action="fleet_vehicle_tag_action" sequence="89"/>
</odoo>
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<odoo>
<!-- Activity types config -->
<record id="mail_activity_type_action_config_fleet" model="ir.actions.act_window">
<field name="name">Activity Types</field>
<field name="res_model">mail.activity.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'fleet.vehicle.log.contract')]</field>
<field name="context">{'default_res_model': 'fleet.vehicle.log.contract'}</field>
</record>
<menuitem id="fleet_menu_config_activity_type"
action="mail_activity_type_action_config_fleet"
parent="fleet_configuration"
sequence="99"
groups="base.group_no_one"/>
</odoo>
+1
View File
@@ -36,6 +36,7 @@ This module also uses analytic accounting and is compatible with the invoice on
'wizard/hr_expense_refuse_reason_views.xml',
'wizard/hr_expense_sheet_register_payment.xml',
'views/hr_expense_views.xml',
'views/mail_activity_views.xml',
'security/ir_rule.xml',
'report/hr_expense_report.xml',
'views/hr_department_views.xml',
+7 -16
View File
@@ -2,7 +2,13 @@
<odoo>
<data noupdate="1">
<!--default alias for leads-->
<record id="mail_act_expense_approval" model="mail.activity.type">
<field name="name">Expense Approval</field>
<field name="icon">fa-dollar</field>
<field name="res_model_id" ref="hr_expense.model_hr_expense_sheet"/>
</record>
<!-- default alias for expenses -->
<record id="mail_alias_expense" model="mail.alias">
<field name="alias_name">expense</field>
<field name="alias_model_id" ref="model_hr_expense"/>
@@ -23,27 +29,12 @@
<field name="default" eval="True"/>
<field name="description">Expense report refused</field>
</record>
<record id="mt_expense_confirmed" model="mail.message.subtype">
<field name="name">Submitted</field>
<field name="res_model">hr.expense.sheet</field>
<field name="description">Expense report submitted, waiting approval</field>
<field name="default" eval="True"/>
</record>
<record id="mt_expense_paid" model="mail.message.subtype">
<field name="name">Paid</field>
<field name="res_model">hr.expense.sheet</field>
<field name="description">Expense report paid</field>
<field name="default" eval="False"/>
</record>
<!-- Department (Parent) related subtypes for messaging / Chatter -->
<record id="mt_department_expense_confirmed" model="mail.message.subtype">
<field name="name">Expenses To Approve</field>
<field name="res_model">hr.department</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_expense_confirmed')"/>
<field name="relation_field">department_id</field>
<field name="sequence" eval="10"/>
</record>
<!-- Email Templates -->
<template id="hr_expense_template_refuse_reason">
+25 -4
View File
@@ -450,7 +450,7 @@ class HrExpense(models.Model):
class HrExpenseSheet(models.Model):
_name = "hr.expense.sheet"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Expense Report"
_order = "accounting_date desc, id desc"
@@ -529,6 +529,7 @@ class HrExpenseSheet(models.Model):
def create(self, vals):
self._create_set_followers(vals)
sheet = super(HrExpenseSheet, self).create(vals)
sheet.activity_update()
return sheet
@api.multi
@@ -554,8 +555,6 @@ class HrExpenseSheet(models.Model):
self.ensure_one()
if 'state' in init_values and self.state == 'approve':
return 'hr_expense.mt_expense_approved'
elif 'state' in init_values and self.state == 'submit':
return 'hr_expense.mt_expense_confirmed'
elif 'state' in init_values and self.state == 'cancel':
return 'hr_expense.mt_expense_refused'
elif 'state' in init_values and self.state == 'done':
@@ -621,6 +620,7 @@ class HrExpenseSheet(models.Model):
self.write({'state': 'post'})
else:
self.write({'state': 'done'})
self.activity_update()
return res
@api.multi
@@ -649,6 +649,7 @@ class HrExpenseSheet(models.Model):
raise UserError(_("Only HR Officers can approve expenses"))
responsible_id = self.user_id.id or self.env.user.id
self.write({'state': 'approve', 'user_id': responsible_id})
self.activity_update()
@api.multi
def paid_expense_sheets(self):
@@ -661,8 +662,28 @@ class HrExpenseSheet(models.Model):
self.write({'state': 'cancel'})
for sheet in self:
sheet.message_post_with_view('hr_expense.hr_expense_template_refuse_reason', values={'reason': reason, 'is_sheet': True, 'name': self.name})
self.activity_update()
@api.multi
def reset_expense_sheets(self):
self.mapped('expense_line_ids').write({'is_refused': False})
return self.write({'state': 'submit'})
self.write({'state': 'submit'})
self.activity_update()
return True
def _get_responsible_for_approval(self):
if self.user_id:
return self.user_id
elif self.employee_id.parent_id.user_id:
return self.employee_id.parent_id.user_id
elif self.employee_id.department_id.manager_id.user_id:
return self.employee_id.department_id.manager_id.user_id
return self.env.user
def activity_update(self):
for expense_report in self.filtered(lambda hol: hol.state == 'submit'):
self.activity_schedule(
'hr_expense.mail_act_expense_approval', fields.Date.today(),
user_id=expense_report._get_responsible_for_approval().id)
self.filtered(lambda hol: hol.state == 'approve').activity_feedback(['hr_expense.mail_act_expense_approval'])
self.filtered(lambda hol: hol.state == 'cancel').activity_unlink(['hr_expense.mail_act_expense_approval'])
@@ -1,5 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_expense_user,hr.expense.user,model_hr_expense,hr_expense.group_hr_expense_user,1,1,1,1
access_hr_expense_employee,hr.expense.employee,model_hr_expense,base.group_user,1,1,1,1
access_hr_expense_sheet_user,hr.expense.sheet.user,model_hr_expense_sheet,hr_expense.group_hr_expense_user,1,1,1,1
@@ -15,3 +14,4 @@ access_account_invoice_tax_user,account.invoice.tax.user,account.model_account_i
access_account_move_user,account.move.user,account.model_account_move,hr_expense.group_hr_expense_user,1,0,0,0
access_account_move_line_user,account.move.line.user,account.model_account_move_line,hr_expense.group_hr_expense_user,1,0,0,0
access_account_analytic_line_user,account.analytic.line.user,account.model_account_analytic_line,hr_expense.group_hr_expense_user,1,1,1,1
access_mail_activity_type_expense_user,mail.activity.type.expense.user,mail.model_mail_activity_type,hr_expense.group_hr_expense_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_hr_expense_user hr.expense.user model_hr_expense hr_expense.group_hr_expense_user 1 1 1 1
2 access_hr_expense_employee access_hr_expense_user hr.expense.employee hr.expense.user model_hr_expense base.group_user hr_expense.group_hr_expense_user 1 1 1 1
3 access_hr_expense_sheet_user access_hr_expense_employee hr.expense.sheet.user hr.expense.employee model_hr_expense_sheet model_hr_expense hr_expense.group_hr_expense_user base.group_user 1 1 1 1
4 access_hr_expense_sheet_employee access_hr_expense_sheet_user hr.expense.sheet.employee hr.expense.sheet.user model_hr_expense_sheet base.group_user hr_expense.group_hr_expense_user 1 1 1 1
14 access_account_move_line_user access_account_move_user account.move.line.user account.move.user account.model_account_move_line account.model_account_move hr_expense.group_hr_expense_user 1 0 0 0
15 access_account_analytic_line_user access_account_move_line_user account.analytic.line.user account.move.line.user account.model_account_analytic_line account.model_account_move_line hr_expense.group_hr_expense_user 1 1 0 1 0 1 0
16 access_account_analytic_line_user account.analytic.line.user account.model_account_analytic_line hr_expense.group_hr_expense_user 1 1 1 1
17 access_mail_activity_type_expense_user mail.activity.type.expense.user mail.model_mail_activity_type hr_expense.group_hr_expense_user 1 1 1 1
@@ -203,6 +203,15 @@
<filter string="My Expenses" name="my_expenses" domain="[('employee_id.user_id', '=', uid)]"/>
<separator />
<filter string="New Mail" name="message_unread" domain="[('message_unread', '=', True)]"/>
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<group expand="0" string="Group By">
<filter string="Report" name="report" domain="[]" context="{'group_by': 'sheet_id'}"/>
<filter string="Employee" name="employee" domain="[]" context="{'group_by': 'employee_id'}"/>
@@ -487,6 +496,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -576,6 +586,15 @@
<filter string="My Reports" name="my_reports" domain="[('employee_id.user_id', '=', uid)]"/>
<separator />
<filter string="New Mail" name="message_unread" domain="[('message_unread', '=', True)]"/>
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<group expand="0" string="Group By">
<filter string="Employee" name="employee" domain="[]" context="{'group_by': 'employee_id'}"/>
<filter string="Department" name="department" domain="[]" context="{'group_by': 'department_id'}"/>
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<odoo>
<!-- Activity types config -->
<record id="mail_activity_type_action_config_hr_expense" model="ir.actions.act_window">
<field name="name">Activity Types</field>
<field name="res_model">mail.activity.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'hr.expense.sheet')]</field>
<field name="context">{'default_res_model': 'hr.expense.sheet'}</field>
</record>
<menuitem id="hr_expense_menu_config_activity_type"
action="mail_activity_type_action_config_hr_expense"
parent="menu_hr_expense_configuration"
groups="base.group_no_one"/>
</odoo>
+2 -1
View File
@@ -34,10 +34,11 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p
'security/ir.model.access.csv',
'views/resource_views.xml',
'views/hr_leave_views.xml',
'views/hr_leave_type_views.xml',
'views/hr_leave_allocation_views.xml',
'views/hr_leave_views.xml',
'views/hr_views.xml',
'views/mail_activity_views.xml',
'wizard/hr_holidays_summary_department_views.xml',
'wizard/hr_holidays_summary_employees_views.xml',
+24 -68
View File
@@ -1,26 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Leave specific activities -->
<record id="mail_act_leave_approval" model="mail.activity.type">
<field name="name">Leaves Approval</field>
<field name="icon">fa-sun-o</field>
<field name="res_model_id" ref="hr_holidays.model_hr_leave"/>
</record>
<record id="mail_act_leave_second_approval" model="mail.activity.type">
<field name="name">Leaves Second Approve</field>
<field name="icon">fa-sun-o</field>
<field name="res_model_id" ref="hr_holidays.model_hr_leave"/>
</record>
<!-- Leave specific activities -->
<record id="mail_act_leave_allocation_approval" model="mail.activity.type">
<field name="name">Allocation Approval</field>
<field name="icon">fa-sun-o</field>
<field name="res_model_id" ref="hr_holidays.model_hr_leave_allocation"/>
</record>
<record id="mail_act_leave_allocation_second_approval" model="mail.activity.type">
<field name="name">Allocation Second Approve</field>
<field name="icon">fa-sun-o</field>
<field name="res_model_id" ref="hr_holidays.model_hr_leave_allocation"/>
</record>
<!-- Holidays-related subtypes for messaging / Chatter -->
<record id="mt_leave_confirmed" model="mail.message.subtype">
<field name="name">Confirmed</field>
<field name="res_model">hr.leave</field>
<field name="description">Request created and waiting confirmation</field>
</record>
<record id="mt_leave_first_validated" model="mail.message.subtype">
<field name="name">Waiting Second Validation</field>
<field name="res_model">hr.leave</field>
<field name="default" eval="False"/>
<field name="description">Request validated, waiting second validation</field>
</record>
<record id="mt_leave_approved" model="mail.message.subtype">
<field name="name">Approved</field>
<field name="res_model">hr.leave</field>
<field name="description">Request approved</field>
</record>
<record id="mt_leave_refused" model="mail.message.subtype">
<field name="name">Refused</field>
<field name="res_model">hr.leave</field>
@@ -29,25 +39,11 @@
</record>
<!-- Allocation-related subtypes for messaging / Chatter -->
<record id="mt_leave_allocation_confirmed" model="mail.message.subtype">
<field name="name">Confirmed</field>
<field name="res_model">hr.leave.allocation</field>
<field name="description">Request created and waiting confirmation</field>
</record>
<record id="mt_leave_allocation_first_validated" model="mail.message.subtype">
<field name="name">Waiting Second Validation</field>
<field name="res_model">hr.leave.allocation</field>
<field name="default" eval="False"/>
<field name="description">Request validated, waiting second validation</field>
</record>
<record id="mt_leave_allocation_approved" model="mail.message.subtype">
<field name="name">Approved</field>
<field name="res_model">hr.leave.allocation</field>
<field name="description">Request approved</field>
</record>
<record id="mt_leave_allocation_refused" model="mail.message.subtype">
<field name="name">Refused</field>
<field name="res_model">hr.leave.allocation</field>
@@ -56,24 +52,6 @@
</record>
<!-- Department related subtypes for messaging / Chatter -->
<record id="mt_department_leave_confirmed" model="mail.message.subtype">
<field name="name">Leaves Confirmed</field>
<field name="res_model">hr.department</field>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('mt_leave_confirmed')"/>
<field name="relation_field">department_id</field>
<field name="sequence" eval="6"/>
</record>
<record id="mt_department_leave_first_validated" model="mail.message.subtype">
<field name="name">Leaves First Approval</field>
<field name="res_model">hr.department</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_leave_first_validated')"/>
<field name="relation_field">department_id</field>
<field name="sequence" eval="7"/>
</record>
<record id="mt_department_leave_approved" model="mail.message.subtype">
<field name="name">Leaves Approved</field>
<field name="res_model">hr.department</field>
@@ -82,7 +60,6 @@
<field name="relation_field">department_id</field>
<field name="sequence" eval="8"/>
</record>
<record id="mt_department_leave_refused" model="mail.message.subtype">
<field name="name">Leaves Refused</field>
<field name="res_model">hr.department</field>
@@ -91,25 +68,6 @@
<field name="relation_field">department_id</field>
<field name="sequence" eval="9"/>
</record>
<record id="mt_department_leave_allocation_confirmed" model="mail.message.subtype">
<field name="name">Allocations Confirmed</field>
<field name="res_model">hr.department</field>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('mt_leave_allocation_confirmed')"/>
<field name="relation_field">department_id</field>
<field name="sequence" eval="6"/>
</record>
<record id="mt_department_leave_allocation_first_validated" model="mail.message.subtype">
<field name="name">Allocations First Approval</field>
<field name="res_model">hr.department</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_leave_allocation_first_validated')"/>
<field name="relation_field">department_id</field>
<field name="sequence" eval="7"/>
</record>
<record id="mt_department_leave_allocation_approved" model="mail.message.subtype">
<field name="name">Allocation Approved</field>
<field name="res_model">hr.department</field>
@@ -118,7 +76,6 @@
<field name="relation_field">department_id</field>
<field name="sequence" eval="8"/>
</record>
<record id="mt_department_leave_allocation_refused" model="mail.message.subtype">
<field name="name">Allocation Refused</field>
<field name="res_model">hr.department</field>
@@ -127,6 +84,5 @@
<field name="relation_field">department_id</field>
<field name="sequence" eval="9"/>
</record>
</data>
</odoo>
+45 -6
View File
@@ -20,7 +20,7 @@ class HolidaysRequest(models.Model):
_name = "hr.leave"
_description = "Leave"
_order = "date_from desc"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
@api.model
def default_get(self, fields_list):
@@ -249,6 +249,7 @@ class HolidaysRequest(models.Model):
holiday.add_follower(employee_id)
if 'employee_id' in values:
holiday._onchange_employee_id()
holiday.activity_update()
return holiday
@api.multi
@@ -357,13 +358,16 @@ class HolidaysRequest(models.Model):
for linked_request in linked_requests:
linked_request.action_draft()
linked_requests.unlink()
self.activity_update()
return True
@api.multi
def action_confirm(self):
if self.filtered(lambda holiday: holiday.state != 'draft'):
raise UserError(_('Leave request must be in Draft state ("To Submit") in order to confirm it.'))
return self.write({'state': 'confirm'})
self.write({'state': 'confirm'})
self.activity_update()
return True
@api.multi
def action_approve(self):
@@ -387,6 +391,7 @@ class HolidaysRequest(models.Model):
self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id})
self.filtered(lambda hol: not hol.validation_type == 'both').action_validate()
self.activity_update()
return True
@api.multi
@@ -418,6 +423,7 @@ class HolidaysRequest(models.Model):
leaves.action_approve()
if leaves and leaves[0].validation_type == 'both':
leaves.action_validate()
self.activity_update()
return True
@api.multi
@@ -440,8 +446,45 @@ class HolidaysRequest(models.Model):
# If a category that created several holidays, cancel all related
holiday.linked_request_ids.action_refuse()
self._remove_resource_leave()
self.activity_update()
return True
# ------------------------------------------------------------
# Activity methods
# ------------------------------------------------------------
def _get_responsible_for_approval(self):
if self.state == 'confirm' and self.manager_id.user_id:
return self.manager_id.user_id
elif self.state == 'confirm' and self.employee_id.parent_id.user_id:
return self.employee_id.parent_id.user_id
elif self.department_id.manager_id.user_id:
return self.department_id.manager_id.user_id
return self.env.user
def activity_update(self):
to_clean, to_do = self.env['hr.leave'], self.env['hr.leave']
for holiday in self:
if holiday.state == 'draft':
to_clean |= holiday
elif holiday.state == 'confirm':
holiday.activity_schedule(
'hr_holidays.mail_act_leave_approval', fields.Date.today(),
user_id=holiday._get_responsible_for_approval().id)
elif holiday.state == 'validate1':
holiday.activity_feedback(['hr_holidays.mail_act_leave_approval'])
holiday.activity_schedule(
'hr_holidays.mail_act_leave_second_approval', fields.Date.today(),
user_id=holiday._get_responsible_for_approval().id)
elif holiday.state == 'validate':
to_do |= holiday
elif holiday.state == 'refuse':
to_clean |= holiday
if to_clean:
to_clean.activity_unlink(['hr_holidays.mail_act_leave_approval', 'hr_holidays.mail_act_leave_second_approval'])
if to_do:
to_do.activity_feedback(['hr_holidays.mail_act_leave_approval', 'hr_holidays.mail_act_leave_second_approval'])
####################################################
# Messaging methods
####################################################
@@ -450,10 +493,6 @@ class HolidaysRequest(models.Model):
def _track_subtype(self, init_values):
if 'state' in init_values and self.state == 'validate':
return 'hr_holidays.mt_leave_approved'
elif 'state' in init_values and self.state == 'validate1':
return 'hr_holidays.mt_leave_first_validated'
elif 'state' in init_values and self.state == 'confirm':
return 'hr_holidays.mt_leave_confirmed'
elif 'state' in init_values and self.state == 'refuse':
return 'hr_holidays.mt_leave_refused'
return super(HolidaysRequest, self)._track_subtype(init_values)
@@ -14,8 +14,8 @@ _logger = logging.getLogger(__name__)
class HolidaysAllocation(models.Model):
_name = "hr.leave.allocation"
_description = "Allocation"
_inherit = ['mail.thread']
_description = "Leaves Allocation"
_inherit = ['mail.thread', 'mail.activity.mixin']
def _default_employee(self):
return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
@@ -147,6 +147,7 @@ class HolidaysAllocation(models.Model):
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
holiday = super(HolidaysAllocation, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values)
holiday.add_follower(employee_id)
holiday.activity_update()
return holiday
@api.multi
@@ -200,13 +201,16 @@ class HolidaysAllocation(models.Model):
for linked_request in linked_requests:
linked_request.action_draft()
linked_requests.unlink()
self.activity_update()
return True
@api.multi
def action_confirm(self):
if self.filtered(lambda holiday: holiday.state != 'draft'):
raise UserError(_('Leave request must be in Draft state ("To Submit") in order to confirm it.'))
return self.write({'state': 'confirm'})
res = self.write({'state': 'confirm'})
self.activity_update()
return res
@api.multi
def action_approve(self):
@@ -230,6 +234,7 @@ class HolidaysAllocation(models.Model):
self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id})
self.filtered(lambda hol: not hol.validation_type == 'both').action_validate()
self.activity_update()
return True
@api.multi
@@ -259,6 +264,7 @@ class HolidaysAllocation(models.Model):
leaves.action_approve()
if leaves and leaves[0].validation_type == 'both':
leaves.action_validate()
self.activity_update()
return True
@api.multi
@@ -277,8 +283,43 @@ class HolidaysAllocation(models.Model):
holiday.write({'state': 'refuse', 'second_approver_id': current_employee.id})
# If a category that created several holidays, cancel all related
holiday.linked_request_ids.action_refuse()
self.activity_update()
return True
# ------------------------------------------------------------
# Activity methods
# ------------------------------------------------------------
def _get_responsible_for_approval(self):
if self.state == 'confirm' and self.employee_id.parent_id.user_id:
return self.employee_id.parent_id.user_id
elif self.department_id.manager_id.user_id:
return self.department_id.manager_id.user_id
return self.env.user
def activity_update(self):
to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation']
for allocation in self:
if allocation.state == 'draft':
to_clean |= allocation
elif allocation.state == 'confirm':
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_approval', fields.Date.today(),
user_id=allocation._get_responsible_for_approval().id)
elif allocation.state == 'validate1':
allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval'])
allocation.activity_schedule(
'hr_holidays.mail_act_leave_allocation_second_approval', fields.Date.today(),
user_id=allocation._get_responsible_for_approval().id)
elif allocation.state == 'validate':
to_do |= allocation
elif allocation.state == 'refuse':
to_clean |= allocation
if to_clean:
to_clean.activity_unlink(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval'])
if to_do:
to_do.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval'])
####################################################
# Messaging methods
####################################################
@@ -287,10 +328,6 @@ class HolidaysAllocation(models.Model):
def _track_subtype(self, init_values):
if 'state' in init_values and self.state == 'validate':
return 'hr_holidays.mt_leave_allocation_approved'
elif 'state' in init_values and self.state == 'validate1':
return 'hr_holidays.mt_leave_allocation_first_validated'
elif 'state' in init_values and self.state == 'confirm':
return 'hr_holidays.mt_leave_allocation_confirmed'
elif 'state' in init_values and self.state == 'refuse':
return 'hr_holidays.mt_leave_allocation_refused'
return super(HolidaysAllocation, self)._track_subtype(init_values)
@@ -10,3 +10,4 @@ access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.mode
access_calendar_event_hr_user,calendar.event.hr.user,calendar.model_calendar_event,hr_holidays.group_hr_holidays_user,1,1,1,1
access_calendar_event_type_manager,calendar.event.type.manager,calendar.model_calendar_event_type,hr_holidays.group_hr_holidays_manager,1,1,1,1
access_calendar_attendee_hr_user,calendar.attendee.hr.user,calendar.model_calendar_attendee,hr_holidays.group_hr_holidays_user,1,1,1,1
access_mail_activity_type_holidays_manager,mail.activity.type.holidays.manager,mail.model_mail_activity_type,hr_holidays.group_hr_holidays_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
10 access_calendar_event_hr_user calendar.event.hr.user calendar.model_calendar_event hr_holidays.group_hr_holidays_user 1 1 1 1
11 access_calendar_event_type_manager calendar.event.type.manager calendar.model_calendar_event_type hr_holidays.group_hr_holidays_manager 1 1 1 1
12 access_calendar_attendee_hr_user calendar.attendee.hr.user calendar.model_calendar_attendee hr_holidays.group_hr_holidays_user 1 1 1 1
13 access_mail_activity_type_holidays_manager mail.activity.type.holidays.manager mail.model_mail_activity_type hr_holidays.group_hr_holidays_manager 1 1 1 1
@@ -25,6 +25,15 @@
<field name="employee_id"/>
<field name="department_id"/>
<field name="holiday_status_id"/>
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<group expand="0" string="Group By">
<filter name="group_name" string="Description" context="{'group_by':'name'}"/>
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
@@ -77,6 +86,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -116,21 +126,6 @@
</field>
</record>
<record id="view_allocation_tree_customize" model="ir.ui.view">
<field name="name">hr.leave.allocation.tree.customize</field>
<field name="model">hr.leave.allocation</field>
<field name="arch" type="xml">
<tree string="Allocation Requests" editable="top">
<field name="employee_id"/>
<field name="holiday_type"/>
<field name="holiday_status_id"/>
<field name="name" readonly="1"/>
<field name="number_of_days_temp" string="Allocated Days" sum="Remaining Days"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="open_allocation" model="ir.actions.act_window">
<field name="name">Allocation Request</field>
<field name="res_model">hr.leave.allocation</field>
@@ -212,4 +207,42 @@
<field name="act_window_id" ref="request_approve_allocation"/>
</record>
<record id="request_approve_holidays" model="ir.actions.act_window">
<field name="name">Requests to Approve</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="context">{
'search_default_approve':1,
}</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="action_request_approve_holidays_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_holiday"/>
<field name="act_window_id" ref="request_approve_holidays"/>
</record>
<record id="action_request_approve_holidays_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="act_window_id" ref="request_approve_holidays"/>
</record>
<menuitem
id="menu_open_department_leave_allocation_approve" groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
name="Allocations to Approve"
parent="menu_hr_holidays_approvals"
action="open_department_holidays_allocation_approve"
sequence="2"/>
<menuitem
id="menu_open_allocation"
parent="menu_hr_holidays_my_leaves"
action="open_allocation"
sequence="30"/>
</odoo>
@@ -128,4 +128,17 @@
<field name="search_view_id" ref="view_hr_holidays_status_search"/>
</record>
<menuitem
id="menu_hr_holidays_configuration"
name="Configuration"
parent="menu_hr_holidays_root"
sequence="100"/>
<menuitem
id="hr_holidays_status_menu_configuration"
action="open_view_holiday_status"
name="Leave Types"
parent="menu_hr_holidays_configuration"
sequence="1"/>
</odoo>
+25 -47
View File
@@ -43,6 +43,15 @@
<field name="employee_id"/>
<field name="department_id"/>
<field name="holiday_status_id"/>
<separator/>
<filter string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Future Activities" name="activities_upcoming_all"
domain="[('activity_ids.date_deadline', '&gt;', context_today().strftime('%Y-%m-%d'))
]"/>
<group expand="0" string="Group By">
<filter name="group_name" string="Description" context="{'group_by':'name'}"/>
<filter name="group_employee" string="Employee" context="{'group_by':'employee_id'}"/>
@@ -119,6 +128,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -240,31 +250,6 @@
action="open_ask_holidays"
sequence="22"/>
<record model="ir.actions.act_window" id="request_approve_holidays">
<field name="name">Requests to Approve</field>
<field name="res_model">hr.leave</field>
<field name="view_type">form</field>
<field name="context">{
'search_default_approve':1,
}</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
<record id="action_request_approve_holidays_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_holiday"/>
<field name="act_window_id" ref="request_approve_holidays"/>
</record>
<record id="action_request_approve_holidays_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="edit_new_allocation"/>
<field name="act_window_id" ref="request_approve_holidays"/>
</record>
<record id="open_employee_leaves" model="ir.actions.act_window">
<field name="name">Leaves Overview</field>
<field name="res_model">hr.leave</field>
@@ -306,13 +291,6 @@
action="open_department_holidays_approve"
sequence="1"/>
<menuitem
id="menu_open_department_leave_allocation_approve" groups="hr_holidays.group_hr_holidays_manager,hr_holidays.group_hr_holidays_user"
name="Allocations to Approve"
parent="menu_hr_holidays_approvals"
action="open_department_holidays_allocation_approve"
sequence="2"/>
<menuitem
id="menu_open_employee_leave"
parent="menu_hr_holidays_approvals"
@@ -320,15 +298,22 @@
action="open_employee_leaves"
sequence="4"/>
<menuitem
id="menu_hr_holidays_configuration"
name="Configuration"
parent="menu_hr_holidays_root"
sequence="100"
groups="hr_holidays.group_hr_holidays_manager"
action="open_view_holiday_status"/>
<!-- Shortcuts -->
<record id="view_allocation_tree_customize" model="ir.ui.view">
<field name="name">hr.leave.allocation.tree.customize</field>
<field name="model">hr.leave.allocation</field>
<field name="arch" type="xml">
<tree string="Allocation Requests" editable="top">
<field name="employee_id"/>
<field name="holiday_type"/>
<field name="holiday_status_id"/>
<field name="name" readonly="1"/>
<field name="number_of_days_temp" string="Allocated Days" sum="Remaining Days"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="act_hr_employee_holiday_request_approved" model="ir.actions.act_window">
<field name="name">Leaves to be reported in Payslip</field>
<field name="type">ir.actions.act_window</field>
@@ -432,11 +417,4 @@
id="menu_hr_available_holidays_report_tree"
action="action_hr_available_holidays_report"
parent="menu_hr_holidays_report" sequence="5"/>
<menuitem
id="menu_open_allocation"
parent="menu_hr_holidays_my_leaves"
action="open_allocation"
sequence="30"/>
</odoo>
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<odoo>
<!-- Activity types config -->
<record id="mail_activity_type_action_config_hr_holidays" model="ir.actions.act_window">
<field name="name">Activity Types</field>
<field name="res_model">mail.activity.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', 'in', ['hr.leave', 'hr.leave.allocation'])]</field>
<field name="context">{'default_res_model': 'hr.leave'}</field>
</record>
<menuitem id="hr_holidays_menu_config_activity_type"
action="mail_activity_type_action_config_hr_holidays"
parent="menu_hr_holidays_configuration"
groups="base.group_no_one"/>
</odoo>
+1 -1
View File
@@ -237,7 +237,7 @@ class MailController(http.Controller):
if date_deadline:
values['date_deadline'] = date_deadline
if not activity_type_id:
activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')]).id
activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id
if activity_type_id:
values['activity_type_id'] = activity_type_id
activity = request.env['mail.activity'].create(values)
+112 -1
View File
@@ -93,6 +93,9 @@ class MailActivity(models.Model):
active = fields.Boolean(
'Open', default=True,
help='Done reminders should be archived instead of marked as done.')
automated = fields.Boolean(
'Automated activity', readonly=True,
help='Indicates this activity has been created automatically and not by any user.')
# description
user_id = fields.Many2one(
'res.users', 'Assigned to',
@@ -345,7 +348,15 @@ class MailActivityMixin(models.AbstractModel):
There is also a kanban widget defined. It defines a small widget to integrate
in kanban vignettes. It allow to manage activities directly from the kanban
view. Use widget="kanban_activity" on activitiy_ids field in kanban view to
use it."""
use it.
Some context keys allow to control the mixin behavior. Use those in some
specific cases like import
* ``mail_activity_automation_skip``: skip activities automation; it means
no automated activities will be generated, updated or unlinked, allowing
to save computation and avoid generating unwanted activities;
"""
_name = 'mail.activity.mixin'
_description = 'Activity Mixin'
@@ -432,3 +443,103 @@ class MailActivityMixin(models.AbstractModel):
[('res_model', '=', self._name), ('res_id', 'in', record_ids)]
).unlink()
return result
def activity_schedule(self, act_type_xmlid='', date_deadline=None, summary='', note='', **act_values):
""" Schedule an activity on each record of the current record set.
This method allow to provide as parameter act_type_xmlid. This is an
xml_id of activity type instead of directly giving an activity_type_id.
It is useful to avoid having various "env.ref" in the code and allow
to let the mixin handle access rights.
"""
if self.env.context.get('mail_activity_automation_skip'):
return False
if not date_deadline:
date_deadline = fields.Date.today()
if act_type_xmlid:
activity_type = self.sudo().env.ref(act_type_xmlid)
else:
activity_type = self.env['mail.activity.type'].sudo().browse(act_values['activity_type_id'])
model_id = self.env['ir.model']._get(self._name).id
activities = self.env['mail.activity']
for record in self:
create_vals = {
'activity_type_id': activity_type.id,
'summary': summary or activity_type.summary,
'automated': True,
'note': note,
'date_deadline': date_deadline,
'res_model_id': model_id,
'res_id': record.id,
}
create_vals.update(act_values)
activities |= self.env['mail.activity'].create(create_vals)
return activities
def activity_reschedule(self, act_type_xmlids, user_id=None, date_deadline=None):
""" Reschedule some automated activities. Activities to reschedule are
selected based on type xml ids and optionally by user. Purpose is to be
able to change the deadline, not anything else currently. """
if self.env.context.get('mail_activity_automation_skip'):
return False
Data = self.env['ir.model.data'].sudo()
activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids]
domain = [
'&', '&', '&',
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('automated', '=', True),
('activity_type_id', 'in', activity_types_ids)
]
if user_id:
domain = ['&'] + domain + [('user_id', '=', user_id)]
activities = self.env['mail.activity'].search(domain)
if activities:
activities.write({
'date_deadline': date_deadline,
})
return activities
def activity_feedback(self, act_type_xmlids, user_id=None, feedback=None):
""" Set activities as done, limiting to some activity types and
optionally to a given user. """
if self.env.context.get('mail_activity_automation_skip'):
return False
Data = self.env['ir.model.data'].sudo()
activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids]
domain = [
'&', '&', '&',
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('automated', '=', True),
('activity_type_id', 'in', activity_types_ids)
]
if user_id:
domain = ['&'] + domain + [('user_id', '=', user_id)]
activities = self.env['mail.activity'].search(domain)
if activities:
activities.action_feedback(feedback=feedback)
return True
def activity_unlink(self, act_type_xmlids, user_id=None):
""" Unlink activities, limiting to some activity types and optionally
to a given user. """
if self.env.context.get('mail_activity_automation_skip'):
return False
Data = self.env['ir.model.data'].sudo()
activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids]
domain = [
'&', '&', '&',
('res_model', '=', self._name),
('res_id', 'in', self.ids),
('automated', '=', True),
('activity_type_id', 'in', activity_types_ids)
]
if user_id:
domain = ['&'] + domain + [('user_id', '=', user_id)]
self.env['mail.activity'].search(domain).unlink()
return True
+1
View File
@@ -16,6 +16,7 @@
'data/mail_data.xml',
'views/maintenance_views.xml',
'views/maintenance_templates.xml',
'views/mail_activity_views.xml',
'data/maintenance_cron.xml',
],
'demo': ['data/maintenance_demo.xml'],
+7
View File
@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Maintenance-specific activities, for automatic generation mainly -->
<record id="mail_act_maintenance_request" model="mail.activity.type">
<field name="name">Maintenance Request</field>
<field name="icon">fa-wrench</field>
<field name="res_model_id" ref="maintenance.model_maintenance_request"/>
</record>
<!-- email alias for maintenance requests -->
<record id="mail_alias_equipment" model="mail.alias">
<field name="alias_name">helpdesk</field>
+24
View File
@@ -336,6 +336,7 @@ class MaintenanceRequest(models.Model):
request._add_followers()
if request.equipment_id and not request.maintenance_team_id:
request.maintenance_team_id = request.equipment_id.maintenance_team_id
request.activity_update()
return request
@api.multi
@@ -349,8 +350,31 @@ class MaintenanceRequest(models.Model):
self._add_followers()
if self.stage_id.done and 'stage_id' in vals:
self.write({'close_date': fields.Date.today()})
self.activity_feedback(['maintenance.mail_act_maintenance_request'])
if 'schedule_date' in vals:
self.activity_update()
return res
def activity_update(self):
""" Update maintenance activities based on current record set state.
It reschedule, unlink or create maintenance request activities. """
self.filtered(lambda request: not request.schedule_date).activity_unlink(['maintenance.mail_act_maintenance_request'])
for request in self.filtered(lambda request: request.schedule_date):
date_dl = fields.Datetime.from_string(request.schedule_date).date()
updated = request.activity_reschedule(
['maintenance.mail_act_maintenance_request'],
date_deadline=date_dl)
if not updated:
if request.equipment_id:
note = _('Request planned for <a href="#" data-oe-model="%s" data-oe-id="%s">%s</a>') % (
request.equipment_id._name, request.equipment_id.id, request.equipment_id.display_name)
else:
note = False
request.activity_schedule(
'maintenance.mail_act_maintenance_request',
fields.Datetime.from_string(request.schedule_date).date(),
note=note, user_id=request.technician_user_id.id or request.owner_user_id.id or self.env.uid)
def _add_followers(self):
for request in self:
user_ids = (request.owner_user_id + request.technician_user_id).ids
@@ -7,4 +7,5 @@ access_equipment_category_admin_user,equipment.category system user,model_mainte
access_maintenance_stage_user,maintenance.stage.user,model_maintenance_stage,base.group_user,1,0,0,0
access_maintenance_stage_admin_user,equipment.request.stage system user,model_maintenance_stage,group_equipment_manager,1,1,1,1
access_maintenance_team_user,maintenance.team.user,model_maintenance_team,base.group_user,1,0,0,0
access_maintenance_team_admin_user,maintenance.team.admin.user,model_maintenance_team,group_equipment_manager,1,1,1,1
access_maintenance_team_admin_user,maintenance.team.admin.user,model_maintenance_team,group_equipment_manager,1,1,1,1
access_mail_activity_type_equipment_manager,mail.activity.type.equipment.manager,mail.model_mail_activity_type,maintenance.group_equipment_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
7 access_maintenance_stage_user maintenance.stage.user model_maintenance_stage base.group_user 1 0 0 0
8 access_maintenance_stage_admin_user equipment.request.stage system user model_maintenance_stage group_equipment_manager 1 1 1 1
9 access_maintenance_team_user maintenance.team.user model_maintenance_team base.group_user 1 0 0 0
10 access_maintenance_team_admin_user maintenance.team.admin.user model_maintenance_team group_equipment_manager 1 1 1 1
11 access_mail_activity_type_equipment_manager mail.activity.type.equipment.manager mail.model_mail_activity_type maintenance.group_equipment_manager 1 1 1 1
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<odoo>
<!-- Activity types config -->
<record id="mail_activity_type_action_config_maintenance" model="ir.actions.act_window">
<field name="name">Activity Types</field>
<field name="res_model">mail.activity.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'maintenance.request')]</field>
<field name="context">{'default_res_model': 'maintenance.request'}</field>
</record>
<menuitem id="maintenance_menu_config_activity_type"
action="mail_activity_type_action_config_maintenance"
parent="menu_maintenance_configuration"
sequence="20"
groups="base.group_no_one"/>
</odoo>
+1
View File
@@ -26,6 +26,7 @@ This module contains all the common features of Sales Management and eCommerce.
'views/account_invoice_views.xml',
'views/sales_team_views.xml',
'views/res_partner_views.xml',
'views/mail_activity_views.xml',
'views/assets.xml',
'views/sale_portal_templates.xml',
'views/sale_layout_category_view.xml',
+7
View File
@@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Activities -->
<record id="mail_act_sale_upsell" model="mail.activity.type">
<field name="name">Order Upsell</field>
<field name="icon">fa-line-chart</field>
<field name="res_model_id" ref="sale.model_sale_order"/>
</record>
<!-- Sale-related subtypes for messaging / Chatter -->
<record id="mt_order_sent" model="mail.message.subtype">
<field name="name">Quotation sent</field>
+25
View File
@@ -289,6 +289,31 @@ class SaleOrder(models.Model):
result = super(SaleOrder, self).create(vals)
return result
@api.multi
def _write(self, values):
""" Override of private write method in order to generate activities
based in the invoice status. As the invoice status is a computed field
triggered notably when its lines and linked invoice status changes the
flow does not necessarily goes through write if the action was not done
on the SO itself. We hence override the _write to catch the computation
of invoice_status field. """
if self.env.context.get('mail_activity_automation_skip'):
return super(SaleOrder, self)._write(values)
res = super(SaleOrder, self)._write(values)
if 'invoice_status' in values:
self.activity_unlink(['sale.mail_act_sale_upsell'])
if values['invoice_status'] == 'upselling':
for order in self:
order.activity_schedule(
'sale.mail_act_sale_upsell', fields.Date.today(),
user_id=order.user_id.id,
note=_("Upsell <a href='#' data-oe-model='%s' data-oe-id='%d'>%s</a> for customer <a href='#' data-oe-model='%s' data-oe-id='%s'>%s</a>") % (
order._name, order.id, order.name,
order.partner_id._name, order.partner_id.id, order.partner_id.display_name))
return res
@api.multi
def copy_data(self, default=None):
if default is None:
+1
View File
@@ -55,3 +55,4 @@ access_account_tax_group_sale_manager,account.tax.group sale manager,account.mod
access_account_account_sale_manager,account.account sale manager,account.model_account_account,sales_team.group_sale_manager,1,0,0,0
access_report_all_channels_sales_salesman,report.all.channels.sales,model_report_all_channels_sales,sales_team.group_sale_salesman,1,1,1,0
access_report_all_channels_sales_manager,report.all.channels.sales,model_report_all_channels_sales,sales_team.group_sale_manager,1,1,1,1
access_mail_activity_type_sale_manager,mail.activity.type.sale.manager,mail.model_mail_activity_type,sales_team.group_sale_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
55 access_account_account_sale_manager account.account sale manager account.model_account_account sales_team.group_sale_manager 1 0 0 0
56 access_report_all_channels_sales_salesman report.all.channels.sales model_report_all_channels_sales sales_team.group_sale_salesman 1 1 1 0
57 access_report_all_channels_sales_manager report.all.channels.sales model_report_all_channels_sales sales_team.group_sale_manager 1 1 1 1
58 access_mail_activity_type_sale_manager mail.activity.type.sale.manager mail.model_mail_activity_type sales_team.group_sale_manager 1 1 1 1
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<odoo>
<!-- Activity types config -->
<record id="mail_activity_type_action_config_sale" model="ir.actions.act_window">
<field name="name">Activity Types</field>
<field name="res_model">mail.activity.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'sale.order')]</field>
<field name="context">{'default_res_model': 'sale.order'}</field>
</record>
<menuitem id="sale_menu_config_activity_type"
action="mail_activity_type_action_config_sale"
parent="menu_sale_config"
groups="base.group_no_one"/>
</odoo>
+1
View File
@@ -12,6 +12,7 @@ tests independently to functional aspects of other models. """,
'depends': ['test_performance', 'mail'],
'data': [
'security/ir.model.access.csv',
'data/data.xml',
],
'demo': [
'data/demo.xml',
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="mail_act_test_todo" model="mail.activity.type">
<field name="name">Do Stuff</field>
<field name="summary">Really?! Wow! A superpowers drug you can just rub onto your skin?</field>
<field name="category">default</field>
<field name="res_model_id" ref="test_mail.model_mail_test_activity"/>
</record>
<record id="mail_act_test_meeting" model="mail.activity.type">
<field name="name">Meet People</field>
<field name="summary">You'd think it would be something you'd have to freebase. Noooooo!</field>
<field name="category">default</field>
<field name="res_model_id" ref="test_mail.model_mail_test_activity"/>
</record>
<record id="mail_act_test_call" model="mail.activity.type">
<field name="name">Call People</field>
<field name="summary">Then throw her in the laundry room, which will hereafter be referred to as "the brig".</field>
<field name="category">default</field>
<field name="res_model_id" ref="test_mail.model_mail_test_activity"/>
</record>
</odoo>
@@ -38,6 +38,15 @@ class MailTestActivity(models.Model):
name = fields.Char()
email_from = fields.Char()
def action_start(self, action_summary):
self.activity_schedule(
'test_mail.mail_act_test_todo',
summary=action_summary
)
def action_close(self, action_feedback):
self.activity_feedback(['test_mail.mail_act_test_todo'], feedback=action_feedback)
class MailTestFull(models.Model):
""" This model can be used in tests when complex chatter features are
+105 -42
View File
@@ -14,7 +14,13 @@ class TestMailActivity(BaseFunctionalTest):
@classmethod
def setUpClass(cls):
super(TestMailActivity, cls).setUpClass()
cls.test_record = cls.env['mail.test.activity'].create({'name': 'Test'})
cls.test_record = cls.env['mail.test.activity'].with_context(cls._quick_create_ctx).create({'name': 'Test'})
# reset ctx
cls.test_record = cls.test_record.with_context(
mail_create_nolog=False,
mail_create_nosubscribe=False,
mail_notrack=False
)
def test_activity_flow_employee(self):
with self.sudoAs('ernest'):
@@ -55,8 +61,6 @@ class TestMailActivity(BaseFunctionalTest):
with self.sudoAs('chell'):
test_record = self.env['mail.test.activity'].browse(self.test_record.id)
# test_record.name
# test_record.read()
with self.assertRaises(exceptions.AccessError):
self.env['mail.activity'].create({
'summary': 'Test Activity',
@@ -64,52 +68,111 @@ class TestMailActivity(BaseFunctionalTest):
'res_model_id': self.env['ir.model']._get(test_record._name).id,
'res_id': test_record.id,
})
# self.assertEqual(test_record.activity_ids, self.env['mail.activity'])
def test_reminder_security(self):
admin = self.env.ref('base.user_root')
demo = self.env.ref('base.user_demo')
reminder = self.env['mail.activity'].sudo(admin).create({
'note': 'Test Reminder',
'date_deadline': date.today() + relativedelta(days=1),
}) #try to delete record with demo user
with self.assertRaises(exceptions.AccessError):
#try to delete admin record with demo user
reminder.sudo(demo).unlink()
with self.assertRaises(exceptions.AccessError):
#try to update admin record with demo user
reminder.sudo(demo).write({'note': 'Give money to demo user'})
#but demo should be able to edit and delete its own reminder
demo_reminder = self.env['mail.activity'].sudo(demo).create({
'note': 'Test Reminder demo',
'date_deadline': date.today() + relativedelta(days=2),
reminder = self.env['mail.activity'].sudo(self.user_admin).create({
'note': 'Test Reminder',
'date_deadline': date.today() + relativedelta(days=1),
})
demo_reminder.sudo(demo).write({'note': 'Holidays'})
self.assertEqual(demo_reminder.note, '<p>Holidays</p>')
#but edit or res_id and res_model should be impossible
with self.assertRaises(exceptions.AccessError):
demo_reminder.sudo(demo).write({'res_id': 1})
with self.assertRaises(exceptions.AccessError):
demo_reminder.sudo(demo).write({'res_model_id': 1})
#user can unlink own reminder
demo_reminder.sudo(demo).unlink()
# try to delete admin record with demo user
reminder.sudo(self.user_employee).unlink()
def test_reminder_summary(self):
reminder = self.env['mail.activity'].create({
with self.assertRaises(exceptions.AccessError):
# try to update admin record with demo user
reminder.sudo(self.user_employee).write({'note': 'Give money to demo user'})
# but demo should be able to edit and delete its own reminder
demo_reminder = self.env['mail.activity'].sudo(self.user_employee).create({
'note': 'Test Reminder demo',
'date_deadline': date.today() + relativedelta(days=2),
})
demo_reminder.write({'note': '<p>Holidays</p>'})
self.assertEqual(demo_reminder.note, '<p>Holidays</p>')
# edit of res_id and res_model should be impossible
with self.assertRaises(exceptions.AccessError):
demo_reminder.write({'res_id': 1})
with self.assertRaises(exceptions.AccessError):
demo_reminder.write({'res_model_id': 1})
demo_reminder.unlink()
def test_reminder_flow(self):
with self.sudoAs('ernest'):
reminder = self.env['mail.activity'].create({
'note': 'Test Reminder',
'date_deadline': date.today(),
})
self.assertEqual(reminder.summary, 'Test Reminder', "Summary should be first line of note by default")
reminder.write({'note': 'Holidays\nDestination: Pescara'})
self.assertEqual(reminder.summary, 'Holidays', "Summary should be first line of note by default")
reminder.write({'note': ''})
self.assertEqual(reminder.summary, 'Reminder', "If note is empty, summary should be Reminder")
reminder.write({'note': 'Holidays', 'summary': 'Summary'})
self.assertEqual(reminder.summary, 'Summary', "If summary is set, shouldn't be changed")
})
self.assertEqual(reminder.summary, 'Test Reminder')
reminder.write({'note': 'Holidays\nDestination: Pescara'})
self.assertEqual(reminder.summary, 'Holidays')
reminder.write({'note': ''})
self.assertEqual(reminder.summary, 'Reminder')
reminder.write({'note': 'Holidays', 'summary': 'Summary'})
self.assertEqual(reminder.summary, 'Summary')
def test_activity_mixin(self):
today = date.today()
with self.sudoAs('ernest'):
self.assertEqual(self.test_record.env.user, self.user_employee)
# Test various scheduling of activities
act1 = self.test_record.activity_schedule(
'test_mail.mail_act_test_todo',
today + relativedelta(days=1),
user_id=self.user_admin.id)
self.assertEqual(act1.automated, True)
act_type = self.env.ref('test_mail.mail_act_test_todo')
self.assertEqual(self.test_record.activity_summary, act_type.summary)
self.assertEqual(self.test_record.activity_state, 'planned')
self.assertEqual(self.test_record.activity_user_id, self.user_admin)
act2 = self.test_record.activity_schedule(
'test_mail.mail_act_test_meeting',
today + relativedelta(days=-1))
self.assertEqual(self.test_record.activity_state, 'overdue')
self.assertEqual(self.test_record.activity_user_id, self.user_employee)
act3 = self.test_record.activity_schedule(
'test_mail.mail_act_test_todo',
today + relativedelta(days=3),
user_id=self.user_employee.id)
self.assertEqual(self.test_record.activity_state, 'overdue')
self.assertEqual(self.test_record.activity_user_id, self.user_employee)
self.assertEqual(self.test_record.activity_ids, act1 | act2 | act3)
# Perform todo activities for admin
self.test_record.activity_feedback(
['test_mail.mail_act_test_todo'],
user_id=self.user_admin.id,
feedback='Test feedback',)
self.assertEqual(self.test_record.activity_ids, act2 | act3)
# Reschedule all activities, should update the record state
self.assertEqual(self.test_record.activity_state, 'overdue')
self.test_record.activity_reschedule(
['test_mail.mail_act_test_meeting', 'test_mail.mail_act_test_todo'],
date_deadline=today + relativedelta(days=3)
)
self.assertEqual(self.test_record.activity_state, 'planned')
# Perform todo activities for remaining people
self.test_record.activity_feedback(
['test_mail.mail_act_test_todo'],
feedback='Test feedback')
# Setting activities as done should delete them and post messages
self.assertEqual(self.test_record.activity_ids, act2)
self.assertEqual(len(self.test_record.message_ids), 2)
self.assertEqual(self.test_record.message_ids.mapped('subtype_id'), self.env.ref('mail.mt_activities'))
# Perform meeting activities
self.test_record.activity_unlink(['test_mail.mail_act_test_meeting'])
# Canceling activities should simply remove them
self.assertEqual(self.test_record.activity_ids, self.env['mail.activity'])
self.assertEqual(len(self.test_record.message_ids), 2)
+18 -2
View File
@@ -160,16 +160,32 @@ class TestAdvMailPerformance(TransactionCase):
'default_res_model': 'mail.test.activity',
})
with self.assertQueryCount(admin=26, emp=32): # test_mail only: 26 - 32
with self.assertQueryCount(admin=26, emp=33): # test_mail only: 26 - 32
activity = MailActivity.create({
'summary': 'Test Activity',
'res_id': record.id,
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
})
with self.assertQueryCount(admin=59, emp=88): # test_mail only: 58 - 86
with self.assertQueryCount(admin=59, emp=88): # test_mail only: 58 - 87
activity.action_feedback(feedback='Zizisse Done !')
@users('admin', 'emp')
@warmup
@mute_logger('odoo.models.unlink')
def test_adv_activity_mixin(self):
record = self.env['mail.test.activity'].create({'name': 'Test'})
with self.assertQueryCount(admin=26, emp=32): # test_mail only: 26 - 32
record.action_start('Test Start')
record.write({'name': 'Dupe write'})
with self.assertQueryCount(admin=58, emp=88): # test_mail only: 58 - 88
record.action_close('Dupe feedback')
self.assertEqual(record.activity_ids, self.env['mail.activity'])
@mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink')
@users('admin', 'emp')
@warmup