Skip to content

Commit

Permalink
Merge 81b6f62 into 90641e1
Browse files Browse the repository at this point in the history
  • Loading branch information
nikul-serpentcs committed Jul 14, 2017
2 parents 90641e1 + 81b6f62 commit f313b92
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 0 deletions.
59 changes: 59 additions & 0 deletions hr_expense_operating_unit/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.. image:: https://img.shields.io/badge/license-LGPLv3-blue.svg
:target: https://www.gnu.org/licenses/lgpl.html
:alt: License: LGPL-3

===============================
HR Expense with Operating Units
===============================

This module introduces the following features:

* Adds the Operating Unit (OU) to the Expense.

* Security rules are defined to ensure that users can only see the Expense of that Operating Units in which they are allowed access to.

* Adds Operating Unit (OU) to the account moves while generating accounting entries from the expense.

Usage
=====

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/213/10.0

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/OCA/operating-unit/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

Contributors
------------

* Jordi Ballester Alomar <jordi.ballester@eficent.com>
* Serpent Consulting Services Pvt. Ltd. <support@serpentcs.com>

Maintainer
----------

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

To contribute to this module, please visit https://odoo-community.org.
8 changes: 8 additions & 0 deletions hr_expense_operating_unit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016-17 Serpent Consulting Services Pvt. Ltd.
# (<http://www.serpentcs.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).
from . import models
from . import tests
23 changes: 23 additions & 0 deletions hr_expense_operating_unit/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016-17 Serpent Consulting Services Pvt. Ltd.
# (<http://www.serpentcs.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

{
"name": "HR Expense Operating Unit",
"version": "10.0.1.0.0",
"license": 'LGPL-3',
"author": "Eficent Business and IT Consulting Services S.L., "
"Serpent Consulting Services Pvt. Ltd.,"
"Odoo Community Association (OCA)",
"website": "http://www.eficent.com",
"category": "Generic Modules/Human Resources",
"depends": ["hr_expense", "account_operating_unit"],
"data": [
"views/hr_expense_view.xml",
"security/hr_expense_security.xml"
],
'installable': True,
}
8 changes: 8 additions & 0 deletions hr_expense_operating_unit/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016-17 Serpent Consulting Services Pvt. Ltd.
# (<http://www.serpentcs.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from . import hr_expense
80 changes: 80 additions & 0 deletions hr_expense_operating_unit/models/hr_expense.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016-17 Serpent Consulting Services Pvt. Ltd.
# (<http://www.serpentcs.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from odoo.tools.translate import _
from odoo import api, fields, models
from odoo.exceptions import UserError


class HrExpenseExpense(models.Model):

_inherit = 'hr.expense'

operating_unit_id = fields.Many2one('operating.unit', 'Operating Unit',
default=lambda self:
self.env['res.users'].
operating_unit_default_get(self._uid))

@api.multi
@api.constrains('operating_unit_id', 'company_id')
def _check_company_operating_unit(self):
for rec in self:
if (rec.company_id and rec.operating_unit_id and rec.company_id !=
rec.operating_unit_id.company_id):
raise UserError(_('Configuration error!\nThe Company in the '
'Expense and in the Operating Unit '
'must be the same.'))

@api.multi
@api.constrains('operating_unit_id', 'sheet_id')
def _check_expense_operating_unit(self):
for rec in self:
if (rec.sheet_id and rec.sheet_id.operating_unit_id and
rec.operating_unit_id and rec.sheet_id.operating_unit_id !=
rec.operating_unit_id):
raise UserError(_('Configuration error!\nThe Operating Unit in'
' the Expense sheet and in the Expense must '
'be the same.'))

@api.multi
def submit_expenses(self):
res = super(HrExpenseExpense, self).submit_expenses()
if len(self.mapped('operating_unit_id')) != 1 or\
any(not expense.operating_unit_id for expense in self):
raise UserError(_('You cannot submit the Expenses having'
' different Operating Units or with'
' no Operating Unit!'))
if res.get('context'):
res.get('context').\
update({'default_operating_unit_id':
self[0].operating_unit_id.id})
return res

def _prepare_move_line(self, line):
res = super(HrExpenseExpense, self)._prepare_move_line(line)
res.update({'operating_unit_id': self.operating_unit_id.id})
return res


class HrExpenseSheet(models.Model):

_inherit = "hr.expense.sheet"

operating_unit_id = fields.Many2one('operating.unit', 'Operating Unit',
default=lambda self:
self.env['res.users'].
operating_unit_default_get(self._uid))

@api.multi
@api.constrains('operating_unit_id', 'company_id')
def _check_company_operating_unit(self):
for rec in self:
if (rec.company_id and rec.operating_unit_id and rec.company_id !=
rec.operating_unit_id.company_id):
raise UserError(_('Configuration error!\nThe Company in the '
'Expense sheet and in the Operating Unit '
'must be the same.'))
31 changes: 31 additions & 0 deletions hr_expense_operating_unit/security/hr_expense_security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
Serpent Consulting Services Pvt. Ltd.
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl-3.0) -->
<odoo>

<record id="ir_rule_hr_expense_allowed_operating_units"
model="ir.rule">
<field name="model_id" ref="hr_expense.model_hr_expense"/>
<field name="domain_force">['|',('operating_unit_id','=',False),('operating_unit_id','in',[g.id for g in user.operating_unit_ids])]</field>
<field name="name">Expenses from allowed operating units</field>
<field name="global" eval="True"/>
<field eval="0" name="perm_unlink"/>
<field eval="0" name="perm_write"/>
<field eval="1" name="perm_read"/>
<field eval="0" name="perm_create"/>
</record>

<record id="ir_rule_hr_expense_sheet_allowed_operating_units"
model="ir.rule">
<field name="model_id" ref="hr_expense.model_hr_expense_sheet"/>
<field name="domain_force">['|',('operating_unit_id','=',False),('operating_unit_id','in',[g.id for g in user.operating_unit_ids])]</field>
<field name="name">Expenses from allowed operating units</field>
<field name="global" eval="True"/>
<field eval="0" name="perm_unlink"/>
<field eval="0" name="perm_write"/>
<field eval="1" name="perm_read"/>
<field eval="0" name="perm_create"/>
</record>

</odoo>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions hr_expense_operating_unit/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016-17 Serpent Consulting Services Pvt. Ltd.
# (<http://www.serpentcs.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from . import test_hr_expense_operating_unit
154 changes: 154 additions & 0 deletions hr_expense_operating_unit/tests/test_hr_expense_operating_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
# Copyright 2016-17 Eficent Business and IT Consulting Services S.L.
# (http://www.eficent.com)
# Copyright 2016-17 Serpent Consulting Services Pvt. Ltd.
# (<http://www.serpentcs.com>)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from odoo.tests import common
from odoo.exceptions import ValidationError, UserError


class TestHrExpenseOperatingUnit(common.TransactionCase):

def setUp(self):
super(TestHrExpenseOperatingUnit, self).setUp()
self.res_users_model = self.env['res.users']
self.hr_expense_model = self.env['hr.expense']
self.hr_expense_sheet_model = self.env['hr.expense.sheet']
self.hr_employee_model = self.env['hr.employee']

self.company = self.env.ref('base.main_company')
self.partner1 = self.env.ref('base.res_partner_1')
self.partner2 = self.env.ref('base.res_partner_2')

# Expense Product
self.product1 = self.env.ref('hr_expense.air_ticket')

self.grp_hr_user = self.env.ref('hr.group_hr_user')
self.grp_accou_mng = self.env.ref('account.group_account_manager')
self.grp_account_invoice =\
self.env.ref('account.group_account_invoice')

# Main Operating Unit
self.ou1 = self.env.ref('operating_unit.main_operating_unit')
# B2C Operating Unit
self.b2c = self.env.ref('operating_unit.b2c_operating_unit')

self.user1 = self._create_user('Test HR User 1', 'user_1', 'demo1',
[self.grp_hr_user, self.grp_accou_mng,
self.grp_account_invoice],
self.company,
[self.ou1, self.b2c])
self.user2 = self._create_user('Test HR User 2', 'user_2', 'demo2',
[self.grp_hr_user, self.grp_accou_mng,
self.grp_account_invoice],
self.company,
[self.b2c])

self.emp = self._create_hr_employee()

self.hr_expense1 = self._create_hr_expense(
self.ou1, self.emp)

self.hr_expense2 = self._create_hr_expense(
self.b2c, self.emp)

self._post_journal_entries(self.hr_expense1)
self._post_journal_entries(self.hr_expense2)

def _create_user(self, name, login, pwd, groups, company, operating_units,
context=None):
"""Creates a user."""
group_ids = [group.id for group in groups]
user = self.res_users_model.create({
'name': name,
'login': login,
'password': pwd,
'email': 'example@yourcompany.com',
'company_id': company.id,
'company_ids': [(4, company.id)],
'operating_unit_ids': [(4, ou.id) for ou in operating_units],
'groups_id': [(6, 0, group_ids)]
})
return user

def _create_hr_employee(self):
"""Creates an Employee."""
emp = self.hr_employee_model.create({
'name': "Test Employee",
'address_home_id': self.partner1.id,
})
return emp

def _create_hr_expense(self, operating_unit, emp):
"""Creates Expense for employee."""
expense_sheet = self.hr_expense_sheet_model.create({
'name': "Traveling Expense",
'employee_id': emp.id,
'operating_unit_id': operating_unit.id
})
self.hr_expense_model.create({
'name': "Traveling Expense",
'product_id': self.product1.id,
'operating_unit_id': operating_unit.id,
'unit_amount': '10.0',
'quantity': '5',
'employee_id': emp.id,
'sheet_id': expense_sheet.id
})
return expense_sheet

def _post_journal_entries(self, expense_sheet):
"""Approves the Expense and creates accounting entries."""
expense_sheet.approve_expense_sheets()
expense_sheet.action_sheet_move_create()

def test_security(self):
# User 2 is only assigned to Operating Unit B2C, and cannot
# Access Expenses of Main Operating Unit.
record = self.hr_expense_model.sudo(self.user2.id).search(
[('id', '=', self.hr_expense1.id),
('operating_unit_id', '=', self.ou1.id)])
self.assertEqual(record.ids, [], 'User 2 should not have access to %s'
% self.ou1.name)

# Expense OU should have same OU of its accounting entries
self.assertEqual(self.hr_expense1.expense_line_ids.operating_unit_id,
self.hr_expense1.account_move_id.line_ids.
mapped('operating_unit_id'),
"Expense OU should match with accounting entries OU")
self.assertEqual(self.hr_expense2.expense_line_ids.operating_unit_id,
self.hr_expense2.account_move_id.line_ids.
mapped('operating_unit_id'),
"Expense OU should match with accounting entries OU")

def test_constrains_error(self):
with self.assertRaises(ValidationError):
self.hr_expense1.write({'operating_unit_id': self.ou1.id})
self.hr_expense1.expense_line_ids.write({'operating_unit_id':
self.b2c.id})

self.hr_expense1.expense_line_ids.write({'state': 'draft'})
self.hr_expense1.expense_line_ids.submit_expenses()

company_id = self.env['res.company'].create({
'name': 'My Company',
'partner_id': self.partner1.id,
'rml_header1': 'My Company',
'currency_id': self.env.ref('base.EUR').id
})

with self.assertRaises(ValidationError):
self.hr_expense1.expense_line_ids.write({'company_id':
company_id.id})

with self.assertRaises(UserError):
self.hr_expense1.expense_line_ids.write({
'state': 'draft',
'operating_unit_id': False
})
self.hr_expense1.expense_line_ids.submit_expenses()

with self.assertRaises(ValidationError):
self.hr_expense1.write({'company_id': company_id.id})

0 comments on commit f313b92

Please sign in to comment.