[IMP] hr_expense: Allow modifications depending on the stage

Purpose
=======

When an Expense report hits the "Approved" stage, the Accountant can no
longer edit the account / tax / analytic account fields. However, this
information can still be modified from the Expense itself, impacting
directly the related Expense report anyway. In any case, it makes sense
for an accountant to be able to edit this type of information, as it is
likely that an employee or a manager will input the wrong accounts.

On the other hand, once the Journal entries are posted, the Expense
report should not be editable anymore.

Specifications
==============

For the Expense report (hr.expense.sheet model):

If the report is in the "Approved" stage AND the user has at
least the Accounting / Accountant access right level

The following fields should be editable
- Taxes (tax_ids)
- Bill reference (reference)
- Account (account_id)
- Analytic account (analytic_account_id)

If the user has a lower access right level => the fields listed above remain
in read only.

If it is in the "Posted" stage (regardless of the access right level of the user):

The following fields should be in read only:
- Taxes (tax_ids)
- Account (account_id)
- Analytic account (analytic_account_id)

The "Bill reference" (reference) field should still be editable (current behavior).

Based on the Expense report's stage and the access right level of the user,
the fields of the related Expenses should also be editable (or not).

closes odoo/odoo#32918

Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
jbm-odoo
2019-06-25 10:28:26 +00:00
committed by Yannick Tivisse
parent 1e8db02368
commit ccf4fc244a
2 changed files with 43 additions and 8 deletions
+36 -3
View File
@@ -52,16 +52,16 @@ class HrExpense(models.Model):
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=_default_product_uom_id)
unit_amount = fields.Float("Unit Price", readonly=True, required=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, digits=dp.get_precision('Product Price'))
quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1)
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]})
tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes')
untaxed_amount = fields.Float("Subtotal", store=True, compute='_compute_amount', digits=dp.get_precision('Account'))
total_amount = fields.Monetary("Total", compute='_compute_amount', store=True, currency_field='currency_id', digits=dp.get_precision('Account'))
total_amount_company = fields.Monetary("Total (Company Currency)", compute='_compute_total_amount_company', store=True, currency_field='company_currency_id', digits=dp.get_precision('Account'))
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.company.currency_id)
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='sheet_id.currency_id', store=True, readonly=False)
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account')
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', oldname='analytic_account')
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags', states={'post': [('readonly', True)], 'done': [('readonly', True)]})
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=_default_account_id, help="An expense account is expected")
account_id = fields.Many2one('account.account', string='Account', default=_default_account_id, help="An expense account is expected")
description = fields.Text('Notes...', readonly=True, states={'draft': [('readonly', False)], 'reported': [('readonly', False)], 'refused': [('readonly', False)]})
payment_mode = fields.Selection([
("own_account", "Employee (to reimburse)"),
@@ -79,6 +79,9 @@ class HrExpense(models.Model):
reference = fields.Char("Bill Reference")
is_refused = fields.Boolean("Explicitely Refused by manager or acccountant", readonly=True, copy=False)
is_editable = fields.Boolean("Is Editable By Current User", compute='_compute_is_editable')
is_ref_editable = fields.Boolean("Reference Is Editable By Current User", compute='_compute_is_ref_editable')
@api.depends('sheet_id', 'sheet_id.account_move_id', 'sheet_id.state')
def _compute_state(self):
for expense in self:
@@ -118,6 +121,26 @@ class HrExpense(models.Model):
for expense in self:
expense.attachment_number = attachment.get(expense.id, 0)
@api.depends('employee_id')
def _compute_is_editable(self):
is_account_manager = self.env.user.has_group('account.group_account_user') or self.env.user.has_group('account.group_account_manager')
for expense in self:
if expense.state == 'draft' or expense.sheet_id.state in ['draft', 'submit']:
expense.is_editable = True
elif expense.sheet_id.state == 'approve':
expense.is_editable = is_account_manager
else:
expense.is_editable = False
@api.depends('employee_id')
def _compute_is_ref_editable(self):
is_account_manager = self.env.user.has_group('account.group_account_user') or self.env.user.has_group('account.group_account_manager')
for expense in self:
if expense.state == 'draft' or expense.sheet_id.state in ['draft', 'submit']:
expense.is_ref_editable = True
else:
expense.is_ref_editable = is_account_manager
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
@@ -149,6 +172,16 @@ class HrExpense(models.Model):
raise UserError(_('You cannot delete a posted or approved expense.'))
return super(HrExpense, self).unlink()
@api.multi
def write(self, vals):
if 'tax_ids' in vals or 'analytic_account_id' in vals or 'account_id' in vals:
if any(not expense.is_editable for expense in self):
raise UserError(_('You are not autorized to edit this expense report.'))
if 'reference' in vals:
if any(not expense.is_ref_editable for expense in self):
raise UserError(_('You are not autorized to edit the reference of this expense report.'))
return super(HrExpense, self).write(vals)
@api.model
def get_empty_list_help(self, help_message):
if help_message and "o_view_nocontent_smiling_face" not in help_message:
+7 -5
View File
@@ -76,6 +76,8 @@
</div>
<group>
<group>
<field name="is_editable" invisible="1"/>
<field name="is_ref_editable" invisible="1"/>
<field name="product_id" required="1" context="{'default_can_be_expensed': 1, 'tree_view_ref': 'hr_expense.product_product_expense_tree_view'}"
widget="many2one_barcode"
/>
@@ -85,16 +87,16 @@
<field name="quantity" class="oe_inline"/>
<field name="product_uom_id" required="1" widget="selection" class="oe_inline" groups="uom.group_uom"/>
</div>
<field name="tax_ids" domain="[('type_tax_use', '=', 'purchase')]" widget="many2many_tags" groups="account.group_account_user"/>
<field name="tax_ids" domain="[('type_tax_use', '=', 'purchase')]" widget="many2many_tags" groups="account.group_account_user" attrs="{'readonly': [('is_editable', '=', False)]}"/>
</group><group>
<field name="reference"/>
<field name="reference" attrs="{'readonly': [('is_ref_editable', '=', False)]}"/>
<field name="date"/>
<field name="account_id" options="{'no_create': True}" domain="[('internal_type', '=', 'other')]" groups="account.group_account_user"/>
<field name="account_id" options="{'no_create': True}" domain="[('internal_type', '=', 'other')]" groups="account.group_account_user" attrs="{'readonly': [('is_editable', '=', False)]}"/>
<field name="employee_id" groups="hr_expense.group_hr_expense_team_approver"/>
<field name="sheet_id" invisible="1"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags"/>
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" attrs="{'readonly': [('is_editable', '=', False)]}"/>
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_tags" attrs="{'readonly': [('is_editable', '=', False)]}"/>
<field name="company_id" groups="base.group_multi_company"/>
</group><group>
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}"/>