[IMP] hr_expense: Allow a user to refuse an expense sheet from a line

Purpose
=======

Currently, a manager or a accountant can only refuse one expense report with a justification message.

If there are a lot of lines too comment individually or if only one line should be modified, the manager or the accountant can now refused one line and give a reason for it. That will refused the expense report automatically while waiting the employee to make the modifications.

Specification
=============

- Rename wizard files, models and ids according to the model

- Allow to refuse one or several lines with a reason

- Use a qweb template to log messages, use message_post_with_view instead of message_post

- Print expense name instead of sheet name. In the log when expense is refused, it prints expense report name instead of expense

- Prevent modifying approved expenses
	* A user should not be able to modify an expense once approved
	* Method refuse_expense renamed to refuse_sheet when applied to a whole
	expense report/sheet
	* 'model' dict key renamed to something more explicit and less prone to
	confusion (passed in context to the refuse wizard)
	* Usage of explicit fields in the wizard to avoid confusion
	* Overriding 'default_get' to get default values for those fields

- When an expense is paid or reported, it must be impossible to refuse it.

- Replace refused expenses tree
	Purpose: The menu 'Refused expenses' is confusing. This improvement
	replaces it by 'Refused Reports' and leads to the refused reports tree
	view. That way, it's easier for the user to find back his refused
	expenses reports.

- Hide create button on refused
This commit is contained in:
d-fence
2017-09-01 15:30:30 +02:00
committed by Yannick Tivisse
parent 5b7e991c42
commit c030213fa3
12 changed files with 100 additions and 64 deletions
+2 -2
View File
@@ -32,8 +32,8 @@ This module also uses analytic accounting and is compatible with the invoice on
'security/ir.model.access.csv',
'data/hr_expense_data.xml',
'data/hr_expense_sequence.xml',
'wizard/hr_expense_refuse_reason.xml',
'wizard/hr_expense_register_payment.xml',
'wizard/hr_expense_refuse_reason_views.xml',
'wizard/hr_expense_sheet_register_payment.xml',
'views/hr_expense_views.xml',
'security/ir_rule.xml',
'report/report_expense_sheet.xml',
+13 -4
View File
@@ -44,6 +44,7 @@ class HrExpense(models.Model):
help="Status of the expense.")
sheet_id = fields.Many2one('hr.expense.sheet', string="Expense Report", readonly=True, copy=False)
reference = fields.Char(string="Bill Reference")
is_refused = fields.Boolean(string="Explicitely Refused by manager or acccountant", readonly=True, copy=False)
@api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state')
def _compute_state(self):
@@ -307,6 +308,13 @@ class HrExpense(models.Model):
res['context'] = {'default_res_model': 'hr.expense', 'default_res_id': self.id}
return res
@api.multi
def refuse_expense(self,reason):
self.write({'is_refused': True})
self.sheet_id.write({'state': 'cancel'})
self.sheet_id.message_post_with_view('hr_expense.hr_expense_template_refuse_reason',
values={'reason': reason, 'is_sheet':False, 'name':self.name})
@api.model
def get_empty_list_help(self, help_message):
if help_message:
@@ -392,7 +400,7 @@ class HrExpenseSheet(models.Model):
('done', 'Paid'),
('cancel', 'Refused')
], string='Status', index=True, readonly=True, track_visibility='onchange', copy=False, default='submit', required=True,
help='Expense Report State')
help='Expense Report State')
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'submit': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1))
address_id = fields.Many2one('res.partner', string="Employee Home Address")
payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], related='expense_line_ids.payment_mode', default='own_account', readonly=True, string="Payment By")
@@ -512,11 +520,11 @@ class HrExpenseSheet(models.Model):
self.attachment_number = sum(self.expense_line_ids.mapped('attachment_number'))
@api.multi
def refuse_expenses(self, reason):
def refuse_sheet(self, reason):
self.write({'state': 'cancel'})
for sheet in self:
body = (_("Your Expense %s has been refused.<br/><ul class=o_timeline_tracking_value_list><li>Reason<span> : </span><span class=o_timeline_tracking_value>%s</span></li></ul>") % (sheet.name, reason))
sheet.message_post(body=body)
sheet.message_post_with_view('hr_expense.hr_expense_template_refuse_reason',
values={'reason': reason ,'is_sheet':True ,'name':self.name})
@api.multi
def approve_expense_sheets(self):
@@ -528,6 +536,7 @@ class HrExpenseSheet(models.Model):
@api.multi
def reset_expense_sheets(self):
self.mapped('expense_line_ids').write({'is_refused': False})
return self.write({'state': 'submit'})
@api.multi
+4 -1
View File
@@ -6,7 +6,10 @@
<field name="name">Manager Expense</field>
<field name="model_id" ref="model_hr_expense"/>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('account.group_account_user')), (4, ref('hr_expense.group_hr_expense_manager')), (4, ref('hr_expense.group_hr_expense_user'))]"/>
<field name="groups" eval="[
(4, ref('account.group_account_user')),
(4, ref('hr_expense.group_hr_expense_manager')),
(4, ref('hr_expense.group_hr_expense_user'))]"/>
</record>
<record id="property_rule_expense_employee" model="ir.rule">
<field name="name">Employee Expense</field>
+6
View File
@@ -7,5 +7,11 @@
<script type="text/javascript" src="/hr_expense/static/src/js/web_planner_hr_expense.js"></script>
</xpath>
</template>
<template id="hr_expense_template_refuse_reason">
<p>Your Expense <t t-if="is_sheet">Report </t><t t-esc="name"/> has been refused</p>
<ul class="o_timeline_tracking_value_list">
<li>Reason : <t t-esc="reason"/></li>
</ul>
</template>
</data>
</odoo>
+25 -32
View File
@@ -225,26 +225,6 @@
</field>
</record>
<record id="action_hr_expense_refused_expenses" model="ir.actions.act_window">
<field name="name">Refused Expenses</field>
<field name="res_model">hr.expense</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="search_view_id" ref="view_hr_expense_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="context">{'search_default_refused': 1}</field>
<field name="help" type="html">
<p>
The steps to process expenses are:
<ol>
<li>You record expenses,</li>
<li>Periodically, you submit expenses report to your manager,</li>
<li>Once approved, your expense report, goes to the accounting,</li>
<li>The accountant validates entries and reimburse you.</li>
</ol>
</p>
</field>
</record>
<record id="view_product_hr_expense_form" model="ir.ui.view">
<field name="name">product.template.expense.form</field>
<field name="model">product.template</field>
@@ -387,7 +367,7 @@
<field name="name">hr.expense.sheet.tree</field>
<field name="model">hr.expense.sheet</field>
<field name="arch" type="xml">
<tree string="Expense Reports" decoration-warning="state=='draft'" decoration-bf="message_unread == True">
<tree string="Expense Reports" decoration-warning="state=='draft'" decoration-bf="message_unread == True" decoration-danger="state=='cancel'">
<field name="name" string="Expense Sheet"/>
<field name="accounting_date"/>
<field name="employee_id"/>
@@ -407,11 +387,10 @@
<form string="Expense Reports" class="o_expense_sheet">
<header>
<button name="approve_expense_sheets" states="submit" string="Approve" type="object" groups="hr_expense.group_hr_expense_user" class="oe_highlight o_expense_sheet_approve"/>
<button name="action_sheet_move_create" states="approve" string="Validate" type="object" groups="account.group_account_manager" class="oe_highlight o_expense_sheet_post"/>
<button name="%(hr_expense.hr_expense_register_payment_wizard_action)d" type="action" string="Register Payment" class="oe_highlight o_expense_sheet_pay" attrs="{'invisible': [('state', '!=', 'post')]}" context="{'default_amount': total_amount, 'partner_id': address_id}" groups="account.group_account_manager"/>
<button name="action_sheet_move_create" states="approve" string="Post Journal Entries" type="object" groups="account.group_account_manager" class="oe_highlight o_expense_sheet_post"/>
<button name="%(hr_expense.hr_expense_sheet_register_payment_wizard_action)d" type="action" string="Register Payment" class="oe_highlight o_expense_sheet_pay" attrs="{'invisible': [('state', '!=', 'post')]}" context="{'default_amount': total_amount, 'partner_id': address_id}" groups="account.group_account_manager"/>
<button name="reset_expense_sheets" states="cancel" string="Resubmit" type="object"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="submit" string="Refuse" type="action" groups="hr_expense.group_hr_expense_user" />
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="approve" string="Refuse" type="action" groups="hr_expense.group_hr_expense_manager"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="submit,approve" context="{'hr_expense_refuse_model':'hr.expense.sheet'}" string="Refuse" type="action" groups="hr_expense.group_hr_expense_user" />
<field name="state" widget="statusbar" statusbar_visible="draft,submit,approve,post,done"/>
</header>
<sheet>
@@ -443,8 +422,8 @@
</group>
<notebook>
<page string="Expense Lines">
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft')]" options="{'not_delete': True}">
<tree>
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft')]" options="{'not_delete': True, 'reload_on_button': True}">
<tree decoration-danger="is_refused">
<field name="date"/>
<field name="name"/>
<field name="state" invisible="1"/>
@@ -455,6 +434,8 @@
<button name="action_get_attachment_view" string="View Attachments" type="object" icon="fa-paperclip"/>
<field name="tax_ids" widget="many2many_tags"/>
<field name="total_amount" sum="Total Amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="is_refused" invisible="True"/>
<button name="%(hr_expense.hr_expense_refuse_wizard_action)d" states="reported, refused" string="Refuse" context="{'hr_expense_refuse_model':'hr.expense'}" type="action" groups="hr_expense.group_hr_expense_user, account.group_account_user" icon="fa-times" attrs="{'invisible': ['|', ('is_refused', '=', True),('state', 'in', ['done'])]}"/>
</tree>
</field>
</page>
@@ -462,15 +443,15 @@
<group>
<group>
<field name="journal_id" domain="[('type', '=', 'purchase')]" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'own_account')]}"/>
<field name="bank_journal_id" groups="account.group_account_invoice" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'company_account')]}" domain="[('type', 'in', ['cash', 'bank'])]"/>
<field name="accounting_date" attrs="{'invisible': [('state', 'not in', ['approve', 'post', 'done'])]}"/>
<field name="bank_journal_id" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'company_account')]}" domain="[('type', 'in', ['cash', 'bank'])]"/>
<field name="accounting_date" groups="account.group_account_user" attrs="{'invisible': [('state', 'not in', ['approve', 'post', 'done'])]}"/>
</group>
<group>
<field name="account_move_id" groups="account.group_account_user" attrs="{'invisible': [('state', 'not in', ['post', 'done'])]}" readonly="1"/>
</group>
</group>
</page>
</notebook>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
@@ -581,7 +562,7 @@
<field name="res_model">hr.expense.sheet</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="search_view_id" ref="view_hr_expense_sheet_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
<field name="domain">[('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')]</field>
<field name="context">{}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@@ -592,6 +573,18 @@
</field>
</record>
<record id="action_hr_expense_sheet_my_refused" model="ir.actions.act_window">
<field name="name">My Refused Reports</field>
<field name="res_model">hr.expense.sheet</field>
<field name="view_mode">tree,kanban,form,graph,pivot</field>
<field name="search_view_id" ref="view_hr_expense_sheet_filter"/>
<field name="domain">[('employee_id.user_id', '=', uid), ('state', '=', 'cancel')]</field>
<field name="context">{'create': False}</field>
<field name="help" type="html">
<p>No refused reports to display.</p>
</field>
</record>
<record id="action_hr_expense_sheet_all_to_approve" model="ir.actions.act_window">
<field name="name">Expense Reports to Approve</field>
<field name="res_model">hr.expense.sheet</field>
@@ -748,7 +741,7 @@ if records:
<menuitem id="menu_hr_expense_my_expenses" name="My Expenses" sequence="1" parent="menu_hr_expense_root" groups="base.group_user"/>
<menuitem id="menu_hr_expense_my_expenses_to_submit" name="Expenses to Submit" sequence="1" parent="menu_hr_expense_my_expenses" action="hr_expense_actions_my_unsubmitted"/>
<menuitem id="menu_hr_expense_my_expenses_refused" name="Refused Expenses" sequence="2" parent="menu_hr_expense_my_expenses" action="action_hr_expense_refused_expenses"/>
<menuitem id="menu_hr_expense_sheet_my_reports_refused" name="Refused Reports" sequence="2" parent="menu_hr_expense_my_expenses" action="action_hr_expense_sheet_my_refused"/>
<menuitem id="menu_hr_expense_sheet_my_reports" name="Expense Reports" sequence="3" parent="menu_hr_expense_my_expenses" action="action_hr_expense_sheet_my_all"/>
<menuitem id="menu_hr_expense_to_approve" name="To Approve" sequence="2" parent="menu_hr_expense_root" groups="hr_expense.group_hr_expense_user"/>
+1 -2
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from . import hr_expense_refuse_reason
from . import hr_expense_register_payment
from . import hr_expense_sheet_register_payment
@@ -5,18 +5,42 @@ from odoo import api, fields, models
class HrExpenseRefuseWizard(models.TransientModel):
"""This wizard can be launched from an he.expense (an expense line)
or from an hr.expense.sheet (En expense report)
'hr_expense_refuse_model' must be passed in the context to differentiate
the right model to use.
"""
_name = "hr.expense.refuse.wizard"
_description = "Hr Expense refuse Reason wizard"
_description = "Expense refuse Reason wizard"
description = fields.Char(string='Reason', required=True)
reason = fields.Char(string='Reason', required=True)
hr_expense_ids = fields.Many2many('hr.expense')
hr_expense_sheet_id = fields.Many2one('hr.expense.sheet')
@api.model
def default_get(self, fields):
res = super(HrExpenseRefuseWizard, self).default_get(fields)
active_ids = self.env.context.get('active_ids', [])
refuse_model = self.env.context.get('hr_expense_refuse_model')
if refuse_model == 'hr.expense':
res.update({
'hr_expense_ids': active_ids,
'hr_expense_sheet_id': False,
})
elif refuse_model == 'hr.expense.sheet':
res.update({
'hr_expense_sheet_id': active_ids[0] if active_ids else False,
'hr_expense_ids': [],
})
return res
@api.multi
def expense_refuse_reason(self):
self.ensure_one()
if self.hr_expense_ids:
self.hr_expense_ids.refuse_expense(self.reason)
if self.hr_expense_sheet_id:
self.hr_expense_sheet_id.refuse_sheet(self.reason)
context = dict(self._context or {})
active_ids = context.get('active_ids', [])
expense_sheet = self.env['hr.expense.sheet'].browse(active_ids)
expense_sheet.refuse_expenses(self.description)
return {'type': 'ir.actions.act_window_close'}
@@ -5,11 +5,13 @@
<field name="model">hr.expense.refuse.wizard</field>
<field name="arch" type="xml">
<form string="Expense refuse reason">
<separator string="Reason to refuse expense."/>
<field name="description"/>
<separator string="Reason to refuse Expense"/>
<field name="hr_expense_ids" invisible="1"/>
<field name="hr_expense_sheet_id" invisible="1"/>
<field name="reason"/>
<footer>
<button string='Refuse' name="expense_refuse_reason" type="object" class="oe_highlight"/>
<button string="Cancel" class="btn btn-default" special="cancel"/>
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
@@ -23,4 +25,4 @@
<field name="view_id" ref="hr_expense_refuse_wizard_view_form"/>
<field name="target">new</field>
</record>
</odoo>
</odoo>
@@ -5,10 +5,10 @@ from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from werkzeug import url_encode
class HrExpenseRegisterPaymentWizard(models.TransientModel):
class HrExpenseSheetRegisterPaymentWizard(models.TransientModel):
_name = "hr.expense.register.payment.wizard"
_description = "Hr Expense Register Payment wizard"
_name = "hr.expense.sheet.register.payment.wizard"
_description = "Expense Report Register Payment wizard"
@api.model
def _default_partner_id(self):
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hr_expense_register_payment_view_form" model="ir.ui.view">
<field name="name">hr.expense.register.payment.wizard.form</field>
<field name="model">hr.expense.register.payment.wizard</field>
<record id="hr_expense_sheet_register_payment_view_form" model="ir.ui.view">
<field name="name">hr.expense.sheet.register.payment.wizard.form</field>
<field name="model">hr.expense.sheet.register.payment.wizard</field>
<field name="arch" type="xml">
<form string="Register Payment">
<sheet>
@@ -37,12 +37,12 @@
</record>
<!-- Expenses -->
<record id="hr_expense_register_payment_wizard_action" model="ir.actions.act_window">
<record id="hr_expense_sheet_register_payment_wizard_action" model="ir.actions.act_window">
<field name="name">Register Payment</field>
<field name="res_model">hr.expense.register.payment.wizard</field>
<field name="res_model">hr.expense.sheet.register.payment.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="hr_expense_register_payment_view_form"/>
<field name="view_id" ref="hr_expense_sheet_register_payment_view_form"/>
<field name="target">new</field>
<field name="context">{'default_payment_type': 'inbound'}</field>
<field name="domain">[('partner_type', '=', 'customer')]</field>
+1 -1
View File
@@ -4,7 +4,7 @@ from odoo import models, fields, api
class HrExpenseRegisterPaymentWizard(models.TransientModel):
_inherit = "hr.expense.register.payment.wizard"
_inherit = "hr.expense.sheet.register.payment.wizard"
check_amount_in_words = fields.Char(string="Amount in Words")
check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing')
+3 -3
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data>
<record id="hr_expense_register_payment_view_form_check_inherit" model="ir.ui.view">
<field name="name">hr.expense.register.payment.wizard.form.check.inherited</field>
<field name="model">hr.expense.register.payment.wizard</field>
<field name="inherit_id" ref="hr_expense.hr_expense_register_payment_view_form" />
<field name="name">hr.expense.sheet.register.payment.wizard.form.check.inherited</field>
<field name="model">hr.expense.sheet.register.payment.wizard</field>
<field name="inherit_id" ref="hr_expense.hr_expense_sheet_register_payment_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@name='amount_div']" position="after">
<field name="check_amount_in_words" attrs="{'invisible': [('payment_method_code_2', '!=', 'check_printing')]}" groups="base.group_no_one"/>