diff --git a/addons/hr/data/hr_data.xml b/addons/hr/data/hr_data.xml index bb2cf4b5a08..2b031146e01 100644 --- a/addons/hr/data/hr_data.xml +++ b/addons/hr/data/hr_data.xml @@ -29,5 +29,16 @@ Leave Management (keep track of employee leaves), Expense Management (manage emp + + HR: unknown employee bounce + Your document has not been created + ${user.email} + ${ctx['email_to']|safe} + ${user.lang} + + + + + diff --git a/addons/hr/models/__init__.py b/addons/hr/models/__init__.py index c870236ee6c..ef90454a6d7 100644 --- a/addons/hr/models/__init__.py +++ b/addons/hr/models/__init__.py @@ -2,5 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import hr +import mail_alias +import mail_thread import res_partner import res_users diff --git a/addons/hr/models/mail_alias.py b/addons/hr/models/mail_alias.py new file mode 100644 index 00000000000..710b7efaff7 --- /dev/null +++ b/addons/hr/models/mail_alias.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class Alias(models.Model): + _inherit = 'mail.alias' + + alias_contact = fields.Selection(selection_add=[('employees', 'Authenticated Employees')]) diff --git a/addons/hr/models/mail_thread.py b/addons/hr/models/mail_thread.py new file mode 100644 index 00000000000..d27cbbd2afc --- /dev/null +++ b/addons/hr/models/mail_thread.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import api, models +from odoo.addons.mail.models.mail_thread import decode_header +from odoo.tools import email_split + +_logger = logging.getLogger(__name__) + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + @api.model + def message_route_verify(self, message, message_dict, route, update_author=True, assert_model=True, create_fallback=True, allow_private=False): + res = super(MailThread, self).message_route_verify(message, message_dict, route, update_author, assert_model, create_fallback, allow_private) + + if res: + alias = route[4] + email_from = decode_header(message, 'From') + message_id = message.get('Message-Id') + + # Identically equal to the definition in mail module because sub methods are local + # variables and cannot be called with super + def _create_bounce_email(body_html): + bounce_to = decode_header(message, 'Return-Path') or email_from + bounce_mail_values = { + 'body_html': body_html, + 'subject': 'Re: %s' % message.get('subject'), + 'email_to': bounce_to, + 'auto_delete': True, + } + bounce_from = self.env['ir.mail_server']._get_default_bounce_address() + if bounce_from: + bounce_mail_values['email_from'] = 'MAILER-DAEMON <%s>' % bounce_from + self.env['mail.mail'].create(bounce_mail_values).send() + + def _warn(message): + _logger.info('Routing mail with Message-Id %s: route %s: %s', + message_id, route, message) + + # Alias: check alias_contact settings for employees + + if alias and alias.alias_contact == 'employees': + email_address = email_split(email_from)[0] + employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) + if not employee: + employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) + if not employee: + mail_template = self.env.ref('hr.mail_template_data_unknown_employee_email_address') + _warn('alias %s does not accept unknown employees, skipping' % alias.alias_name) + _create_bounce_email(mail_template.body_html) + return False + return res diff --git a/addons/hr_expense/data/hr_expense_data.xml b/addons/hr_expense/data/hr_expense_data.xml index 3ca4fed609c..e378419120d 100644 --- a/addons/hr_expense/data/hr_expense_data.xml +++ b/addons/hr_expense/data/hr_expense_data.xml @@ -17,6 +17,7 @@ expense + employees @@ -41,18 +42,24 @@ Approved hr.expense.sheet - Expense approved + Expense report approved Refused hr.expense.sheet - Expense refused + Expense report refused - To Approve + Submitted hr.expense.sheet - Expense confirmed, waiting confirmation + Expense report submitted, waiting approval + + + + Paid + hr.expense.sheet + Expense report paid @@ -65,15 +72,5 @@ - - HrExpense: unknown employee bounce - Your expense has not been created - help@odoo.com - ${ctx['email_to']|safe} - ${user.lang} - - - - diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 87273168687..a3c70a5f53f 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -33,13 +33,14 @@ class HrExpense(models.Model): description = fields.Text() payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], default='own_account', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, string="Payment By") attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments') - state = fields.Selection([('draft', 'To Report'), - ('reported', 'Reported'), - ('done', 'Posted'), - ('refused', 'Refused') - ], compute='_compute_state', default="draft", string='Status', index=True, readonly=True, copy=False, required=True, store=True, + state = fields.Selection([ + ('draft', 'To Report'), + ('reported', 'Reported'), + ('done', 'Posted'), + ('refused', 'Refused') + ], compute='_compute_state', string='Status', copy=False, default="draft", index=True, readonly=True, required=True, store=True, help="Status of the expense.") - sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", readonly=True) + sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", readonly=True, copy=False) reference = fields.Char(string="Bill Reference") @api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state') @@ -100,6 +101,8 @@ class HrExpense(models.Model): def submit_expenses(self): if any(expense.state != 'draft' for expense in self): raise UserError(_("You cannot report twice the same line!")) + if len(self.mapped('employee_id')) != 1: + raise UserError(_("You cannot report expenses for different employees in the same report!")) return { 'type': 'ir.actions.act_window', 'view_mode': 'form', @@ -290,26 +293,13 @@ class HrExpense(models.Model): if custom_values is None: custom_values = {} - # Retrieve the email address from the email field. The string is constructed like - # 'foo '. We will extract 'bar' from this email_address = email_split(msg_dict.get('email_from', False))[0] - # Look after an employee who has this email address or an employee for whom the related - # user has this email address. In the case not employee is found, we send back an email - # to explain that the expense will not be created. - employee = self.env['hr.employee'].search([('work_email', 'ilike', email_address)], limit=1) - if not employee: - employee = self.env['hr.employee'].search([('user_id.email', 'ilike', email_address)], limit=1) - if not employee: - access = self.env['ir.config_parameter'].get_param("hr_expense.email.gateway") or 'open' - if access == 'open': - # Open access is mostly used during the on boarding flow, should not be used in production - employee = self.env['hr.employee'].search([('user_id','<>',False)], limit=1) - if not employee: - # Send back an email to explain why the expense has not been created - mail_template = self.env.ref('hr_expense.mail_template_data_expense_unknown_email_address') - mail_template.with_context(email_to=email_address).send_mail(self.env.ref('base.module_hr_expense').id) - return False + employee = self.env['hr.employee'].search([ + '|', + ('work_email', 'ilike', email_address), + ('user_id.email', 'ilike', email_address) + ], limit=1) expense_description = msg_dict.get('subject', '') @@ -381,7 +371,7 @@ class HrExpenseSheet(models.Model): help="The journal used when the expense is done.") bank_journal_id = fields.Many2one('account.journal', string='Bank Journal', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, default=lambda self: self.env['account.journal'].search([('type', 'in', ['case', 'bank'])], limit=1), help="The payment method used when the expense is paid by the company.") accounting_date = fields.Date(string="Accounting Date") - account_move_id = fields.Many2one('account.move', string='Journal Entry', copy=False, track_visibility="onchange") + account_move_id = fields.Many2one('account.move', string='Journal Entry', copy=False) department_id = fields.Many2one('hr.department', string='Department', states={'post': [('readonly', True)], 'done': [('readonly', True)]}) @api.multi @@ -422,6 +412,8 @@ class HrExpenseSheet(models.Model): 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': + return 'hr_expense.mt_expense_paid' return super(HrExpenseSheet, self)._track_subtype(init_values) def _add_followers(self): @@ -474,16 +466,8 @@ class HrExpenseSheet(models.Model): @api.multi def approve_expense_sheets(self): - self.message_post(body=_("The expense has been validated by %s") % (self.env.user.name)) self.write({'state': 'approve', 'responsible_id': self.env.user.id}) - @api.multi - def refuse_expense_sheets(self, reason): - self.write({'state': 'cancel'}) - for sheet in self: - body = (_("Your Expense %s has been refused.
  • Reason : %s
") % (sheet.name, reason)) - sheet.message_post(body=body) - @api.multi def paid_expense_sheets(self): self.write({'state': 'done'}) diff --git a/addons/hr_expense/models/res_config.py b/addons/hr_expense/models/res_config.py index 71bc01b0861..c1eda4f459b 100644 --- a/addons/hr_expense/models/res_config.py +++ b/addons/hr_expense/models/res_config.py @@ -1,29 +1,14 @@ # -*- coding: utf-8 -*- from odoo import api, fields, models -from openerp import SUPERUSER_ID class HrExpenseConfigSettings(models.TransientModel): _name = 'hr.expense.config.settings' _inherit = 'res.config.settings' - alias_expense_open = fields.Selection([ - ('open', "Accept expenses from any email address"), - ('restricted', "Accept expenses from employee email addresses only") - ], "Mail Gateway") - alias_prefix = fields.Char('Default Alias Name for Expenses') alias_domain = fields.Char('Alias Domain', default=lambda self: self.env["ir.config_parameter"].get_param("mail.catchall.domain")) - @api.model - def get_default_alias_expense_open(self, fields): - return {'alias_expense_open': self.env['ir.config_parameter'].get_param("hr_expense.email.gateway") or 'open'} - - @api.multi - def set_default_alias_expense_open(self): - self.env['ir.config_parameter'].set_param("hr_expense.email.gateway", self.alias_expense_open) - return True - @api.model def get_default_alias_prefix(self, fields): alias_name = self.env.ref('hr_expense.mail_alias_expense').alias_name diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index 830e87fb3a7..13c564280f6 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -1,5 +1,6 @@ + hr.expense.tree hr.expense @@ -187,7 +188,7 @@ All Expenses hr.expense - tree,kanban,form,graph,pivot + pivot,graph,tree,kanban,form

@@ -204,26 +205,6 @@ - - My Expenses - hr.expense - tree,kanban,form,graph,pivot - - [('employee_id.user_id', '=', uid)] - {'show_report': True} - -

- The steps to process expenses are: -

    -
  1. You record expenses,
  2. -
  3. Periodically, you submit expenses report to your manager,
  4. -
  5. Once approved, your expense report, goes to the accounting,
  6. -
  7. The accountant validates entries and reimburse you.
  8. -
-

-
-
- My Expenses to Report hr.expense @@ -244,25 +225,24 @@
- - - tree - - - - - - - kanban - - - - - - - form - - + + Refused Expenses + hr.expense + tree,kanban,form,graph,pivot + + [('employee_id.user_id', '=', uid)] + {'search_default_refused': 1} + +

+ The steps to process expenses are: +

    +
  1. You record expenses,
  2. +
  3. Periodically, you submit expenses report to your manager,
  4. +
  5. Once approved, your expense report, goes to the accounting,
  6. +
  7. The accountant validates entries and reimburse you.
  8. +
+

+
@@ -412,6 +392,7 @@ + @@ -578,6 +559,7 @@ + @@ -585,22 +567,6 @@ - - My Reports - hr.expense.sheet - tree,kanban,form,pivot,graph - - [('employee_id.user_id', '=', uid)] - {'search_default_submitted': 1} - -

- Click here to create a new expense report. -

- Once you have created your expense, submit it to your manager who will validate it. -

-
-
- My Reports hr.expense.sheet @@ -668,7 +634,7 @@ My Reports hr.expense.sheet - tree,kanban,form,pivot,graph + pivot,graph,tree,kanban,form [] {} @@ -778,23 +744,24 @@ - - - + + + + - - + + + + + + - - - - - - + + -