Merge branch 'dev_odex_hr' of https://github.com/expsa/odex_30 into dev_odex_hr
|
|
@ -244,6 +244,7 @@ class employee_overtime_request(models.Model):
|
|||
'line_ids': [(0, 0, debit_line_vals), (0, 0, credit_line_vals)],
|
||||
'res_model': 'employee.overtime.request',
|
||||
'res_id': self.id
|
||||
|
||||
})
|
||||
record.account_id = account_debit_id.id
|
||||
record.journal_id = journal_id.id
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class HrPersonalPermission(models.Model):
|
|||
cal_hour_start = calendar.full_min_sign_in
|
||||
cal_hour_end = calendar.full_max_sign_out
|
||||
if hour_start > cal_hour_end or hour_end < cal_hour_start:
|
||||
raise exceptions.ValidationError(_('Sorry, Permission Must Be within The Attendance Hours'))
|
||||
raise ValidationError(_('Sorry, Permission Must Be within The Attendance Hours'))
|
||||
|
||||
|
||||
|
||||
|
|
@ -345,7 +345,7 @@ class HrPersonalPermission(models.Model):
|
|||
if item.duration <= 0.0:
|
||||
raise UserError(_('This Duration Must Be Greater Than Zero'))
|
||||
|
||||
if item.duration < item.balance:
|
||||
if item.duration > item.balance:
|
||||
raise UserError(_('This Duration must be less than or equal to the Permission Limit'))
|
||||
|
||||
if item.duration > item.permission_number:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
from . import test_overtime_process
|
||||
from . import test_employee_department_jobs
|
||||
from . import test_hr_clearance_form
|
||||
from . import test_hr_personal_permission
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import fields
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class TestEmployeeDepartmentJobs(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestEmployeeDepartmentJobs, self).setUp()
|
||||
|
||||
self.manager_employee = self.env['hr.employee'].create({
|
||||
'name': 'Big Manager',
|
||||
|
||||
})
|
||||
|
||||
self.dep1 = self.env['hr.department'].create({
|
||||
'name': 'IT Department',
|
||||
'manager_id': self.manager_employee.id,
|
||||
})
|
||||
|
||||
self.dep2 = self.env['hr.department'].create({
|
||||
'name': 'HR Department',
|
||||
'manager_id': self.manager_employee.id,
|
||||
})
|
||||
|
||||
self.job1 = self.env['hr.job'].create({'name': 'Developer', 'department_id': self.dep1.id})
|
||||
self.job2 = self.env['hr.job'].create(
|
||||
{'name': 'HR Officer', 'department_id': self.dep2.id, 'no_of_recruitment': 1})
|
||||
self.job_full = self.env['hr.job'].create(
|
||||
{'name': 'Manager', 'department_id': self.dep2.id, 'no_of_recruitment': 0})
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee Job',
|
||||
'department_id': self.dep1.id,
|
||||
'job_id': self.job1.id,
|
||||
'parent_id': self.manager_employee.id, # ربطه بالمدير
|
||||
'first_hiring_date': fields.Date.today() - relativedelta(years=2),
|
||||
'joining_date': fields.Date.today() - relativedelta(years=1),
|
||||
})
|
||||
|
||||
def test_prevent_job_change_no_vacancy(self):
|
||||
|
||||
req = self.env['employee.department.jobs'].new({
|
||||
'employee_id': self.employee.id,
|
||||
'new_department_id': self.dep2.id,
|
||||
'new_job_id': self.job_full.id, # 0 vacancies
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError, msg="Should raise error when no recruitment spots available"):
|
||||
req.not_reused_same_dep_job()
|
||||
|
||||
def test_update_employee_data_on_approval(self):
|
||||
request = self.env['employee.department.jobs'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'promotion_type': 'both',
|
||||
'new_department_id': self.dep2.id,
|
||||
'new_job_id': self.job2.id,
|
||||
'date': fields.Date.today(),
|
||||
'state': 'hr_manager',
|
||||
})
|
||||
|
||||
request.store_level_group_and_degree_values()
|
||||
|
||||
request.approved()
|
||||
|
||||
self.assertEqual(self.employee.department_id.id, self.dep2.id, "Department not updated")
|
||||
self.assertEqual(self.employee.job_id.id, self.job2.id, "Job not updated")
|
||||
self.assertEqual(self.employee.joining_date, fields.Date.today(), "Joining date not updated")
|
||||
|
||||
def test_service_duration_calculation(self):
|
||||
today = fields.Date.today()
|
||||
one_year_one_month_ago = today - relativedelta(years=1, months=1)
|
||||
|
||||
self.employee.joining_date = one_year_one_month_ago
|
||||
|
||||
request = self.env['employee.department.jobs'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'date': today,
|
||||
})
|
||||
|
||||
request.store_level_group_and_degree_values()
|
||||
request._compute_duration()
|
||||
|
||||
self.assertEqual(request.service_year, 1, "Service year calculation wrong")
|
||||
self.assertEqual(request.service_month, 1, "Service month calculation wrong")
|
||||
self.assertEqual(request.service_day, 0, "Service day calculation wrong")
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from unittest.mock import patch
|
||||
import base64
|
||||
|
||||
|
||||
class TestHrClearanceForm(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHrClearanceForm, self).setUp()
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee Clearance',
|
||||
'first_hiring_date': '2020-01-01',
|
||||
})
|
||||
self.clearance = self.env['hr.clearance.form'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'clearance_type': 'final',
|
||||
})
|
||||
|
||||
def test_bank_attachment_constraint(self):
|
||||
|
||||
|
||||
self.clearance.state = 'admin_manager'
|
||||
|
||||
with self.assertRaises(UserError, msg="Should require attachment for Final Clearance"):
|
||||
self.clearance.wait()
|
||||
|
||||
|
||||
file_content = b'This is a test file content'
|
||||
encoded_content = base64.b64encode(file_content)
|
||||
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': 'Bank Clearance.pdf',
|
||||
'datas': encoded_content,
|
||||
'res_model': 'hr.clearance.form',
|
||||
'res_id': self.clearance.id,
|
||||
})
|
||||
|
||||
self.clearance.bank_attachment_id = [(6, 0, [attachment.id])]
|
||||
|
||||
self.clearance.wait()
|
||||
self.assertEqual(self.clearance.state, 'wait', "State should move to wait after attachment")
|
||||
|
||||
def test_custody_check_blocking(self):
|
||||
|
||||
with patch('odoo.models.Model.search') as mock_search:
|
||||
def side_effect(domain, **kwargs):
|
||||
if domain == [('state', '=', 'installed'), ('name', '=', 'exp_employee_custody')]:
|
||||
return [1]
|
||||
|
||||
if len(domain) > 0 and domain[0][0] == 'employee_id' and domain[0][2] == self.employee.id:
|
||||
return [1]
|
||||
|
||||
return []
|
||||
|
||||
mock_search.side_effect = side_effect
|
||||
|
||||
if 'custom.employee.custody' in self.env:
|
||||
with self.assertRaises(UserError, msg="Should block clearance if custody exists"):
|
||||
self.clearance.check_custody()
|
||||
else:
|
||||
pass
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo import fields
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class TestHrPersonalPermission(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHrPersonalPermission, self).setUp()
|
||||
|
||||
self.permission_type = self.env['hr.personal.permission.type'].create({
|
||||
'name': 'Medical Permission',
|
||||
'daily_hours': 4.0,
|
||||
'monthly_hours': 10.0,
|
||||
'approval_by': 'direct_manager',
|
||||
})
|
||||
|
||||
self.calendar = self.env['resource.calendar'].create({
|
||||
'name': 'Standard 8 Hours',
|
||||
'is_full_day': True,
|
||||
'hours_per_day': 8.0,
|
||||
'full_min_sign_in': 7.0,
|
||||
'full_max_sign_in': 9.0,
|
||||
'full_min_sign_out': 15.0,
|
||||
'full_max_sign_out': 17.0,
|
||||
})
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Ahmed Test Employee',
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
'first_hiring_date': fields.Date.today() - timedelta(days=365),
|
||||
})
|
||||
|
||||
def test_01_duration_calculation(self):
|
||||
start_time = datetime.now().replace(hour=9, minute=0, second=0, microsecond=0)
|
||||
end_time = start_time + timedelta(hours=1.5)
|
||||
|
||||
permission = self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': start_time,
|
||||
'date_to': end_time,
|
||||
'permission_number': 10.0,
|
||||
})
|
||||
|
||||
self.assertEqual(permission.duration, 1.5, "Duration calculation is wrong")
|
||||
|
||||
def test_02_daily_limit_constraint(self):
|
||||
|
||||
self.permission_type.daily_hours = 2.0
|
||||
|
||||
start_time = datetime.now().replace(hour=9, minute=0, second=0, microsecond=0)
|
||||
end_time = start_time + timedelta(hours=3)
|
||||
|
||||
with self.assertRaises(UserError, msg="Should prevent permission > daily limit"):
|
||||
self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': start_time,
|
||||
'date_to': end_time,
|
||||
'permission_number': 10.0,
|
||||
})
|
||||
|
||||
def test_03_monthly_limit_constraint(self):
|
||||
self.permission_type.monthly_hours = 4.0
|
||||
|
||||
base_date = datetime.now().replace(day=1, hour=9, minute=0, second=0, microsecond=0)
|
||||
|
||||
self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': base_date,
|
||||
'date_to': base_date + timedelta(hours=2),
|
||||
'state': 'approve',
|
||||
'permission_number': 4.0,
|
||||
})
|
||||
|
||||
base_date_2 = base_date + timedelta(days=1)
|
||||
self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': base_date_2,
|
||||
'date_to': base_date_2 + timedelta(hours=2),
|
||||
'state': 'approve',
|
||||
'permission_number': 2.0,
|
||||
})
|
||||
|
||||
base_date_3 = base_date + timedelta(days=2)
|
||||
|
||||
with self.assertRaises(UserError, msg="Should prevent permission > monthly limit"):
|
||||
self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': base_date_3,
|
||||
'date_to': base_date_3 + timedelta(hours=1),
|
||||
'permission_number': 0.0, # الرصيد انتهى
|
||||
})
|
||||
|
||||
def test_04_check_attendance_hours(self):
|
||||
|
||||
start_time = datetime.now().replace(hour=18, minute=0, second=0, microsecond=0)
|
||||
end_time = start_time + timedelta(hours=1)
|
||||
|
||||
with self.assertRaises(ValidationError, msg="Should prevent permission outside attendance hours"):
|
||||
self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': start_time,
|
||||
'date_to': end_time,
|
||||
'permission_number': 10.0,
|
||||
})
|
||||
|
||||
def test_05_overlap_with_holiday(self):
|
||||
if 'hr.holidays' not in self.env:
|
||||
return
|
||||
|
||||
today = fields.Date.today()
|
||||
holiday_status = self.env['hr.holidays.status'].search([], limit=1)
|
||||
if not holiday_status:
|
||||
holiday_status = self.env['hr.holidays.status'].create({'name': 'Test Leave'})
|
||||
|
||||
self.env['hr.holidays'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': holiday_status.id,
|
||||
'date_from': today,
|
||||
'date_to': today,
|
||||
'type': 'remove',
|
||||
'state': 'validate',
|
||||
})
|
||||
|
||||
start_time = datetime.now().replace(hour=10, minute=0, second=0, microsecond=0)
|
||||
|
||||
with self.assertRaises(UserError, msg="Should prevent permission during approved holiday"):
|
||||
perm = self.env['hr.personal.permission'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'permission_type_id': self.permission_type.id,
|
||||
'date_from': start_time,
|
||||
'date_to': start_time + timedelta(hours=1),
|
||||
'permission_number': 10.0,
|
||||
})
|
||||
perm.check_holiday_mission()
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import fields
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
class TestOvertimeProcess(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOvertimeProcess, self).setUp()
|
||||
|
||||
self.account_type_expenses = self.env['account.account'].search([('account_type', '=', 'expense')], limit=1)
|
||||
if not self.account_type_expenses:
|
||||
user_type_expense = self.env.ref('account.data_account_type_expenses')
|
||||
self.account_type_expenses = self.env['account.account'].create({
|
||||
'name': 'Overtime Expense',
|
||||
'code': 'X6000',
|
||||
'account_type': 'expense',
|
||||
'reconcile': True,
|
||||
})
|
||||
|
||||
self.overtime_account = self.account_type_expenses
|
||||
self.journal_account = self.env['account.account'].search([('account_type', '=', 'asset_cash')], limit=1)
|
||||
|
||||
self.journal = self.env['account.journal'].create({
|
||||
'name': 'Overtime Journal',
|
||||
'type': 'cash',
|
||||
'code': 'OTJ',
|
||||
'default_account_id': self.journal_account.id,
|
||||
})
|
||||
|
||||
self.working_hours = self.env['resource.calendar'].create({
|
||||
'name': 'Standard 40 Hours',
|
||||
'hours_per_day': 8.0,
|
||||
# 'max_overtime_hour': 10.0,
|
||||
# 'overtime_factor_daily': 1.5,
|
||||
# 'overtime_factor_holiday': 2.0,
|
||||
# 'work_days': 20,
|
||||
# 'work_hour': 8,
|
||||
# 'journal_overtime_id': self.journal.id,
|
||||
# 'account_overtime_id': self.overtime_account.id,
|
||||
})
|
||||
|
||||
self.working_hours.write({
|
||||
'max_overtime_hour': 20.0,
|
||||
'overtime_factor_daily': 1.5,
|
||||
'overtime_factor_holiday': 2.0,
|
||||
'work_days': 30,
|
||||
'work_hour': 8,
|
||||
'journal_overtime_id': self.journal.id,
|
||||
'account_overtime_id': self.overtime_account.id,
|
||||
})
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Ahmed Tester',
|
||||
'state': 'open',
|
||||
'first_hiring_date': date.today() - timedelta(days=365), # موظف منذ سنة
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Contract for Ahmed',
|
||||
'employee_id': self.employee.id,
|
||||
'state': 'open',
|
||||
'wage': 5000, # الراتب الأساسي
|
||||
'resource_calendar_id': self.working_hours.id,
|
||||
# 'total_allowance': 1000,
|
||||
# 'salary': 5000,
|
||||
})
|
||||
self.contract.write({
|
||||
'total_allowance': 1000,
|
||||
'salary': 5000,
|
||||
})
|
||||
|
||||
def test_overtime_period_constraint_cross_month(self):
|
||||
|
||||
# Arrange
|
||||
date_from = date(2025, 1, 31)
|
||||
date_to = date(2025, 2, 1) # شهر مختلف
|
||||
|
||||
# Act & Assert
|
||||
with self.assertRaises(UserError):
|
||||
request = self.env['employee.overtime.request'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'request_date': date_from,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'line_ids_over_time': [(0, 0, {
|
||||
'employee_id': self.employee.id,
|
||||
'over_time_workdays_hours': 5.0,
|
||||
})]
|
||||
})
|
||||
|
||||
request.line_ids_over_time.get_max_remain_hours()
|
||||
|
||||
def test_max_hours_constraint_and_exception(self):
|
||||
|
||||
# Arrange
|
||||
self.working_hours.write({'max_overtime_hour': 10.0})
|
||||
|
||||
date_from = date(2025, 3, 1)
|
||||
date_to = date(2025, 3, 5)
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
self.env['employee.overtime.request'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'exception': False,
|
||||
'line_ids_over_time': [(0, 0, {
|
||||
'employee_id': self.employee.id,
|
||||
'over_time_workdays_hours': 15.0,
|
||||
})]
|
||||
})
|
||||
|
||||
request = self.env['employee.overtime.request'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'exception': True,
|
||||
'line_ids_over_time': [(0, 0, {
|
||||
'employee_id': self.employee.id,
|
||||
'over_time_workdays_hours': 15.0,
|
||||
})]
|
||||
})
|
||||
|
||||
# Assert
|
||||
self.assertTrue(request.line_ids_over_time.exception, "Exception flag should be propagated to lines")
|
||||
|
||||
def test_calculate_daily_rate(self):
|
||||
|
||||
expected_hourly_rate = 8500.0 / 240.0
|
||||
|
||||
# Act
|
||||
request = self.env['employee.overtime.request'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date(2025, 4, 1),
|
||||
'date_to': date(2025, 4, 1),
|
||||
'line_ids_over_time': [(0, 0, {
|
||||
'employee_id': self.employee.id,
|
||||
'over_time_workdays_hours': 2.0,
|
||||
})]
|
||||
})
|
||||
|
||||
line = request.line_ids_over_time[0]
|
||||
|
||||
# Assert
|
||||
self.assertAlmostEqual(line.daily_hourly_rate, expected_hourly_rate, places=2,
|
||||
msg="Daily hourly rate calculation is incorrect")
|
||||
|
||||
expected_total_price = expected_hourly_rate * 2.0
|
||||
self.assertAlmostEqual(line.price_hour, expected_total_price, places=2,
|
||||
msg="Total price calculation is incorrect")
|
||||
|
||||
def test_accounting_transfer_validation(self):
|
||||
|
||||
# Arrange
|
||||
request = self.env['employee.overtime.request'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date(2025, 5, 1),
|
||||
'date_to': date(2025, 5, 2),
|
||||
'transfer_type': 'accounting',
|
||||
'line_ids_over_time': [(0, 0, {
|
||||
'employee_id': self.employee.id,
|
||||
'over_time_workdays_hours': 10.0,
|
||||
})]
|
||||
})
|
||||
|
||||
request.state = 'executive_office'
|
||||
|
||||
# Act
|
||||
request.validated()
|
||||
|
||||
# Assert
|
||||
self.assertEqual(request.state, 'validated', "State should be validated")
|
||||
line = request.line_ids_over_time[0]
|
||||
|
||||
self.assertTrue(line.move_id, "Account Move should be created")
|
||||
self.assertEqual(line.move_id.state, 'draft', "Move should be in draft state initially")
|
||||
|
||||
debit_line = line.move_id.line_ids.filtered(lambda l: l.debit > 0)
|
||||
credit_line = line.move_id.line_ids.filtered(lambda l: l.credit > 0)
|
||||
|
||||
self.assertTrue(debit_line and credit_line, "Should have debit and credit lines")
|
||||
self.assertEqual(debit_line.account_id, self.overtime_account, "Debit account mismatch")
|
||||
self.assertEqual(credit_line.account_id, self.journal.default_account_id, "Credit account mismatch")
|
||||
self.assertAlmostEqual(debit_line.debit, line.price_hour, places=2, msg="Debit amount mismatch")
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import test_custody_receiving
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests import tagged
|
||||
from datetime import date
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestCustodyReceiving(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCustodyReceiving, self).setUp()
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee for Custody',
|
||||
'emp_no': 'EMP001',
|
||||
})
|
||||
|
||||
self.deduction_category = self.env['hr.salary.rule.category'].create({
|
||||
'name': 'Deduction',
|
||||
'code': 'DED',
|
||||
'rule_type': 'deduction',
|
||||
})
|
||||
|
||||
self.salary_rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Custody Deduction Rule',
|
||||
'sequence': 1,
|
||||
'code': 'CUST_DED',
|
||||
'category_id': self.deduction_category.id,
|
||||
'condition_select': 'none',
|
||||
'amount_select': 'fix',
|
||||
'amount_fix': 0.0,
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Contract Test',
|
||||
'employee_id': self.employee.id,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
self.custody_delivered = self.env['custom.employee.custody'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'state': 'approve',
|
||||
})
|
||||
|
||||
self.custody_line = self.env['employee.custody.line'].create({
|
||||
'employee_custody_line': self.custody_delivered.id,
|
||||
'name': 'Laptop Dell',
|
||||
'serial': 'SN123456',
|
||||
'quantity': 1.0,
|
||||
'receiving_quantity': 0.0,
|
||||
'amount': 0.0,
|
||||
})
|
||||
|
||||
def test_01_custody_receiving_workflow_full_return(self):
|
||||
|
||||
receiving = self.env['hr.custody.receiving'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'note': 'Returning Laptop',
|
||||
})
|
||||
|
||||
|
||||
receiving._get_custody_line_domain()
|
||||
|
||||
self.assertTrue(receiving.return_custody_line_ids, "Should fetch pending custody lines")
|
||||
line = receiving.return_custody_line_ids[0]
|
||||
self.assertEqual(line.custody_line_id, self.custody_line, "Should link to correct original line")
|
||||
self.assertEqual(line.quantity, 1.0, "Should default to remaining quantity")
|
||||
|
||||
receiving.send()
|
||||
self.assertEqual(receiving.state, 'submit')
|
||||
|
||||
receiving.dr_manager()
|
||||
self.assertEqual(receiving.state, 'direct')
|
||||
|
||||
receiving.dr_hr_manager()
|
||||
self.assertEqual(receiving.state, 'admin')
|
||||
|
||||
receiving.warehouse_keeper()
|
||||
self.assertEqual(receiving.state, 'approve')
|
||||
|
||||
receiving.done()
|
||||
self.assertEqual(receiving.state, 'done')
|
||||
|
||||
|
||||
self.assertEqual(self.custody_line.receiving_quantity, 1.0, "Original line receiving qty should be updated")
|
||||
|
||||
self.assertEqual(self.custody_delivered.state, 'done', "Original custody should be done as all items returned")
|
||||
|
||||
def test_02_custody_receiving_with_deduction(self):
|
||||
|
||||
receiving = self.env['hr.custody.receiving'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'salary_rule_id': self.salary_rule.id,
|
||||
})
|
||||
receiving._get_custody_line_domain()
|
||||
|
||||
line = receiving.return_custody_line_ids[0]
|
||||
line.deduction_amount = 500.0
|
||||
|
||||
receiving.compute_deduction_amount()
|
||||
self.assertEqual(receiving.deduction_amount, 500.0)
|
||||
self.assertTrue(receiving.salary_rule_flag)
|
||||
|
||||
receiving.state = 'approve'
|
||||
receiving.done()
|
||||
|
||||
self.assertTrue(receiving.advantage_line_id, "Should create advantage line")
|
||||
self.assertEqual(receiving.advantage_line_id.amount, 500.0, "Advantage amount mismatch")
|
||||
self.assertEqual(receiving.advantage_line_id.contract_advantage_id, self.contract)
|
||||
|
||||
def test_03_validation_over_quantity(self):
|
||||
|
||||
receiving = self.env['hr.custody.receiving'].create({
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
receiving._get_custody_line_domain()
|
||||
|
||||
line = receiving.return_custody_line_ids[0]
|
||||
line.quantity = 2.0
|
||||
|
||||
receiving.state = 'approve'
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
receiving.done()
|
||||
|
||||
def test_04_reset_to_draft(self):
|
||||
|
||||
receiving = self.env['hr.custody.receiving'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'salary_rule_id': self.salary_rule.id,
|
||||
})
|
||||
receiving._get_custody_line_domain()
|
||||
|
||||
receiving.return_custody_line_ids[0].deduction_amount = 100.0
|
||||
receiving.compute_deduction_amount()
|
||||
receiving.state = 'approve'
|
||||
receiving.done()
|
||||
|
||||
self.assertEqual(receiving.state, 'done')
|
||||
self.assertTrue(receiving.advantage_line_id)
|
||||
|
||||
receiving.set_to_draft()
|
||||
|
||||
self.assertEqual(receiving.state, 'draft')
|
||||
self.assertFalse(receiving.advantage_line_id.exists(), "Advantage line should be deleted")
|
||||
self.assertEqual(self.custody_line.receiving_quantity, 0.0, "Original qty should be reverted")
|
||||
self.assertEqual(self.custody_delivered.state, 'approve')
|
||||
|
||||
def test_05_unlink_protection(self):
|
||||
|
||||
receiving = self.env['hr.custody.receiving'].create({
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
receiving.send()
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
receiving.unlink()
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
###################################################################################
|
||||
|
||||
{
|
||||
'name': 'Appraisal KPI',
|
||||
'version': '18.0.1.0.0',
|
||||
'category': 'HR-Odex',
|
||||
'summary': 'Manage Appraisal KPI',
|
||||
'description': """
|
||||
Helps you to manage Appraisal of your company's staff.
|
||||
""",
|
||||
'author': 'Expert Co. Ltd.',
|
||||
'company': 'Exp-co-ltd',
|
||||
'maintainer': 'Cybrosys Techno Solutions',
|
||||
'website': 'http://exp-sa.com',
|
||||
'depends': [
|
||||
|
||||
'exp_hr_appraisal', 'base','kpi_scorecard', 'hr','kpi_scorecard', 'account', 'exp_hr_payroll', 'mail', 'hr_base', 'hr_contract', 'hr_contract_custom'
|
||||
|
||||
],
|
||||
'data': [
|
||||
'security/group.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/kpi_category.xml',
|
||||
'views/kpi_item.xml',
|
||||
'views/kpi_period.xml',
|
||||
'views/kpi_skills.xml',
|
||||
'views/skill_appraisal.xml',
|
||||
'views/years_employee_goals.xml',
|
||||
'views/employee_performance_evaluation.xml',
|
||||
'views/appraisal_percentage.xml',
|
||||
'views/employee_apprisal.xml',
|
||||
|
||||
|
||||
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from . import kpi_item
|
||||
from . import kpi_period
|
||||
from . import kpi_skill
|
||||
from . import skill_apprisal
|
||||
from . import years_employee_goals
|
||||
from . import employee_performance_evaluation
|
||||
from . import appraisal_percentage
|
||||
from . import employee_apprisal
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
from odoo import fields, models, api,_
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class AppraisalPercentage(models.Model):
|
||||
_name = 'job.class.apprisal'
|
||||
_description = 'Appraisal Percentage'
|
||||
name = fields.Char(string='Name')
|
||||
percentage_kpi = fields.Float(string="Percentage of indicator Appraisal%",)
|
||||
percentage_skills = fields.Float(string="Percentage of Skills Appraisal%",)
|
||||
job_ids = fields.Many2many(
|
||||
comodel_name='hr.job',
|
||||
string='Jobs')
|
||||
|
||||
# Constraint to ensure total percentage is 100
|
||||
@api.constrains('percentage_kpi', 'percentage_skills')
|
||||
def _check_percentage_total(self):
|
||||
for record in self:
|
||||
total_percentage = record.percentage_kpi + record.percentage_skills
|
||||
if total_percentage != 1:
|
||||
raise ValidationError(_("Total percentage should be 100."))
|
||||
if self.job_ids:
|
||||
for rec in self.job_ids:
|
||||
rec.appraisal_percentages_id = self.id
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
from odoo import models, fields,_,api,exceptions
|
||||
|
||||
class EmployeeApprisal(models.Model):
|
||||
_inherit = 'hr.group.employee.appraisal'
|
||||
year_id = fields.Many2one(comodel_name='kpi.period',string='Year',required=True)
|
||||
appraisal_ids = fields.One2many('hr.employee.appraisal', 'employee_appraisal2')
|
||||
|
||||
def gen_appraisal(self):
|
||||
for item in self:
|
||||
if item.employee_ids:
|
||||
appraisal_lines_list = []
|
||||
# Fill employee appraisal
|
||||
for element in item.employee_ids:
|
||||
standard_appraisal_list, manager_appraisal_list = [], []
|
||||
year_goal_obj = self.env['years.employee.goals'].search([('employee_id','=',element.id),('year_id','=',self.year_id.id)])
|
||||
print('year = ',year_goal_obj)
|
||||
goal_ids = year_goal_obj.ids if year_goal_obj else []
|
||||
appraisal_line = {
|
||||
'employee_id': element.id,
|
||||
'manager_id': item.manager_id.id,
|
||||
'year_id': item.year_id.id,
|
||||
'department_id': item.department_id.id,
|
||||
'job_id': element.job_id.id,
|
||||
'appraisal_date': item.date,
|
||||
'goal_ids': [(6, 0, goal_ids)],
|
||||
}
|
||||
line_id = self.env['hr.employee.appraisal'].create(appraisal_line)
|
||||
line_id.compute_apprisal()
|
||||
appraisal_lines_list.append(line_id.id)
|
||||
|
||||
item.appraisal_ids = self.env['hr.employee.appraisal'].browse(appraisal_lines_list)
|
||||
|
||||
else:
|
||||
raise exceptions.Warning(_('Please select at least one employee to make appraisal.'))
|
||||
item.state = 'gen_appraisal'
|
||||
def draft(self):
|
||||
print('draft ..............')
|
||||
# Delete all appraisals when re-draft
|
||||
if self.appraisal_ids:
|
||||
print('if appr line.............')
|
||||
for line in self.appraisal_ids:
|
||||
print('for..................')
|
||||
if line.state == 'draft':
|
||||
print('state...........')
|
||||
line.unlink()
|
||||
self.state = 'draft'
|
||||
|
||||
elif line.state == 'closed':
|
||||
line.state = 'state_done'
|
||||
self.state = 'start_appraisal'
|
||||
|
||||
elif line.state == 'state_done':
|
||||
self.state = 'start_appraisal'
|
||||
# Call the original draft method using super()
|
||||
|
||||
class EmployeeApprisal(models.Model):
|
||||
_inherit = 'hr.employee.appraisal'
|
||||
|
||||
employee_appraisal2 = fields.Many2one('hr.group.employee.appraisal') # Inverse field
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee',tracking=True,required=True)
|
||||
manager_id = fields.Many2one('hr.employee', string='Manager',readonly=False,tracking=True,required=True,default=lambda item: item.get_user_id())
|
||||
year_id = fields.Many2one(comodel_name='kpi.period',string='Year',required=True)
|
||||
period_goals_id = fields.Many2one('kpi.period.notes',force_save=1,string='Period',tracking=True,)
|
||||
department_id = fields.Many2one('hr.department',required=True,readonly=False,store=True,compute='compute_depart_job', tracking=True,string='Department')
|
||||
job_id = fields.Many2one('hr.job',force_save=1,readonly=True,store=True, string='Job Title',related='employee_id.job_id',tracking=True,)
|
||||
|
||||
goals_mark = fields.Float(store=True,string='Goals Apprisal Mark',readonly=True,tracking=True)
|
||||
skill_mark = fields.Float(store=True,string='Skills Apprisal Mark',readonly=True,tracking=True)
|
||||
total_score = fields.Float(string='Total Mark',store=True,readonly=True,compute='compute_total_score',tracking=True)
|
||||
apprisal_result = fields.Many2one('appraisal.result',string='Apprisal Result',store=True,tracking=True)
|
||||
|
||||
notes= fields.Text(string='Notes',required=False)
|
||||
goal_ids = fields.One2many('years.employee.goals', 'employee_apprisal_id', string='Goals')
|
||||
skill_ids = fields.One2many('skill.item.employee.table', 'employee_apprisal_id', string='Skills')
|
||||
|
||||
@api.constrains('employee_id', 'year_id')
|
||||
def check_unique_employee_year_period_goals(self):
|
||||
for record in self:
|
||||
if self.search_count([
|
||||
('employee_id', '=', record.employee_id.id),
|
||||
('year_id', '=', record.year_id.id),
|
||||
('id', '!=', record.id),
|
||||
]) > 0:
|
||||
raise exceptions.ValidationError(_("Employee Apprisal must be unique per Employee, Year, and Period!"))
|
||||
@api.depends('skill_mark','goals_mark',)
|
||||
def compute_total_score(self):
|
||||
appraisal_result_list = []
|
||||
for rec in self:
|
||||
if rec.skill_mark and rec.goals_mark and rec.job_id.appraisal_percentages_id.percentage_kpi>0.0 and rec.job_id.appraisal_percentages_id.percentage_skills>0.0:
|
||||
skill_mark_precentage = rec.skill_mark*rec.job_id.appraisal_percentages_id.percentage_skills
|
||||
goal_mark_precentage = rec.goals_mark*rec.job_id.appraisal_percentages_id.percentage_kpi
|
||||
|
||||
rec.total_score = (skill_mark_precentage+goal_mark_precentage)
|
||||
appraisal_result = self.env['appraisal.result'].search([
|
||||
('result_from', '<', rec.total_score),
|
||||
('result_to', '>=', rec.total_score)])
|
||||
if rec.total_score and len(appraisal_result) > 1:
|
||||
for line in appraisal_result:
|
||||
appraisal_result_list.append(line.name)
|
||||
raise exceptions.Warning(
|
||||
_('Please check appraisal result configuration , there is more than result for '
|
||||
'percentage %s are %s ') % (
|
||||
round(rec.total_score, 2), appraisal_result_list))
|
||||
else:
|
||||
rec.appraisal_result = appraisal_result.id
|
||||
def get_user_id(self):
|
||||
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
if employee_id:
|
||||
return employee_id.id
|
||||
else:
|
||||
return False
|
||||
|
||||
@api.depends('employee_id')
|
||||
def compute_depart_job(self):
|
||||
for rec in self:
|
||||
if rec.employee_id:
|
||||
rec.department_id = rec.employee_id.department_id.id
|
||||
def compute_apprisal(self):
|
||||
year_goal_obj = self.env['years.employee.goals'].search([('employee_id','=',self.employee_id.id),('year_id','=',self.year_id.id)])
|
||||
if year_goal_obj:
|
||||
print('if goal...........')
|
||||
self.goal_ids = year_goal_obj.ids
|
||||
#
|
||||
sum2 = 0
|
||||
for rec in self.goal_ids:
|
||||
sum2 = sum2+ ((rec.weight*int(rec.choiec))/100)
|
||||
self.goals_mark = sum2
|
||||
#
|
||||
item_lines=[(5,0,0)]
|
||||
skill_apprisal = self.env['skill.appraisal'].search([('employee_id','=',self.employee_id.id),('year_id','=',self.year_id.id),('job_id','=',self.job_id.id)])
|
||||
dic_item = {}
|
||||
print('s a = ',skill_apprisal)
|
||||
for obj in skill_apprisal:
|
||||
for rec in obj.items_ids:
|
||||
if rec.mark and rec.item_id:
|
||||
if rec.item_id.name in dic_item:
|
||||
dic_item[rec.item_id.name].append(rec.mark)
|
||||
else:
|
||||
dic_item.update({rec.item_id.name:[rec.mark]})
|
||||
print('dic_item = ',dic_item)
|
||||
averages = {}
|
||||
for key, values in dic_item.items():
|
||||
# Convert values to integers and calculate sum
|
||||
total = sum(int(value) for value in values)
|
||||
# Calculate average
|
||||
avg = total / len(values)
|
||||
# Store the average in the dictionary
|
||||
averages[key] = avg
|
||||
|
||||
if self.job_id:
|
||||
for line in self.job_id.item_job_ids:
|
||||
|
||||
line_item = {'item_id':line.item_id.id,'name':line.name,'level':line.level,}
|
||||
if line.item_id.name in averages:
|
||||
line_item.update({'mark_avg':averages[line.item_id.name]})
|
||||
item_lines.append((0,0,line_item))
|
||||
self.skill_ids = item_lines
|
||||
# Calculate the average of averages
|
||||
if len(averages)!=0:
|
||||
average_of_averages = sum(averages.values()) / len(averages)
|
||||
self.skill_mark = average_of_averages
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
from odoo import fields, models, exceptions, api, _
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from lxml import etree
|
||||
import json
|
||||
|
||||
|
||||
class EmployeePerformanceEvaluation(models.Model):
|
||||
_name = 'employee.performance.evaluation'
|
||||
_rec_name = 'employee_id'
|
||||
_inherit = ['mail.thread']
|
||||
_description = "Employee performance evaluation"
|
||||
recommendations = fields.Text(string='Recommendations', tracking=True, required=False)
|
||||
total = fields.Float(string='Total Mark', readonly=True, store=True, tracking=True, )
|
||||
mark_apprisal = fields.Float(string='Mark Apprisal', readonly=False, store=True, tracking=True,
|
||||
compute='total_mark')
|
||||
date_apprisal = fields.Date(default=lambda self: fields.Date.today(), string='Apprisal Date', tracking=True, )
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', tracking=True, required=True)
|
||||
manager_id = fields.Many2one('hr.employee', string='Employee m', readonly=False, tracking=True, required=False,
|
||||
default=lambda item: item.get_user_id())
|
||||
year_id = fields.Many2one(comodel_name='kpi.period', string='Year')
|
||||
period_goals_id = fields.Many2one('kpi.period.notes', force_save=1, string='Period', tracking=True, )
|
||||
department_id = fields.Many2one('hr.department', readonly=False, store=True, compute='compute_depart_job',
|
||||
tracking=True, string='Department')
|
||||
job_id = fields.Many2one('hr.job', force_save=1, readonly=True, store=True, string='Job Title',
|
||||
related='employee_id.job_id', tracking=True, )
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'), ('dir_manager', 'Wait Employee Accept'),
|
||||
('wait_dir_manager', 'Wait Manager Accept'),
|
||||
('wait_hr_manager', 'Wait HR Manager Accept'),
|
||||
('approve', 'Accept'),
|
||||
('refuse', 'Refused')
|
||||
], string='State', tracking=True, default='draft')
|
||||
emp_goal_ids = fields.One2many(comodel_name='period.goals', inverse_name='employee_eval_id',
|
||||
string='Employee Goals', copy=True)
|
||||
|
||||
@api.constrains('employee_id', 'year_id', 'period_goals_id')
|
||||
def check_unique_employee_year_period_goals(self):
|
||||
for record in self:
|
||||
if self.search_count([
|
||||
('employee_id', '=', record.employee_id.id),
|
||||
('year_id', '=', record.year_id.id),
|
||||
('period_goals_id', '=', record.period_goals_id.id),
|
||||
('id', '!=', record.id),
|
||||
]) > 0:
|
||||
raise exceptions.ValidationError(
|
||||
_("Employee Goals Apprisal must be unique per Employee, Year, and Period!"))
|
||||
|
||||
def get_user_id(self):
|
||||
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
if employee_id:
|
||||
return employee_id.id
|
||||
else:
|
||||
return False
|
||||
|
||||
@api.depends('employee_id')
|
||||
def compute_depart_job(self):
|
||||
for rec in self:
|
||||
if rec.employee_id:
|
||||
rec.department_id = rec.employee_id.department_id.id
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(EmployeePerformanceEvaluation, self).fields_view_get(view_id=view_id, view_type=view_type,
|
||||
toolbar=toolbar,
|
||||
submenu=submenu)
|
||||
doc = etree.XML(res['arch'])
|
||||
emp_group = self.env.ref('exp_hr_appraisal.group_appraisal_employee').id
|
||||
user_group = self.env.ref('exp_hr_appraisal.group_appraisal_user').id
|
||||
manager_group = self.env.ref('exp_hr_appraisal.group_appraisal_manager').id
|
||||
current_user_gids = self.env.user.groups_id.mapped('id')
|
||||
if ((emp_group in current_user_gids) and (user_group not in current_user_gids) and (
|
||||
manager_group not in current_user_gids)):
|
||||
if view_type == 'tree' or view_type == 'form':
|
||||
print('if node1.....')
|
||||
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('if node.....')
|
||||
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif ((user_group in current_user_gids or manager_group in current_user_gids)):
|
||||
if view_type == 'tree' or view_type == 'form':
|
||||
print('if node2.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif (
|
||||
user_group in current_user_gids and manager_group in current_user_gids and emp_group in current_user_gids):
|
||||
if view_type == 'tree' or view_type == 'form':
|
||||
print('if node3.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
return res
|
||||
|
||||
def send(self):
|
||||
self.state = 'wait_dir_manager'
|
||||
|
||||
def reset_draft(self):
|
||||
self.state = 'draft'
|
||||
|
||||
def action_approval(self):
|
||||
if self.state == 'dir_manager':
|
||||
self.state = 'wait_dir_manager'
|
||||
elif self.state == 'wait_dir_manager':
|
||||
self.state = 'wait_hr_manager'
|
||||
else:
|
||||
self.state = 'approve'
|
||||
|
||||
def action_refuse(self):
|
||||
self.state = 'refuse'
|
||||
|
||||
def onchange_emp_goal_ids(self):
|
||||
goals_lines = [(5, 0, 0)]
|
||||
sum = 0
|
||||
period_goal_obj = self.env['period.goals'].search(
|
||||
[('period_goals_id', '=', self.period_goals_id.id), ('employee_id', '=', self.employee_id.id),
|
||||
('year_id', '=', self.year_id.id)])
|
||||
self.emp_goal_ids = period_goal_obj.ids
|
||||
for rec in self.emp_goal_ids:
|
||||
sum = sum + ((rec.weight * rec.mark_evaluation) / 100)
|
||||
self.mark_apprisal = sum
|
||||
|
||||
def unlink(self):
|
||||
for rec in self:
|
||||
if rec.state != 'draft':
|
||||
raise ValidationError(_("You can't delete a Goal apprisal not in Draft State , archive it instead."))
|
||||
return super().unlink()
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
from odoo import fields, models, api,_
|
||||
from lxml import etree
|
||||
import json
|
||||
from odoo.exceptions import MissingError, UserError, ValidationError, AccessError
|
||||
|
||||
class KPICategory(models.Model):
|
||||
_inherit = 'kpi.category'
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(KPICategory, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar,
|
||||
submenu=submenu)
|
||||
doc = etree.XML(res['arch'])
|
||||
emp_group = self.env.ref('exp_hr_appraisal.group_appraisal_employee').id
|
||||
user_group = self.env.ref('exp_hr_appraisal.group_appraisal_user').id
|
||||
manager_group = self.env.ref('exp_hr_appraisal.group_appraisal_manager').id
|
||||
current_user_gids = self.env.user.groups_id.mapped('id')
|
||||
if ((emp_group in current_user_gids) and (user_group not in current_user_gids )and(manager_group not in current_user_gids)):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node1.....')
|
||||
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('if node.....')
|
||||
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif ((user_group in current_user_gids or manager_group in current_user_gids)):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node2.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif (user_group in current_user_gids and manager_group in current_user_gids and emp_group in current_user_gids):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node3.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
return res
|
||||
class KPIitem(models.Model):
|
||||
_inherit = 'kpi.item'
|
||||
department_item_id = fields.Many2one(comodel_name='hr.department',string='Department')
|
||||
responsible_item_id = fields.Many2one(comodel_name='hr.employee',string='Responsible')
|
||||
mark_ids = fields.One2many(comodel_name='mark.mark',inverse_name='kip_id')
|
||||
method_of_calculate = fields.Selection(
|
||||
string='Method Of Calculate',
|
||||
selection=[('accumulative', 'Accumulative'),
|
||||
('avrerage', 'Average'),('undefined', 'Undefined'),],
|
||||
required=False,default='accumulative')
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(KPIitem, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar,
|
||||
submenu=submenu)
|
||||
doc = etree.XML(res['arch'])
|
||||
emp_group = self.env.ref('exp_hr_appraisal.group_appraisal_employee').id
|
||||
user_group = self.env.ref('exp_hr_appraisal.group_appraisal_user').id
|
||||
manager_group = self.env.ref('exp_hr_appraisal.group_appraisal_manager').id
|
||||
current_user_gids = self.env.user.groups_id.mapped('id')
|
||||
if ((emp_group in current_user_gids) and (user_group not in current_user_gids )and(manager_group not in current_user_gids)):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node1.....')
|
||||
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('if node.....')
|
||||
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif ((user_group in current_user_gids or manager_group in current_user_gids)):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node2.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif (user_group in current_user_gids and manager_group in current_user_gids and emp_group in current_user_gids):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node3.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
return res
|
||||
|
||||
@api.onchange('department_item_id')
|
||||
def onchange_responsible(self):
|
||||
domain = []
|
||||
if self.department_item_id:
|
||||
# Define your dynamic domain based on field1's value
|
||||
domain = [('department_id', '=', self.department_item_id.id)]
|
||||
return {'domain': {'responsible_item_id': domain}}
|
||||
|
||||
|
||||
class Marks(models.Model):
|
||||
_name = 'mark.mark'
|
||||
choiec = fields.Selection(string='Choiec',selection=[('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'),('5','5'),])
|
||||
target = fields.Float(string='From(Done)',)
|
||||
to = fields.Float(string='To(Target)',)
|
||||
kip_id = fields.Many2one(comodel_name='kpi.item',string='Kip_id')
|
||||
|
||||
@api.constrains('target', 'to', 'kip_id')
|
||||
def _check_target_to_values(self):
|
||||
for record in self:
|
||||
if record.to <= record.target:
|
||||
raise ValidationError(_('The To value must be greater than the From value.'))
|
||||
|
||||
# Get previous marks for the same KPI sorted by target
|
||||
# previous_marks = self.env['mark.mark'].search([('kip_id', '=', record.kip_id.id), ('id', '!=', record.id)], order='target')
|
||||
# for prev_mark in previous_marks:
|
||||
# if record.target <= prev_mark.to:
|
||||
# raise ValidationError(_('The From value must be greater than the previous To value.'))
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
from odoo import fields, models, api,_
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import models, api, exceptions
|
||||
from datetime import timedelta
|
||||
class KPIPeriod(models.Model):
|
||||
_inherit = 'kpi.period'
|
||||
kpi_periods_ids = fields.One2many(
|
||||
comodel_name='kpi.period.notes',
|
||||
inverse_name='kpi_period_id',
|
||||
ondelete='cascade') # Add this line to enable cascade deletion
|
||||
kpi_goals_periods_ids = fields.One2many(
|
||||
comodel_name='kpi.period.notes',
|
||||
inverse_name='kpi_goal_period_id',
|
||||
ondelete='cascade' ) # Add this line to enable cascade deletion
|
||||
|
||||
|
||||
|
||||
class KIPSkills (models.Model):
|
||||
_name = 'kpi.period.notes'
|
||||
|
||||
name = fields.Char(string='Name',)
|
||||
sequence = fields.Char(string='Sequence',)
|
||||
date_start_k = fields.Date(string='Star Date',)
|
||||
date_end_k = fields.Date(string='End Date',)
|
||||
kpi_period_id = fields.Many2one(comodel_name='kpi.period',ondelete='cascade')
|
||||
kpi_goal_period_id = fields.Many2one(comodel_name='kpi.period',ondelete='cascade')
|
||||
|
||||
def create_apprisal_goals_employee(self):
|
||||
employee_objs = self.env['hr.employee'].search([('state','=','open')])
|
||||
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
|
||||
for item in self:
|
||||
# Fill employee appraisal
|
||||
for element in employee_objs:
|
||||
appraisal_line = {
|
||||
'employee_id': element.id,
|
||||
'year_id': item.kpi_goal_period_id.id,
|
||||
'department_id': element.department_id.id,
|
||||
'job_id': element.job_id.id,
|
||||
'manager_id': employee_id.id,
|
||||
'date_apprisal': fields.Date.today(),
|
||||
'period_goals_id': item.id,
|
||||
}
|
||||
line_id = self.env['employee.performance.evaluation'].create(appraisal_line)
|
||||
line_id.onchange_emp_goal_ids()
|
||||
@api.constrains('date_start_k','kpi_goal_period_id','date_end_k')
|
||||
def _check_period_overlap(self):
|
||||
for record in self:
|
||||
if record.kpi_goal_period_id:
|
||||
periods = record.kpi_goal_period_id.kpi_goals_periods_ids.sorted(key=lambda r: r.date_start_k)
|
||||
for i in range(1, len(periods)):
|
||||
if periods[i-1].date_end_k >= periods[i].date_start_k:
|
||||
raise ValidationError(_("Overlap detected between periods!"))
|
||||
|
||||
@api.constrains('date_start_k','kpi_period_id','date_end_k')
|
||||
def _check_period_overlap2(self):
|
||||
for record in self:
|
||||
if record.kpi_period_id:
|
||||
periods = record.kpi_period_id.kpi_periods_ids.sorted(key=lambda r: r.date_start_k)
|
||||
for i in range(1, len(periods)):
|
||||
if periods[i-1].date_end_k >= periods[i].date_start_k:
|
||||
raise ValidationError(_("Overlap detected between periods!"))
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
from odoo import fields, models, api
|
||||
class Skill(models.Model):
|
||||
_name = 'skill.skill'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char(string='Name', required=True,tracking=True,)
|
||||
description = fields.Text(string='Description',tracking=True,)
|
||||
items_ids = fields.One2many('skill.item', 'skill_id', string='Items',tracking=True,)
|
||||
class SkillItems(models.Model):
|
||||
_name = 'skill.item'
|
||||
|
||||
skill_id = fields.Many2one('skill.skill', string='Skill',ondelete='cascade')
|
||||
skill_appraisal_id = fields.Many2one(comodel_name='skill.appraisal')
|
||||
name = fields.Char(string='Description')
|
||||
level = fields.Selection([('beginner', '1'),('intermediate', '2'),('advanced', '3')],string='Level', default='beginner')
|
||||
mark = fields.Selection([('1', '1'),('2', '2'),('3', '3'),('4', '4'),('5', '5')],string='Mark',Ccopy=False)
|
||||
mark_avg = fields.Float(string='Mark',Ccopy=False)
|
||||
item_id = fields.Many2one(comodel_name='item.item',string='Item')
|
||||
display_type = fields.Selection([
|
||||
('line_section', "Section"),
|
||||
('line_note', "Note")],default=False, help="Technical field for UX purpose.")
|
||||
employee_apprisal_id = fields.Many2one(
|
||||
comodel_name='hr.employee.appraisal')
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
|
||||
class SkillItems(models.Model):
|
||||
_name = 'skill.item.table'
|
||||
|
||||
skill_id = fields.Many2one('skill.skill', string='Skill')
|
||||
skill_appraisal_id = fields.Many2one(comodel_name='skill.appraisal',ondelete='cascade')
|
||||
name = fields.Char(string='Description')
|
||||
level = fields.Selection([('beginner', '1'),('intermediate', '2'),('advanced', '3')],string='Level', default='beginner')
|
||||
mark = fields.Selection([('1', '1'),('2', '2'),('3', '3'),('4', '4'),('5', '5')],string='Mark',Ccopy=False)
|
||||
mark_avg = fields.Float(string='Mark',Ccopy=False)
|
||||
item_id = fields.Many2one(comodel_name='item.item',string='Item')
|
||||
employee_apprisal_id = fields.Many2one(
|
||||
|
||||
comodel_name='hr.employee.appraisal')
|
||||
class SkillItems(models.Model):
|
||||
_name = 'skill.item.employee.table'
|
||||
|
||||
skill_id = fields.Many2one('skill.skill', string='Skill')
|
||||
skill_appraisal_id = fields.Many2one(comodel_name='skill.appraisal',ondelete='cascade')
|
||||
name = fields.Char(string='Description')
|
||||
level = fields.Selection([('beginner', '1'),('intermediate', '2'),('advanced', '3')],string='Level', default='beginner')
|
||||
mark = fields.Selection([('1', '1'),('2', '2'),('3', '3'),('4', '4'),('5', '5')],string='Mark',Ccopy=False)
|
||||
mark_avg = fields.Float(string='Mark',Ccopy=False)
|
||||
item_id = fields.Many2one(comodel_name='item.item',string='Item')
|
||||
employee_apprisal_id = fields.Many2one(
|
||||
comodel_name='hr.employee.appraisal')
|
||||
|
||||
|
||||
class SkillItem(models.Model):
|
||||
_name = 'item.item'
|
||||
name = fields.Char(string='Name')
|
||||
|
||||
class SkillJob(models.Model):
|
||||
_inherit = 'hr.job'
|
||||
item_job_ids = fields.Many2many('skill.item', 'merge_item_skill1_rel', 'merge1_id', 'item1_id', string='Skills')
|
||||
# appraisal_percentage_id = fields.Many2one(comodel_name='job.class.apprisal',string='Appraisal Percentage')
|
||||
appraisal_percentages_id = fields.Many2one(comodel_name='job.class.apprisal',string='Appraisal Percentage')
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
from odoo import fields, models,exceptions, api,_
|
||||
from odoo.exceptions import UserError,ValidationError
|
||||
from lxml import etree
|
||||
import json
|
||||
class SkillAppraisal(models.Model):
|
||||
_name = 'skill.appraisal'
|
||||
_inherit = ['mail.thread']
|
||||
_rec_name = 'employee_id'
|
||||
_description = 'Skill Appraisal'
|
||||
name= fields.Char(string='Name',tracking=True,)
|
||||
recommendations= fields.Text(string='Recommendations',tracking=True,required=False)
|
||||
date_apprisal = fields.Date(default=lambda self: fields.Date.today(),string='Apprisal Date',tracking=True,)
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee',tracking=True,required=True)
|
||||
manager_id = fields.Many2one('hr.employee', string='Manager',readonly=False,tracking=True,required=True,default=lambda item: item.get_user_id())
|
||||
period = fields.Many2one('kpi.period.notes',string='Period',tracking=True,)
|
||||
department_id = fields.Many2one('hr.department',readonly=True,store=True,compute='compute_depart_job', tracking=True,string='Department')
|
||||
job_id = fields.Many2one('hr.job',readonly=False,store=True, string='Job Title',tracking=True,)
|
||||
year_id = fields.Many2one(comodel_name='kpi.period',string='Year')
|
||||
|
||||
@api.constrains('employee_id', 'year_id', 'period')
|
||||
def check_unique_employee_year_period_skills(self):
|
||||
for record in self:
|
||||
if self.search_count([
|
||||
('employee_id', '=', record.employee_id.id),
|
||||
('year_id', '=', record.year_id.id),
|
||||
('period', '=', record.period.id),
|
||||
('id', '!=', record.id),
|
||||
]) > 0:
|
||||
raise exceptions.ValidationError(_("Employee Skill Apprisal must be unique per Employee, Year, and Period!"))
|
||||
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),('dir_manager', 'Wait Employee Accept'),
|
||||
('wait_dir_manager', 'Wait Manager Accept'),
|
||||
('wait_hr_manager', 'Wait HR Manager Accept'),
|
||||
('approve', 'Accept'),
|
||||
('refuse', 'Refused')
|
||||
], string='State',tracking=True,default='draft')
|
||||
avarage = fields.Float(string='Result',readonly=True,store=True,tracking=True,compute='calc_avg')
|
||||
items_ids = fields.One2many(comodel_name='skill.item.table',inverse_name='skill_appraisal_id',string='Items',copy=True)
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
|
||||
res = super(SkillAppraisal, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar,
|
||||
submenu=submenu)
|
||||
doc = etree.XML(res['arch'])
|
||||
emp_group = self.env.ref('exp_hr_appraisal.group_appraisal_employee').id
|
||||
user_group = self.env.ref('exp_hr_appraisal.group_appraisal_user').id
|
||||
manager_group = self.env.ref('exp_hr_appraisal.group_appraisal_manager').id
|
||||
current_user_gids = self.env.user.groups_id.mapped('id')
|
||||
if ((emp_group in current_user_gids) and (user_group not in current_user_gids )and(manager_group not in current_user_gids)):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node1.....')
|
||||
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('if node.....')
|
||||
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'false')
|
||||
node.set('delete', 'false')
|
||||
node.set('edit', 'false')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif ((user_group in current_user_gids or manager_group in current_user_gids)):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node2.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
res['arch'] = etree.tostring(doc)
|
||||
elif (user_group in current_user_gids and manager_group in current_user_gids and emp_group in current_user_gids):
|
||||
if view_type=='tree' or view_type=='form':
|
||||
print('if node3.....')
|
||||
# if view_type == 'tree':
|
||||
for node in doc.xpath("//tree"):
|
||||
print('for..node')
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
for node in doc.xpath("//form"):
|
||||
node.set('create', 'true')
|
||||
node.set('edit', 'true')
|
||||
|
||||
res['arch'] = etree.tostring(doc)
|
||||
return res
|
||||
def get_user_id(self):
|
||||
employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)
|
||||
if employee_id:
|
||||
return employee_id.id
|
||||
else:
|
||||
return False
|
||||
|
||||
@api.depends('employee_id')
|
||||
def compute_depart_job(self):
|
||||
for rec in self:
|
||||
if rec.employee_id:
|
||||
rec.department_id = rec.employee_id.department_id.id
|
||||
rec.job_id = rec.employee_id.job_id.id
|
||||
|
||||
@api.depends('items_ids.mark')
|
||||
def calc_avg(self):
|
||||
sum = 0
|
||||
for rec in self.items_ids:
|
||||
if rec.mark and len(self.items_ids)!=0:
|
||||
sum = sum+int(rec.mark)
|
||||
self.avarage = sum/len(self.items_ids)
|
||||
def send(self):
|
||||
self.state = 'dir_manager'
|
||||
def reset_draft(self):
|
||||
self.state = 'draft'
|
||||
def action_approval(self):
|
||||
if self.state=='dir_manager':
|
||||
self.state='wait_dir_manager'
|
||||
elif self.state=='wait_dir_manager':
|
||||
self.state='wait_hr_manager'
|
||||
else:
|
||||
self.state='approve'
|
||||
|
||||
def action_refuse(self):
|
||||
self.state = 'refuse'
|
||||
|
||||
@api.onchange('job_id','employee_id')
|
||||
def onchange_emp(self):
|
||||
item_lines=[(5,0,0)]
|
||||
for line in self.job_id.item_job_ids:
|
||||
line_item = {'item_id':line.item_id.id,'name':line.name,'level':line.level}
|
||||
item_lines.append((0,0,line_item))
|
||||
self.items_ids = item_lines
|
||||
|
||||
def unlink(self):
|
||||
for rec in self:
|
||||
if rec.state != 'draft':
|
||||
raise ValidationError(_("You can't delete a Skill apprisal not in Draft State , archive it instead."))
|
||||
return super().unlink()
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api,exceptions,_
|
||||
|
||||
|
||||
class Period(models.Model):
|
||||
_name = 'period.goals'
|
||||
|
||||
period_goals_id = fields.Many2one('kpi.period.notes', domain=[('kpi_period_id','=',False)], string='Period Of Goals', tracking=True)
|
||||
employee_goals_id = fields.Many2one('years.employee.goals')
|
||||
target = fields.Float(string='Target', store=True)
|
||||
done = fields.Float(string='Done')
|
||||
kpi_id = fields.Many2one(comodel_name='kpi.item', string='KPI', related='employee_goals_id.kpi_id')
|
||||
employee_eval_id = fields.Many2one(comodel_name='employee.performance.evaluation', string='KPI')
|
||||
weight = fields.Float(string='Weight', related='employee_goals_id.weight')
|
||||
mark_evaluation = fields.Integer(string='Evaluation Mark', store=True, compute='_compute_mark_evaluation')
|
||||
year_id = fields.Many2one(comodel_name='kpi.period', related='employee_goals_id.year_id')
|
||||
employee_id = fields.Many2one(comodel_name='hr.employee', related='employee_goals_id.employee_id')
|
||||
|
||||
@api.depends('done', 'target', 'kpi_id')
|
||||
def _compute_mark_evaluation(self):
|
||||
sum = 0
|
||||
for record in self:
|
||||
if record.done!=0.0 and record.target!=0.0 and record.kpi_id:
|
||||
done_percentage = (record.done / record.target) * 100
|
||||
marks = self.env['mark.mark'].search([('kip_id', '=', record.kpi_id.id)])
|
||||
if marks:
|
||||
# Finding the closest mark where the done_percentage fits into the target-to range
|
||||
closest_mark = min(
|
||||
marks,
|
||||
key=lambda x: abs(done_percentage - ((x.target + x.to) / 2))
|
||||
)
|
||||
if closest_mark.target <= done_percentage <= closest_mark.to:
|
||||
record.mark_evaluation = int(closest_mark.choiec)
|
||||
closest_mark = None
|
||||
for mark in marks:
|
||||
if mark.target <= done_percentage <= mark.to:
|
||||
record.mark_evaluation = mark.choiec
|
||||
break
|
||||
else:
|
||||
record.mark_evaluation = 0 # Or any other default value if fields are empty
|
||||
sum = sum+ ((record.weight*record.mark_evaluation)/100)
|
||||
record.employee_eval_id.mark_apprisal = sum
|
||||
|
||||
class YearEmployeeGoals(models.Model):
|
||||
_name = 'years.employee.goals'
|
||||
_inherit = ['mail.thread']
|
||||
_description = 'years employee goals'
|
||||
_rec_name = 'employee_id'
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee',tracking=True,required=True)
|
||||
year_id = fields.Many2one(comodel_name='kpi.period',string='Year')
|
||||
category_id = fields.Many2one(comodel_name='kpi.category',string='Category')
|
||||
kpi_id = fields.Many2one(comodel_name='kpi.item',string='KPI',)
|
||||
method_of_calculate = fields.Selection(related='kpi_id.method_of_calculate')
|
||||
responsible_item_id = fields.Many2one(comodel_name='hr.employee',related='kpi_id.responsible_item_id',store=True,string='Responsible')
|
||||
user_id = fields.Many2one(comodel_name='res.users',related='responsible_item_id.user_id',store=True,string='Responsible')
|
||||
department_id = fields.Many2one('hr.department',readonly=True,store=True,compute='compute_depart_job', tracking=True,string='Department')
|
||||
job_id = fields.Many2one('hr.job',readonly=True,store=True,compute='compute_depart_job', string='Job Title',tracking=True,)
|
||||
year_target = fields.Float(string='Year Target')
|
||||
weight = fields.Float(string='Weight')
|
||||
goals_period_ids = fields.One2many(comodel_name='period.goals',inverse_name='employee_goals_id',string='Period',copy=False)
|
||||
done = fields.Float(string='Done',store=True,compute='total_done')
|
||||
state = fields.Selection([('draft', 'Draft'),('apprisal', 'Apprisal'),('close', 'Close')], string='State',tracking=True,default='draft')
|
||||
choiec = fields.Integer(string='Choiec',store=True,compute='compute_choice')
|
||||
employee_apprisal_id = fields.Many2one(comodel_name='hr.employee.appraisal')
|
||||
first_period_traget = fields.Float(compute='_compute_first_period_traget', string='First Period Traget',
|
||||
inverse='_inverse_first_period_traget')
|
||||
second_period_traget = fields.Float(compute='_compute_second_period_traget', string='Second Period Traget',
|
||||
inverse='_inverse_second_period_traget')
|
||||
third_period_traget = fields.Float(compute='_compute_third_period_traget', string='Third Period Traget',
|
||||
inverse='_inverse_third_period_traget')
|
||||
fourth_period_traget = fields.Float(compute='_compute_fourth_period_traget', string='Fourth Period Traget',
|
||||
inverse='_inverse_fourth_period_traget')
|
||||
|
||||
def _compute_first_period_traget(self):
|
||||
for rec in self:
|
||||
rec.first_period_traget = 0.0
|
||||
first_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '1')
|
||||
if first_period:
|
||||
rec.first_period_traget = first_period.target
|
||||
|
||||
def _inverse_first_period_traget(self):
|
||||
for rec in self:
|
||||
first_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '1')
|
||||
if first_period:
|
||||
first_period.sudo().target = rec.first_period_traget
|
||||
else:
|
||||
if rec.year_id:
|
||||
first_period = rec.year_id.kpi_goals_periods_ids.filtered(lambda period: period.sequence == '1')
|
||||
if first_period:
|
||||
rec.goals_period_ids = [(0, 0, {'period_goals_id':first_period.id,'target':rec.first_period_traget})]
|
||||
|
||||
|
||||
def _compute_second_period_traget(self):
|
||||
for rec in self:
|
||||
rec.second_period_traget = 0.0
|
||||
second_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '2')
|
||||
if second_period:
|
||||
rec.second_period_traget = second_period.target
|
||||
|
||||
def _inverse_second_period_traget(self):
|
||||
for rec in self:
|
||||
second_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '2')
|
||||
if second_period:
|
||||
second_period.sudo().target = rec.second_period_traget
|
||||
else:
|
||||
if rec.year_id:
|
||||
second_period = rec.year_id.kpi_goals_periods_ids.filtered(lambda period: period.sequence == '2')
|
||||
if second_period:
|
||||
rec.goals_period_ids = [(0, 0, {'period_goals_id':second_period.id,'target':rec.second_period_traget})]
|
||||
|
||||
def _compute_third_period_traget(self):
|
||||
for rec in self:
|
||||
rec.third_period_traget = 0.0
|
||||
third_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '3')
|
||||
if third_period:
|
||||
rec.third_period_traget = third_period.target
|
||||
|
||||
def _inverse_third_period_traget(self):
|
||||
for rec in self:
|
||||
third_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '3')
|
||||
if third_period:
|
||||
third_period.sudo().target = rec.third_period_traget
|
||||
else:
|
||||
if rec.year_id:
|
||||
third_period = rec.year_id.kpi_goals_periods_ids.filtered(lambda period: period.sequence == '3')
|
||||
if third_period:
|
||||
rec.goals_period_ids = [(0, 0, {'period_goals_id':third_period.id,'target':rec.third_period_traget})]
|
||||
|
||||
def _compute_fourth_period_traget(self):
|
||||
for rec in self:
|
||||
rec.fourth_period_traget = 0.0
|
||||
fourth_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '4')
|
||||
if fourth_period:
|
||||
rec.fourth_period_traget = fourth_period.target
|
||||
|
||||
def _inverse_fourth_period_traget(self):
|
||||
for rec in self:
|
||||
fourth_period = rec.goals_period_ids.filtered(lambda period: period.period_goals_id.sequence == '4')
|
||||
if fourth_period:
|
||||
fourth_period.sudo().target = rec.fourth_period_traget
|
||||
else:
|
||||
if rec.year_id:
|
||||
fourth_period = rec.year_id.kpi_goals_periods_ids.filtered(lambda period: period.sequence == '4')
|
||||
if fourth_period:
|
||||
rec.goals_period_ids = [(0, 0, {'period_goals_id':fourth_period.id,'target':rec.fourth_period_traget})]
|
||||
|
||||
@api.model
|
||||
def search(self, args, offset=0, limit=None, order=None, count=False):
|
||||
# add domain filter to only show records related to login responsible_item_id employee
|
||||
if self.env.user.has_group("exp_hr_appraisal_kpi.group_appraisal_responsabil") and not self.env.user.has_group("exp_hr_appraisal.group_appraisal_manager") and not self.env.user.has_group("exp_hr_appraisal.group_appraisal_user") :
|
||||
args += [('user_id','=',self.env.user.id)]
|
||||
return super (YearEmployeeGoals,self).search(args,offset,limit,order,count)
|
||||
|
||||
@api.depends('goals_period_ids.done','goals_period_ids.target','method_of_calculate')
|
||||
def total_done(self):
|
||||
for rec in self:
|
||||
if rec.method_of_calculate=='accumulative':
|
||||
sum=0
|
||||
for record in rec.goals_period_ids:
|
||||
sum = sum+record.done
|
||||
|
||||
rec.done = sum
|
||||
elif rec.method_of_calculate=='avrerage':
|
||||
sum=0
|
||||
for record in rec.goals_period_ids:
|
||||
sum = (sum+record.done)
|
||||
rec.done = sum/len(rec.goals_period_ids)
|
||||
else:
|
||||
rec.done=0.0
|
||||
|
||||
|
||||
@api.depends('goals_period_ids.done','done','goals_period_ids.target','method_of_calculate')
|
||||
def compute_choice(self):
|
||||
for rec in self:
|
||||
choice = 0
|
||||
if rec.done!=0.0 and rec.year_target!=0.0 and rec.kpi_id:
|
||||
done_percentage = (rec.done / rec.year_target) * 100
|
||||
marks = self.env['mark.mark'].search([('kip_id', '=', rec.kpi_id.id),('target','<=',done_percentage),('to','>=',done_percentage)],limit=1)
|
||||
if marks:
|
||||
choice = marks.choiec
|
||||
rec.choiec = int(choice)
|
||||
|
||||
def apprisal(self):
|
||||
self.state='apprisal'
|
||||
|
||||
def action_close(self):
|
||||
self.state='close'
|
||||
|
||||
def action_set_to_dratt(self):
|
||||
self.state='draft'
|
||||
|
||||
@api.constrains('employee_id', 'year_id', 'kpi_id')
|
||||
def check_unique_employee_year_period_goals(self):
|
||||
for record in self:
|
||||
if self.search_count([
|
||||
('employee_id', '=', record.employee_id.id),
|
||||
('year_id', '=', record.year_id.id),
|
||||
('kpi_id', '=', record.kpi_id.id),
|
||||
('id', '!=', record.id),
|
||||
]) > 0:
|
||||
raise exceptions.ValidationError(_("Employee Goals must be unique per Employee, Year, and kpi!"))
|
||||
|
||||
@api.depends('employee_id')
|
||||
def compute_depart_job(self):
|
||||
for rec in self:
|
||||
if rec.employee_id:
|
||||
rec.department_id = rec.employee_id.department_id.id
|
||||
rec.job_id = rec.employee_id.job_id.id
|
||||
|
||||
@api.onchange('year_id')
|
||||
def onchange_emp(self):
|
||||
goals_lines=[(5,0,0)]
|
||||
if self.year_id:
|
||||
for line in self.year_id.kpi_goals_periods_ids:
|
||||
line_item = {'period_goals_id':line.id}
|
||||
goals_lines.append((0,0,line_item))
|
||||
self.goals_period_ids = goals_lines
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="apprisal_kpi_group" model="res.groups">
|
||||
<field name="name">Menu apprisal hide/show</field>
|
||||
</record>
|
||||
|
||||
<record id="group_appraisal_responsabil" model="res.groups">
|
||||
<field name="name">Goals Responsible</field>
|
||||
<field name="category_id" ref="exp_hr_appraisal.module_category_hr_appraisal"/>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user')),(4, ref('exp_hr_appraisal.group_appraisal_employee'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="extended_kpi_category_rule" model="ir.rule">
|
||||
<field name="name">Extended KPI Category Rule</field>
|
||||
<field name="model_id" ref="kpi_scorecard.model_kpi_category"/>
|
||||
<field name="domain_force">[
|
||||
'|',
|
||||
('company_id','=', False),
|
||||
('company_id', 'in', company_ids),
|
||||
]
|
||||
</field>
|
||||
<field name="groups"
|
||||
eval="[(4, ref('base.group_user')), (4, ref('exp_hr_appraisal.group_appraisal_employee')),(4, ref('exp_hr_appraisal.group_appraisal_manager')),(4, ref('exp_hr_appraisal.group_appraisal_user'))]"/>
|
||||
</record>
|
||||
<record id="extended_kpi_item_rule" model="ir.rule">
|
||||
<field name="name">Extended KPI Category Rule</field>
|
||||
<field name="model_id" ref="kpi_scorecard.model_kpi_item"/>
|
||||
<field name="domain_force">[
|
||||
'|',
|
||||
('company_id','=', False),
|
||||
('company_id', 'in', company_ids),
|
||||
]
|
||||
</field>
|
||||
<field name="groups"
|
||||
eval="[(4, ref('base.group_user')), (4, ref('exp_hr_appraisal.group_appraisal_employee')),(4, ref('exp_hr_appraisal.group_appraisal_manager')),(4, ref('exp_hr_appraisal.group_appraisal_user'))]"/>
|
||||
</record>
|
||||
<!--add record rule for skill apprisal,employee apprisal -->
|
||||
<record id="hr_employee_appraisal_kpi_employee_rule" model="ir.rule">
|
||||
<field name="name">Employee: views its Skill appraisals only</field>
|
||||
<field name="model_id" ref="model_skill_appraisal"/>
|
||||
<field name="domain_force">[('employee_id.user_id','=',user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_appraisal_goal_kpi_employee_rule" model="ir.rule">
|
||||
<field name="name">Employee: views its Goal appraisals only</field>
|
||||
<field name="model_id" ref="model_employee_performance_evaluation"/>
|
||||
<field name="domain_force">[('employee_id.user_id','=',user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_kpi_appraisal_manager_rule" model="ir.rule">
|
||||
<field name="name">Manager: views Skill appraisals of its subordinates</field>
|
||||
<field name="model_id" ref="model_skill_appraisal"/>
|
||||
<field name="domain_force">['|','|',('employee_id.department_id.manager_id','=',False),
|
||||
('employee_id.department_id.manager_id.user_id','in', [user.id]),
|
||||
('employee_id.department_id.parent_id.manager_id.user_id','in', [user.id])]
|
||||
</field>
|
||||
<field name="groups"
|
||||
eval="[(4, ref('hr_base.group_department_manager')),(4, ref('hr_base.group_division_manager'))]"/>
|
||||
</record>
|
||||
<record id="hr_employee_kpi_appraisal_goals_manager_rule" model="ir.rule">
|
||||
<field name="name">Manager: views Goals appraisals of its subordinates</field>
|
||||
<field name="model_id" ref="model_employee_performance_evaluation"/>
|
||||
<field name="domain_force">['|','|',('employee_id.department_id.manager_id','=',False),
|
||||
('employee_id.department_id.manager_id.user_id','in', [user.id]),
|
||||
('employee_id.department_id.parent_id.manager_id.user_id','in', [user.id])]
|
||||
</field>
|
||||
<field name="groups"
|
||||
eval="[(4, ref('hr_base.group_department_manager')),(4, ref('hr_base.group_division_manager'))]"/>
|
||||
</record>
|
||||
<record id="hr_employee_skill_appraisal_all_rule" model="ir.rule">
|
||||
<field name="name"> Manager: views Skills appraisals of all subordinates </field>
|
||||
<field name="model_id" ref="model_employee_performance_evaluation"/>
|
||||
<field name="domain_force">[(1 ,'=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('hr_base.group_executive_manager')),
|
||||
(4, ref('hr_base.group_general_manager')),
|
||||
(4, ref('exp_hr_appraisal.group_appraisal_manager')),
|
||||
(4, ref('hr.group_hr_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_goal_appraisal_all_rule" model="ir.rule">
|
||||
<field name="name"> Manager: views Goals appraisals of all subordinates </field>
|
||||
<field name="model_id" ref="model_skill_appraisal"/>
|
||||
<field name="domain_force">[(1 ,'=', 1)]</field>
|
||||
<field name="groups" eval="[(4, ref('hr_base.group_executive_manager')),
|
||||
(4, ref('hr_base.group_general_manager')),
|
||||
(4, ref('exp_hr_appraisal.group_appraisal_manager')),
|
||||
(4, ref('hr.group_hr_user'))]"/>
|
||||
</record>
|
||||
<!--#################################################################################################################################################################-->
|
||||
<!-- end -->
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mark2,access_marks2,model_mark_mark,base.group_user,1,1,1,1
|
||||
|
||||
access_mark3,access_marks3,model_mark_mark,exp_hr_appraisal.group_appraisal_employee,1,1,1,1
|
||||
access_mark4,access_marks4,model_mark_mark,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_mark5,access_marks5,model_mark_mark,exp_hr_appraisal.group_appraisal_user,1,1,1,1
|
||||
|
||||
access_kpi_p7,access_kpi_ps8,model_kpi_period_notes,base.group_user,1,1,1,1
|
||||
access_kpi_p7,access_kpi_ps8,model_kpi_period_notes,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_p7,access_kpi_ps8,model_kpi_period_notes,exp_hr_appraisal.group_appraisal_user,1,1,1,1
|
||||
access_kpi_p7,access_kpi_ps8,model_kpi_period_notes,exp_hr_appraisal.group_appraisal_employee,1,1,1,1
|
||||
|
||||
access_kpi_p535,access_kpi_ps185,model_skill_skill,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_p535,access_kpi_ps185,model_skill_skill,exp_hr_appraisal.group_appraisal_user,1,1,1,1
|
||||
|
||||
|
||||
access_kpi_p54435,access_kpi_ps18555,model_skill_item,base.group_user,1,1,1,1
|
||||
access_kpi_p544355,access_kpi_ps189555,model_skill_item_table,base.group_user,1,1,1,1
|
||||
access_kpi_p544385,access_kpi_ps179555,model_skill_item_employee_table,base.group_user,1,1,1,1
|
||||
|
||||
|
||||
|
||||
access_kpi_p53577,access_kpi_ps17785,model_item_item,base.group_user,1,1,1,1
|
||||
access_kpi_p53577,access_kpi_ps17785,model_item_item,exp_hr_appraisal.group_appraisal_user,1,1,1,1
|
||||
access_kpi_p53577,access_kpi_ps17785,model_item_item,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_p53577,access_kpi_ps17785,model_item_item,exp_hr_appraisal.group_appraisal_employee,1,1,1,1
|
||||
|
||||
access_kpi_p5357b7p,access_kpbi_ps17785p,model_skill_appraisal,base.group_user,1,1,1,1
|
||||
access_kpi_p5357b7,access_kpbi_ps17785,model_skill_appraisal,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_p5357b7,access_kpbi_ps17785,model_skill_appraisal,exp_hr_appraisal.group_appraisal_user,1,1,1,1
|
||||
access_kpi_p5357b7,access_kpbi_ps17785,model_skill_appraisal,exp_hr_appraisal.group_appraisal_employee,1,0,0,1
|
||||
|
||||
access_kpi_p5357b7d,access_kpbi_ps1778d5,model_skill_appraisal,hr_base.group_division_manager,1,1,1,1
|
||||
access_kpi_p535d7b7,access_kpbi_ps17d785,model_skill_appraisal,hr_base.group_department_manager,1,1,1,1
|
||||
access_kpi_p5357bd7,access_kpbi_ps1778d5,model_skill_appraisal,hr.group_hr_user,1,1,1,0
|
||||
|
||||
access_kpi_emp_performansel1,access_kpbi_emp_perfomance_evalution39,model_employee_performance_evaluation,base.group_user,1,1,1,0
|
||||
access_kpi_emp_performanse1,access_kpbi_emp_perfomance_evalution3,model_employee_performance_evaluation,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_emp_performanse1,access_kpbi_emp_perfomance_evalution3,model_employee_performance_evaluation,exp_hr_appraisal.group_appraisal_user,1,1,1,1
|
||||
access_kpi_emp_performanse1,access_kpbi_emp_perfomance_evalution3,model_employee_performance_evaluation,exp_hr_appraisal.group_appraisal_employee,1,0,0,1
|
||||
|
||||
access_kpi_emp_performanse11,access_kpbi_emp_perfomance_evalution33,model_employee_performance_evaluation,hr_base.group_division_manager,1,1,1,1
|
||||
access_kpi_emp_performanse12,access_kpbi_emp_perfomance_evalutionr3,model_employee_performance_evaluation,hr_base.group_department_manager,1,1,1,1
|
||||
access_kpi_emp_performanse13,access_kpbi_emp_perfomance_evalution53,model_employee_performance_evaluation,hr.group_hr_user,1,1,1,0
|
||||
|
||||
access_kpi_emp_goals_res,access_kpbi_emp_goals1_res,model_years_employee_goals,exp_hr_appraisal_kpi.group_appraisal_responsabil,1,1,1,0
|
||||
access_kpi_emp_goals11,access_kpbi_emp_goals1,model_years_employee_goals,base.group_user,1,1,1,0
|
||||
access_kpi_emp_goals1,access_kpbi_emp_goals11,model_years_employee_goals,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_emp_goals5,access_kpbi_emp_goals151,model_years_employee_goals,exp_hr_appraisal.group_appraisal_user,1,1,1,0
|
||||
access_kpi_emp_goals,access_kpbi_emp_goals1,model_years_employee_goals,hr_base.group_department_manager,1,1,0,0
|
||||
|
||||
access_kpi_emp_goals_period,access_kpbi_emp_period1,model_period_goals,base.group_user,1,1,1,1
|
||||
access_kpi_perecentage,access_kpbi_perecentage1,model_job_class_apprisal,base.group_user,1,1,1,1
|
||||
|
||||
access_kpi_category,access_kpi_category,kpi_scorecard.model_kpi_category,base.group_user,1,1,1,1
|
||||
access_kpi_category,access_kpbi_category1,kpi_scorecard.model_kpi_category,kpi_scorecard.group_kpi_admin,1,1,1,1
|
||||
access_kpi_category,access_kpbi_category1,kpi_scorecard.model_kpi_category,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_category1,access_kpbi_category2,kpi_scorecard.model_kpi_category,exp_hr_appraisal.group_appraisal_user,1,1,1,0
|
||||
access_kpi_category21,access_kpbi_category25,kpi_scorecard.model_kpi_category,exp_hr_appraisal.group_appraisal_employee,1,0,0,0
|
||||
|
||||
access_kpi_period,access_kpbi_period1,kpi_scorecard.model_kpi_period,base.group_user,1,0,0,0
|
||||
access_kpi_period,access_kpbi_period1,kpi_scorecard.model_kpi_period,kpi_scorecard.group_kpi_admin,1,1,1,1
|
||||
access_kpi_period,access_kpbi_period1,kpi_scorecard.model_kpi_period,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_category1,access_kpbi_period2,kpi_scorecard.model_kpi_period,exp_hr_appraisal.group_appraisal_user,1,1,1,0
|
||||
access_kpi_category21,access_kpbi_period25,kpi_scorecard.model_kpi_period,exp_hr_appraisal.group_appraisal_employee,1,0,0,0
|
||||
|
||||
access_kpi_item,access_kpbi_item1,kpi_scorecard.model_kpi_item,base.group_user,1,0,0,0
|
||||
access_kpi_item,access_kpbi_item1,kpi_scorecard.model_kpi_item,kpi_scorecard.group_kpi_admin,1,1,1,1
|
||||
access_kpi_item,access_kpbi_item1,kpi_scorecard.model_kpi_item,exp_hr_appraisal.group_appraisal_manager,1,1,1,1
|
||||
access_kpi_item1,access_item2,kpi_scorecard.model_kpi_item,exp_hr_appraisal.group_appraisal_user,1,1,1,0
|
||||
access_kpi_item21,access_kpbi_item25,kpi_scorecard.model_kpi_item,exp_hr_appraisal.group_appraisal_employee,1,0,0,0
|
||||
|
|
After Width: | Height: | Size: 32 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
@media (min-width: 768px){
|
||||
.rtl .navbar-right{
|
||||
float: left !important;
|
||||
}
|
||||
.rtl .navbar-right .dropdown .dropdown-menu{
|
||||
right: auto !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
.rtl .navbar-left{
|
||||
float: right !important;
|
||||
}
|
||||
.rtl .navbar-left .dropdown .dropdown-menu{
|
||||
left: auto !important;
|
||||
right: 0 !important;
|
||||
}
|
||||
.navbar-nav.navbar-right:last-child{
|
||||
margin-left: auto;
|
||||
}
|
||||
.rtl .pull-left{
|
||||
float: right !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="apprisal_percentag_form_view" model="ir.ui.view">
|
||||
<field name="name">apprisal.apprisal_percentag.form</field>
|
||||
<field name="model">job.class.apprisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Apprisal Percentag">
|
||||
<sheet>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="percentage_kpi" widget="percentage" />
|
||||
<field name="percentage_skills" widget="percentage" />
|
||||
<field name="job_ids"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="apprisal_percentag_tree_view" model="ir.ui.view">
|
||||
<field name="name">apprisal.apprisal_percentag.tree</field>
|
||||
<field name="model">job.class.apprisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="ModelTitle">
|
||||
<field name="name"/>
|
||||
<field name="percentage_kpi"/>
|
||||
<field name="percentage_skills"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="apprisal_percentag_search_view" model="ir.ui.view">-->
|
||||
<!-- <field name="name">ProjectName.apprisal_percentag.search</field>-->
|
||||
<!-- <field name="model">ProjectName.apprisal_percentag</field>-->
|
||||
<!-- <field name="arch" type="xml">-->
|
||||
<!-- <search string="Apprisal Percentag">-->
|
||||
<!-- <group expand="1" string="Group By">-->
|
||||
<!-- <filter string="Example Field" name="example_field" domain="[]"-->
|
||||
<!-- context="{'group_by':'example_field'}"/>-->
|
||||
<!-- </group>-->
|
||||
<!-- </search>-->
|
||||
<!-- </field>-->
|
||||
<!-- </record>-->
|
||||
|
||||
<record id="apprisal_percentag_act_window1" model="ir.actions.act_window">
|
||||
<field name="name">Apprisal Percentag</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">job.class.apprisal</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
There is no examples click here to add new ModelTitle.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Appraisal Percentage"
|
||||
id="menu_kpi_percentage"
|
||||
parent="exp_hr_appraisal.appraisal_configuration"
|
||||
action="apprisal_percentag_act_window1"
|
||||
sequence="1" groups="exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_employee"
|
||||
|
||||
/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="employee_apprisal_extend" model="ir.ui.view">
|
||||
<field name="name">employee.apprisal.form.extend</field>
|
||||
<field name="model">hr.employee.appraisal</field>
|
||||
<field name="inherit_id" ref="exp_hr_appraisal.hr_appraisal_form_view"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='appraisal_result']" position="replace">
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_date']" position="after">
|
||||
<field name="year_id"/>
|
||||
<field name="goals_mark"/>
|
||||
<field name="skill_mark"/>
|
||||
<field name="total_score"/>
|
||||
<field name="appraisal_result"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='great_level']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='level_achieved']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='level_achieved_percentage']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
|
||||
<xpath expr="//field[@name='manager_appraisal_line_id']" position="replace">
|
||||
<!-- <attribute name="invisible">1</attribute>-->
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_plan_id']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_type']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='standard_appraisal_employee_line_ids']" position="replace">
|
||||
<!-- <attribute name="invisible">1</attribute>-->
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='is_manager']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_from']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_to']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//sheet/group[2]" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='recompute_values_level_achieved']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="manager_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//sheet/group[1]" position="after">
|
||||
<group>
|
||||
<group>
|
||||
<button name="compute_apprisal" string="Compute Apprisal" type="object" class="oe_highlight"
|
||||
icon="fa-cogs"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
||||
<page string="Goals">
|
||||
<field name="goal_ids">
|
||||
<list create="0" delete="0" editable="bottom">
|
||||
<field name="kpi_id" width="12"
|
||||
options='{"no_open": False,"no_create_edit": True,"no_create":True}'/>
|
||||
<field name="weight" sum="Total Weight" width="12"/>
|
||||
<field name="year_target" sum="Total Target" width="12"/>
|
||||
<field name="done" width="12"/>
|
||||
<field name="choiec" width="12"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Skills">
|
||||
<field name="skill_ids">
|
||||
<list create="0" delete="0" editable="bottom">
|
||||
<field readonly="1" force_save='1' name="item_id" width="12"
|
||||
options='{"no_open": True,"no_create_edit": True}'/>
|
||||
<field readonly="1" force_save='1' name="name" width="12"/>
|
||||
<field readonly="1" force_save='1' name="level" width="12"/>
|
||||
<field force_save='1' name="mark_avg" width="12"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Notes">
|
||||
<field widget="html" required="0" name="notes"/>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="employee_apprisal_view_tree" model="ir.ui.view">
|
||||
<field name="name">employee_apprisal.extend.view.tree</field>
|
||||
<field name="model">hr.employee.appraisal</field>
|
||||
<field name="inherit_id" ref="exp_hr_appraisal.hr_appraisal_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='appraisal_plan_id']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="manager_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="year_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="before">
|
||||
<field name="skill_mark"/>
|
||||
<field name="goals_mark"/>
|
||||
<field name="total_score"/>
|
||||
<field name="apprisal_result"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="employee_apprisal_view_tree2" model="ir.ui.view">
|
||||
<field name="name">employee_apprisal.extend.view.tree</field>
|
||||
<field name="model">hr.group.employee.appraisal</field>
|
||||
<field name="inherit_id" ref="exp_hr_appraisal.employee_appraisal_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='totals_great_level']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_level_achieved']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_level_achieved_percentage']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_appraisal_result']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_plan_id']" position="replace">
|
||||
<field name="year_id"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<!-- Inherit Form View to Modify it -->
|
||||
<record id="group_employee_apprisal_extend" model="ir.ui.view">
|
||||
<field name="name">employee.apprisal.group.extend</field>
|
||||
<field name="model">hr.group.employee.appraisal</field>
|
||||
<field name="inherit_id" ref="exp_hr_appraisal.employee_appraisal_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='date']" position="after">
|
||||
<field name="year_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_plan_id']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_type']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_great_level']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_level_achieved']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_level_achieved_percentage']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='totals_appraisal_result']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='state']" position="attributes">
|
||||
<attribute name="statusbar_visible">
|
||||
draft,gen_appraisal,finish_appraisal,hr_approval,gm_approval,done
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='appraisal_id']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//separator[2]" position="replace">
|
||||
</xpath>
|
||||
<xpath expr="//separator[1]" position="replace">
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='employee_ids']" position="replace">
|
||||
<notebook>
|
||||
<page string="Employees">
|
||||
<field name="employee_ids" string="" readonly="state != 'draft'" >
|
||||
<list>
|
||||
<field name="name" string="Employee name"/>
|
||||
<field name="department_id" string="Department"/>
|
||||
<field name="job_id" string="Job title"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Apprisal">
|
||||
<field name="appraisal_ids" string=""
|
||||
readonly="state != 'draft'" invisible="state == 'draft'">
|
||||
<list editable="bottom">
|
||||
<field name="employee_id" width='12' string="Employee"/>
|
||||
<field name="department_id" width='12' string=""/>
|
||||
<field name="appraisal_date" width='12' string=""/>
|
||||
<field name="year_id" width='12' string=""/>
|
||||
<field name="skill_mark" width='12' string=""/>
|
||||
<field name="goals_mark" width='12' string=""/>
|
||||
<field name="total_score" width='12' string=""/>
|
||||
<field name="apprisal_result" width='12' string=""/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Form View -->
|
||||
<record id="view_emplo_evalu_form" model="ir.ui.view">
|
||||
<field name="name">evalution.employee.goals.form1</field>
|
||||
<field name="model">employee.performance.evaluation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Evaluation Employee Goals">
|
||||
<header>
|
||||
<button string="Send" groups='hr_base.group_division_manager' invisible="state != 'draft'" class="oe_highlight" type="object" name="send"/>
|
||||
<button string="Accept" invisible="state != 'dir_manager'" class="oe_highlight" type="object" name="action_approval"/>
|
||||
<button string="refuse" invisible="state != 'dir_manager'" class="oe_highlight" type="object" name="action_refuse"/>
|
||||
|
||||
<button string="Accept" groups='hr_base.group_department_manager' invisible="state != 'wait_dir_manager'" class="oe_highlight" type="object" name="action_approval"/>
|
||||
<button string="refuse" groups='hr_base.group_department_manager' invisible="state != 'wait_dir_manager'" class="oe_highlight" type="object" name="action_refuse"/>
|
||||
|
||||
<button string="Accept" groups='hr.group_hr_user' invisible="state != 'wait_hr_manager'" class="oe_highlight" type="object" name="action_approval"/>
|
||||
<button string="refuse" groups='hr.group_hr_user' invisible="state != 'wait_hr_manager'" class="oe_highlight" type="object" name="action_refuse"/>
|
||||
<button string="Reset To Draft" invisible="state not in ['approve', 'refuse']" class="oe_highlight" type="object" name="reset_draft"/>
|
||||
<field name="state" required="1" statusbar_visible="draft,wait_dir_manager,wait_hr_manager,approve,refuse" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id" required="1"/>
|
||||
<field name="manager_id" required="1"/>
|
||||
<field name="department_id" required="1"/>
|
||||
<field name="job_id" required="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_apprisal"/>
|
||||
<field name="year_id" required="1" options='{"no_open": True,"no_create_edit": True,"no_create":True}'/>
|
||||
<field name="period_goals_id" domain="[('kpi_goal_period_id', '=', year_id),('kpi_period_id','=',False)]" invisible="not year_id" required="1" options='{"no_open": False,"no_create_edit": True,"no_create":True}'/>
|
||||
<field name="mark_apprisal" decoration-bf="1" required="1"/>
|
||||
<!-- <field name="total" decoration-bf="1" />-->
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Employee Goals">
|
||||
<button string="Select Goals" class="oe_highlight" type="object" name="onchange_emp_goal_ids"/>
|
||||
<field name="emp_goal_ids">
|
||||
<list create="0" delete="0" editable="bottom">
|
||||
<field name="kpi_id" width="12" options='{"no_open": False,"no_create_edit": True,"no_create":True}'/>
|
||||
<field name="weight" sum="Total Weight" width="12"/>
|
||||
<field name="target" sum="Total Target" width="12"/>
|
||||
<field name="done" width="12"/>
|
||||
<field decoration-bf="1" name="mark_evaluation" width="12"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Recommendations">
|
||||
<field widget="html" required="0" name="recommendations"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree View -->
|
||||
<record id="view_evalution_goals_employee_tree1" model="ir.ui.view">
|
||||
<field name="name">evalution.employee.goals.tree1</field>
|
||||
<field name="model">employee.performance.evaluation</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Year Employee Goals" decoration-info="state == 'draft'" decoration-danger="state == 'refuse'" decoration-success="state== 'approve'" >
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="year_id"/>
|
||||
<field name="period_goals_id"/><field name="date_apprisal"/>
|
||||
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-danger="state == 'refuse'" decoration-success="state== 'approve'"/>
|
||||
<field name="mark_apprisal" sum='Total' decoration-bf="1"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Action -->
|
||||
<record id="action_evalution_goal_emp" model="ir.actions.act_window">
|
||||
<field name="name">Evaluation Employee Goals</field>
|
||||
<field name="res_model">employee.performance.evaluation</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_evalution_employee_goals_" sequence="2" groups="exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_employee" name="Employee Goals Appraisal" parent="exp_hr_appraisal.appraisal_menu_id" action="action_evalution_goal_emp"/>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="kpi_category_inherit" model="ir.ui.view">
|
||||
<field name="name">kpi.category.form</field>
|
||||
<field name="model">kpi.category</field>
|
||||
<field name="inherit_id" ref="kpi_scorecard.kpi_category_view_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook/page[1]" position="attributes">
|
||||
<attribute name="groups">kpi_scorecard.group_kpi_admin</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem name="Goals"
|
||||
id="menu_kpi_categories"
|
||||
parent="exp_hr_appraisal.appraisal_configuration"
|
||||
action="kpi_scorecard.kpi_category_action"
|
||||
sequence="1"
|
||||
groups="kpi_scorecard.group_kpi_admin,exp_hr_appraisal.group_appraisal_employee,exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_user"/>
|
||||
<menuitem name="KPI"
|
||||
id="menu_kpi_kpi"
|
||||
parent="exp_hr_appraisal.appraisal_configuration"
|
||||
action="kpi_scorecard.kpi_item_action"
|
||||
sequence="1"
|
||||
groups="kpi_scorecard.group_kpi_admin,exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_employee,exp_hr_appraisal.group_appraisal_user"
|
||||
/>
|
||||
<menuitem name="Periods"
|
||||
id="menu_kpi_period"
|
||||
parent="exp_hr_appraisal.appraisal_configuration"
|
||||
action="kpi_scorecard.kpi_period_action"
|
||||
sequence="1"
|
||||
groups="kpi_scorecard.group_kpi_admin,exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_employee"
|
||||
/>
|
||||
|
||||
<menuitem name="Goals and Skills" id="menu_kpi_goal_skill" parent="exp_hr_appraisal.appraisal_menu_id" sequence="3"/>
|
||||
<!-- todo start -->
|
||||
<!-- hide menu -->
|
||||
|
||||
<record model="ir.ui.menu" id="exp_hr_appraisal.appraisal_plan_menu">
|
||||
<field name="groups_id" eval="[(6,0,[ref('exp_hr_appraisal_kpi.apprisal_kpi_group')])]"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.menu" id="exp_hr_appraisal.appraisal_setting_menu">
|
||||
<field name="groups_id" eval="[(6,0,[ref('exp_hr_appraisal_kpi.apprisal_kpi_group')])]"/>
|
||||
</record>
|
||||
<record model="ir.ui.menu" id="exp_hr_appraisal.appraisal_menu">
|
||||
<field name="groups_id" eval="[(6,0,[ref('exp_hr_appraisal_kpi.apprisal_kpi_group')])]"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.menu" id="exp_hr_appraisal.appraisal_degree_menu">
|
||||
<field name="groups_id" eval="[(6,0,[ref('exp_hr_appraisal_kpi.apprisal_kpi_group')])]"/>
|
||||
</record>
|
||||
<!-- todo end -->
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<odoo>
|
||||
<!-- Inherit Form View to Modify it -->
|
||||
<record id="kpi_item_tree_extend" model="ir.ui.view">
|
||||
<field name="name">kpi.item.tree.extend</field>
|
||||
<field name="model">kpi.item</field>
|
||||
<field name="inherit_id" ref="kpi_scorecard.kpi_item_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//field[@name='formula_warning']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="hpi_item_extend" model="ir.ui.view">
|
||||
<field name="name">kpi.form.extend</field>
|
||||
<field name="model">kpi.item</field>
|
||||
<field name="inherit_id" ref="kpi_scorecard.kpi_item_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- add new tab -->
|
||||
<xpath expr="//field[@name='formula_warning']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='formula']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//h2" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='category_id']" position="after">
|
||||
<field name="department_item_id"/>
|
||||
<field name="responsible_item_id"/>
|
||||
<field name="method_of_calculate"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Marks" groups="base.group_user,exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_employee">
|
||||
<field name="mark_ids">
|
||||
<list editable="bottom">
|
||||
<field name="choiec" width="12"/>
|
||||
<field name="target" string="From(Done)" width="12"/>
|
||||
<field name="to" width="12"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page[2]" position="attributes">
|
||||
<attribute name="groups">kpi_scorecard.group_kpi_admin</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="kpi_period_form_extend" model="ir.ui.view">
|
||||
<field name="name">kpi.period.form.extend</field>
|
||||
<field name="model">kpi.period</field>
|
||||
<field name="inherit_id" ref="kpi_scorecard.kpi_period_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//page[1]" position="attributes">
|
||||
<attribute name="groups">kpi_scorecard.group_kpi_admin</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[2]" position="attributes">
|
||||
<attribute name="groups">kpi_scorecard.group_kpi_admin</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='%(kpi_scorecard.kpi_copy_template_action)d']" position="attributes">
|
||||
<attribute name="groups">kpi_scorecard.group_kpi_admin</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//page[2]" position="after">
|
||||
<page string="Goals Period">
|
||||
<field name="kpi_goals_periods_ids">
|
||||
<list editable="bottom">
|
||||
<field name="sequence" width="4"/>
|
||||
<field name="name" width="8"/>
|
||||
<field name="date_start_k" width="12"/>
|
||||
<field name="date_end_k" width="12"/>
|
||||
<button string="Create Apprisal" width="" class="oe_highlight" type="object" name="create_apprisal_goals_employee"/>
|
||||
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Skills Period">
|
||||
|
||||
<field name="kpi_periods_ids">
|
||||
<list editable="bottom">
|
||||
<field name="name" width="12"/>
|
||||
<field name="date_start_k" width="12"/>
|
||||
<field name="date_end_k" width="12"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Skill Form View -->
|
||||
<record id="view_skill_form" model="ir.ui.view">
|
||||
<field name="name">skill.form</field>
|
||||
<field name="model">skill.skill</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Skill">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Items">
|
||||
<field widget="section_and_note_one2many" name="items_ids">
|
||||
<list editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="item_id"/>
|
||||
<field widget="section_and_note_text" name="name"/>
|
||||
<field name="level"/>
|
||||
<control>
|
||||
<create name="add_product_control" string="Add a Item "/>
|
||||
<create name="add_section_control" string="Add a section " context="{'default_display_type': 'line_section'}"/>
|
||||
<create name="add_note_control" string="Add a note" context="{'default_display_type': 'line_note'}"/>
|
||||
</control>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Skill Tree View -->
|
||||
<record id="view_skill_tree" model="ir.ui.view">
|
||||
<field name="name">skill.tree</field>
|
||||
<field name="model">skill.skill</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Skills">
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="skill_search_view" model="ir.ui.view">
|
||||
<field name="name">kpi.skill.search</field>
|
||||
<field name="model">skill.skill</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Skill">
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="skill_act_window" model="ir.actions.act_window">
|
||||
<field name="name">Skills</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">skill.skill</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
There is no examples click here to add new Skill.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<!-- form inherit -->
|
||||
<!-- Inherit Form View to Modify it -->
|
||||
<record id="hr_job_from_extend" model="ir.ui.view">
|
||||
<field name="name">hr.job.extend</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Skills">
|
||||
<field name="item_job_ids" domain="[('display_type','=',False)]"/>
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="item_tree_view_tree" model="ir.ui.view">
|
||||
<field name="name">item.tree.view.tree</field>
|
||||
<field name="model">skill.item</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="item_tree_tree">
|
||||
<field name="skill_id"/>
|
||||
<field name="item_id"/>
|
||||
<field name="name"/>
|
||||
<field name="level"/>
|
||||
<field name="mark" invisible="1"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Inherit the menu -->
|
||||
<!-- <record model="ir.ui.menu" id="hr_base_reports.appraisal_report_menu">-->
|
||||
<!-- <field name="groups_id" eval="[(4, ref('exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_manager'))]"/>-->
|
||||
<!-- </record>-->
|
||||
<!-- end from -->
|
||||
<menuitem name="Skills" id="skill_menu" sequence="1" parent="exp_hr_appraisal_kpi.menu_kpi_goal_skill" groups="exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_manager"
|
||||
action="skill_act_window"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Form View -->
|
||||
<record id="view_skill_appraisal_form" model="ir.ui.view">
|
||||
<field name="name">skill.appraisal.form</field>
|
||||
<field name="model">skill.appraisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Skill Appraisal">
|
||||
<header>
|
||||
<button string="Send" groups='hr_base.group_division_manager' invisible="state not in ['draft']" class="oe_highlight" type="object" name="send"/>
|
||||
<button string="Accept" invisible="state not in ['dir_manager']" class="oe_highlight" type="object" name="action_approval"/>
|
||||
<button string="refuse" invisible="state not in ['dir_manager']" class="oe_highlight" type="object" name="action_refuse"/>
|
||||
|
||||
<button string="Accept" groups='hr_base.group_department_manager' invisible="state not in ['wait_dir_manager']" class="oe_highlight" type="object" name="action_approval"/>
|
||||
<button string="refuse" groups='hr_base.group_department_manager' invisible="state not in ['wait_dir_manager']" class="oe_highlight" type="object" name="action_refuse"/>
|
||||
|
||||
<button string="Accept" groups='hr.group_hr_user' invisible="state not in ['wait_hr_manager']" class="oe_highlight" type="object" name="action_approval"/>
|
||||
<button string="refuse" groups='hr.group_hr_user' invisible="state not in ['wait_hr_manager']" class="oe_highlight" type="object" name="action_refuse"/>
|
||||
<button string="Reset To Draft" invisible="state not in ['refuse','approve']" class="oe_highlight" type="object" name="reset_draft"/>
|
||||
<field name="state" required='1' statusbar_visible="draft,dir_manager,wait_dir_manager,wait_hr_manager,approve,refuse" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id" readonly="state not in ['draft']" required='1'/>
|
||||
<field readonly="state not in ['draft']" name="department_id" required='1'/>
|
||||
<field readonly="state not in ['draft']" name="job_id" required='1'/>
|
||||
<field readonly="state not in ['draft']" name="manager_id" required='1'/>
|
||||
|
||||
</group>
|
||||
<group>
|
||||
|
||||
<field readonly="state not in ['draft']" name="date_apprisal"/>
|
||||
<field readonly="state not in ['draft']" name="year_id" required="1" options='{"no_open": True,"no_create_edit": True,"no_create":True}'/>
|
||||
<field readonly="state != 'draft'" invisible=" not year_id" name="period" domain="[('kpi_period_id', '=',year_id),('kpi_goal_period_id','=',False)]" required="1" options='{"no_open": True,"no_create_edit": True,"no_create":True}'/>
|
||||
<field readonly="state not in ['draft']" required='1' decoration-bf="1" name="avarage"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Items">
|
||||
<field name="items_ids">
|
||||
<list create="0" delete='0' editable="bottom">
|
||||
<field readonly="1" force_save='1' name="item_id" width="12" options='{"no_open": True,"no_create_edit": True}'/>
|
||||
<field readonly="1" force_save='1' name="name" width="12" />
|
||||
<field readonly="1" force_save='1' name="level" width="12"/>
|
||||
<field force_save='1' invisible="parent.state not in ['wait_dir_manager','draft']" name="mark" width="12"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Recommendations">
|
||||
<field readonly="state not in ['draft']" widget="html" required="0" name="recommendations"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree View -->
|
||||
<record id="view_skill_appraisal_tree" model="ir.ui.view">
|
||||
<field name="name">skill.appraisal.tree</field>
|
||||
<field name="model">skill.appraisal</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Skill Appraisal" decoration-info="state == 'draft'" decoration-danger="state == 'refuse'" decoration-success="state== 'approve'">
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="period"/>
|
||||
<field name="date_apprisal"/>
|
||||
<field name="state" widget="badge" decoration-info="state == 'draft'" decoration-danger="state == 'refuse'" decoration-success="state== 'approve'"/>
|
||||
<field name="avarage" decoration-bf="1"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Action -->
|
||||
<record id="action_skill_appraisal" model="ir.actions.act_window">
|
||||
<field name="name">Skill Appraisal</field>
|
||||
<field name="res_model">skill.appraisal</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_skill_appraisal_list" groups="exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_user,exp_hr_appraisal.group_appraisal_employee" sequence="2" name="Employee Skill Appraisal" parent="exp_hr_appraisal.appraisal_menu_id" action="action_skill_appraisal"/>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Form View -->
|
||||
<record id="view_emplo_goals_form" model="ir.ui.view">
|
||||
<field name="name">years.employee.goals.form1</field>
|
||||
<field name="model">years.employee.goals</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Year Employee Goals">
|
||||
<header>
|
||||
<button string="Start Apprisal" invisible="state not in ['draft']" class="oe_highlight" type="object" name="apprisal"/>
|
||||
<button string="Close" invisible="state not in ['apprisal']" class="oe_highlight" type="object" name="action_close"/>
|
||||
<button string="Set To Dratt" invisible="state not in ['close','apprisal']" type="object" name="action_set_to_dratt"/>
|
||||
<field name="state" required="1" statusbar_visible="draft,apprisal,close" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="year_id" required="1"/>
|
||||
<field name="employee_id" required="1"/>
|
||||
<field name="department_id" required="1"/>
|
||||
<field required="1" name="job_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field required="1" name="category_id"/>
|
||||
<field required="1" name="kpi_id" domain="[('category_id', '=',category_id)]"/>
|
||||
<field required="1" name="responsible_item_id"/>
|
||||
<field required="1" name="year_target"/>
|
||||
<field required="1" invisible='1' name="method_of_calculate"/>
|
||||
<field name="done" invisible="state in ['draft']" readonly="method_of_calculate not in ['undefined']"/>
|
||||
<field name="choiec" invisible="state in ['draft']"/>
|
||||
<field required="1" name="weight"/>
|
||||
<field name="first_period_traget" invisible="1"/>
|
||||
<field name="second_period_traget" invisible="1"/>
|
||||
<field name="third_period_traget" invisible="1"/>
|
||||
<field name="fourth_period_traget" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Period">
|
||||
<field name="goals_period_ids" readonly="state == 'close'">
|
||||
<list create="0" editable="bottom">
|
||||
<field name="period_goals_id" width="12" options='{"no_open": False,"no_create_edit": True,"no_create":True}'/>
|
||||
<field sum='Totat Traget' name="target" width="12"/>
|
||||
<field name="done" width="12" column_invisible="parent.state == 'draft'" />
|
||||
<field name="mark_evaluation" width="12" column_invisible="parent.state == 'draft'"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree View -->
|
||||
<record id="view_year_goals_employee_tree1" model="ir.ui.view">
|
||||
<field name="name">years.employee.goals.tree1</field>
|
||||
<field name="model">years.employee.goals</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Year Employee Goals">
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="year_id"/>
|
||||
<field name="category_id"/>
|
||||
<field name="kpi_id"/>
|
||||
<field name="responsible_item_id"/>
|
||||
<field name="weight"/>
|
||||
<field name="done" />
|
||||
<field name="year_target" sum="Total"/>
|
||||
<field name="state" decoration-info="state == 'draft'" decoration-primary="state== 'apprisal'" decoration-success="state== 'close'" widget="badge"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Action -->
|
||||
<record id="action_year_goal_emp" model="ir.actions.act_window">
|
||||
<field name="name">Employee Goals</field>
|
||||
<field name="res_model">years.employee.goals</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem groups="exp_hr_appraisal_kpi.group_appraisal_responsabil,exp_hr_appraisal.group_appraisal_manager,exp_hr_appraisal.group_appraisal_user" id="menu_year_employee_goals_list" sequence="2" name="Employee Goals" parent="exp_hr_appraisal_kpi.menu_kpi_goal_skill" action="action_year_goal_emp"/>
|
||||
</odoo>
|
||||
|
|
@ -182,14 +182,14 @@ class HrPayslip(models.Model):
|
|||
current_leave_struct['number_of_days'] += hours / work_hours
|
||||
|
||||
# compute worked days
|
||||
work_data = contract.employee_id._get_work_days_data(day_from, day_to,
|
||||
work_data = contract.employee_id._get_work_days_data_batch(day_from, day_to,
|
||||
calendar=contract.resource_calendar_id)
|
||||
attendances = {
|
||||
'name': _("Normal Working Days paid at 100%"),
|
||||
'sequence': 1,
|
||||
'code': 'WORK100',
|
||||
'number_of_days': work_data['days'],
|
||||
'number_of_hours': work_data['hours'],
|
||||
'number_of_days': work_data.get('days', 0.0),
|
||||
'number_of_hours': work_data.get('hours', 0.0),
|
||||
'contract_id': contract.id,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ class HrPayrollStructure(models.Model):
|
|||
rule_ids = fields.Many2many('hr.salary.rule', 'hr_structure_salary_rule_rel', 'struct_id', 'rule_id',
|
||||
string='Salary Rules')
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('You cannot create a recursive salary structure.'))
|
||||
# @api.constrains('parent_id')
|
||||
# def _check_parent_id(self):
|
||||
# if not self._has_cycle():
|
||||
# raise ValidationError(_('You cannot create a recursive salary structure.'))
|
||||
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
|
|
@ -85,7 +85,7 @@ class HrSalaryRuleCategory(models.Model):
|
|||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
if not self._has_cycle():
|
||||
raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rule Category.'))
|
||||
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ class HrSalaryRule(models.Model):
|
|||
|
||||
@api.constrains('parent_rule_id')
|
||||
def _check_parent_rule_id(self):
|
||||
if not self._check_recursion(parent='parent_rule_id'):
|
||||
if not self._has_cycle('parent_rule_id'):
|
||||
raise ValidationError(_('Error! You cannot create recursive hierarchy of Salary Rules.'))
|
||||
|
||||
def _recursive_search_of_rules(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from . import test_payroll_rules
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo import fields
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
|
||||
class TestPayrollRules(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestPayrollRules, self).setUp()
|
||||
|
||||
self.env.user.tz = 'UTC'
|
||||
self.company = self.env.ref('base.main_company')
|
||||
|
||||
self.category_basic = self.env['hr.salary.rule.category'].create({
|
||||
'name': 'Basic',
|
||||
'code': 'BASIC',
|
||||
})
|
||||
self.structure = self.env['hr.payroll.structure'].create({
|
||||
'name': 'Test Structure',
|
||||
'code': 'TEST_STRUCT',
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
|
||||
attendances = []
|
||||
for day in range(5): # 0=Monday to 4=Friday
|
||||
attendances.append((0, 0, {
|
||||
'name': 'Morning',
|
||||
'dayofweek': str(day),
|
||||
'hour_from': 8,
|
||||
'hour_to': 12,
|
||||
'day_period': 'morning',
|
||||
}))
|
||||
attendances.append((0, 0, {
|
||||
'name': 'Afternoon',
|
||||
'dayofweek': str(day),
|
||||
'hour_from': 13,
|
||||
'hour_to': 17,
|
||||
'day_period': 'afternoon',
|
||||
}))
|
||||
|
||||
self.calendar = self.env['resource.calendar'].create({
|
||||
'name': 'Standard 40 Hours UTC',
|
||||
'tz': 'UTC',
|
||||
'hours_per_day': 8.0,
|
||||
'attendance_ids': attendances,
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee Payroll',
|
||||
'company_id': self.company.id,
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
})
|
||||
|
||||
if self.employee.resource_id:
|
||||
self.employee.resource_id.write({
|
||||
'calendar_id': self.calendar.id,
|
||||
'tz': 'UTC',
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Contract for Test',
|
||||
'employee_id': self.employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
'date_start': date.today() - timedelta(days=100),
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
'schedule_pay': 'monthly',
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
|
||||
self.payslip = self.env['hr.payslip'].create({
|
||||
'employee_id': self.employee.id,
|
||||
'contract_id': self.contract.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': date.today().replace(day=1),
|
||||
'date_to': (date.today().replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1),
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
|
||||
def test_satisfy_condition_python(self):
|
||||
rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Python Condition Rule',
|
||||
'sequence': 10,
|
||||
'code': 'PY_COND',
|
||||
'category_id': self.category_basic.id,
|
||||
'condition_select': 'python',
|
||||
'condition_python': 'result = contract.wage > 3000',
|
||||
'amount_select': 'fix',
|
||||
'amount_fix': 100.0,
|
||||
})
|
||||
localdict = {'contract': self.contract, 'employee': self.employee}
|
||||
self.assertTrue(rule._satisfy_condition(localdict))
|
||||
|
||||
rule.condition_python = 'result = contract.wage > 6000'
|
||||
self.assertFalse(rule._satisfy_condition(localdict))
|
||||
|
||||
def test_satisfy_condition_range(self):
|
||||
rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Range Condition Rule',
|
||||
'sequence': 10,
|
||||
'code': 'RANGE_COND',
|
||||
'category_id': self.category_basic.id,
|
||||
'condition_select': 'range',
|
||||
'condition_range': 'contract.wage',
|
||||
'condition_range_min': 1000,
|
||||
'condition_range_max': 6000,
|
||||
'amount_select': 'fix',
|
||||
'amount_fix': 100.0,
|
||||
})
|
||||
localdict = {'contract': self.contract}
|
||||
self.assertTrue(rule._satisfy_condition(localdict))
|
||||
|
||||
self.contract.wage = 8000
|
||||
self.assertFalse(rule._satisfy_condition(localdict))
|
||||
|
||||
def test_compute_rule_percentage(self):
|
||||
rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Percentage Rule',
|
||||
'sequence': 10,
|
||||
'code': 'PERCENT',
|
||||
'category_id': self.category_basic.id,
|
||||
'amount_select': 'percentage',
|
||||
'amount_percentage_base': 'contract.wage',
|
||||
'amount_percentage': 10.0,
|
||||
'quantity': '1.0',
|
||||
})
|
||||
localdict = {'contract': self.contract}
|
||||
amount, qty, rate = rule._compute_rule(localdict)
|
||||
self.assertEqual(amount, 5000.0)
|
||||
self.assertEqual(amount * qty * rate / 100.0, 500.0)
|
||||
|
||||
def test_compute_rule_python_code(self):
|
||||
rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Python Code Rule',
|
||||
'sequence': 10,
|
||||
'code': 'PY_CODE',
|
||||
'category_id': self.category_basic.id,
|
||||
'amount_select': 'code',
|
||||
'amount_python_compute': 'result = contract.wage + 500',
|
||||
})
|
||||
localdict = {'contract': self.contract}
|
||||
amount, qty, rate = rule._compute_rule(localdict)
|
||||
self.assertEqual(amount, 5500.0)
|
||||
|
||||
def test_get_contract(self):
|
||||
old_contract = self.env['hr.contract'].create({
|
||||
'name': 'Old Contract',
|
||||
'employee_id': self.employee.id,
|
||||
'wage': 4000,
|
||||
'state': 'close',
|
||||
'date_start': date.today() - timedelta(days=400),
|
||||
'date_end': date.today() - timedelta(days=200),
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
'struct_id': self.structure.id,
|
||||
})
|
||||
|
||||
date_from = date.today().replace(day=1)
|
||||
date_to = (date.today().replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
|
||||
contract_ids = self.env['hr.payslip'].get_contract(self.employee, date_from, date_to)
|
||||
|
||||
self.assertIn(self.contract.id, contract_ids)
|
||||
self.assertNotIn(old_contract.id, contract_ids)
|
||||
|
||||
def test_get_worked_day_lines(self):
|
||||
today = date.today()
|
||||
days_ahead = 0 - today.weekday()
|
||||
if days_ahead <= 0:
|
||||
days_ahead += 7
|
||||
|
||||
next_monday = today + timedelta(days=days_ahead)
|
||||
date_from = next_monday
|
||||
date_to = next_monday + timedelta(days=4)
|
||||
try:
|
||||
worked_days = self.env['hr.payslip'].get_worked_day_lines(self.contract, date_from, date_to)
|
||||
|
||||
work_entry = next((item for item in worked_days if item['code'] == 'WORK100'), None)
|
||||
|
||||
self.assertIsNotNone(work_entry, "WORK100 entry not found in result")
|
||||
|
||||
except AttributeError as e:
|
||||
print(f"Skipping test_get_worked_day_lines due to missing dependency: {e}")
|
||||
|
||||
def test_payslip_line_compute_total(self):
|
||||
line = self.env['hr.payslip.line'].create({
|
||||
'slip_id': self.payslip.id,
|
||||
'name': 'Test Line',
|
||||
'code': 'TEST',
|
||||
'contract_id': self.contract.id,
|
||||
'salary_rule_id': self.env['hr.salary.rule'].search([], limit=1).id,
|
||||
'employee_id': self.employee.id,
|
||||
'quantity': 2.0,
|
||||
'amount': 500.0,
|
||||
'rate': 50.0,
|
||||
'category_id': self.category_basic.id,
|
||||
})
|
||||
self.assertEqual(line.total, 500.0)
|
||||
|
||||
def test_get_inputs(self):
|
||||
rule_with_input = self.env['hr.salary.rule'].create({
|
||||
'name': 'Rule with Input',
|
||||
'code': 'INPUT_RULE',
|
||||
'category_id': self.category_basic.id,
|
||||
'amount_select': 'fix',
|
||||
'amount_fix': 0.0,
|
||||
'sequence': 50,
|
||||
})
|
||||
self.env['hr.rule.input'].create({
|
||||
'name': 'Commission Input',
|
||||
'code': 'COMMISSION',
|
||||
'input_id': rule_with_input.id,
|
||||
})
|
||||
self.structure.write({'rule_ids': [(4, rule_with_input.id)]})
|
||||
|
||||
date_from = date.today()
|
||||
date_to = date.today()
|
||||
inputs = self.env['hr.payslip'].get_inputs(self.contract, date_from, date_to)
|
||||
|
||||
found_input = next((i for i in inputs if i['code'] == 'COMMISSION'), None)
|
||||
self.assertIsNotNone(found_input)
|
||||
self.assertEqual(found_input['contract_id'], self.contract.id)
|
||||
|
|
@ -1175,7 +1175,7 @@ class HrOfficialMissionEmployee(models.Model):
|
|||
def check_dates(self):
|
||||
for rec in self:
|
||||
if rec.hour_from >= 24 or rec.hour_to >= 24:
|
||||
raise exceptions.ValidationError(_('Wrong Time Format.!'))
|
||||
raise ValidationError(_('Wrong Time Format.!'))
|
||||
date_from = datetime.strptime(str(rec.date_from), DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
date_to = datetime.strptime(str(rec.date_to), DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
delta = timedelta(days=1)
|
||||
|
|
@ -1190,7 +1190,7 @@ class HrOfficialMissionEmployee(models.Model):
|
|||
'&', ('hour_from', '>=', rec.hour_from), ('hour_to', '<=', rec.hour_to),
|
||||
])
|
||||
if missions_ids:
|
||||
raise exceptions.ValidationError(_('Sorry The Employee %s Actually On %s For this Time') %
|
||||
raise ValidationError(_('Sorry The Employee %s Actually On %s For this Time') %
|
||||
(rec.employee_id.name,
|
||||
missions_ids.official_mission_id.mission_type.name))
|
||||
date_from += delta
|
||||
|
|
@ -1203,7 +1203,7 @@ class HrOfficialMissionEmployee(models.Model):
|
|||
('official_mission_id.process_type', '=', 'training'),
|
||||
('official_mission_id.course_name.id', '=', item.official_mission_id.course_name.id)])
|
||||
if duplicated:
|
||||
raise exceptions.ValidationError(
|
||||
raise ValidationError(
|
||||
_("Employee %s has already take this course.") % (item.employee_id.name))
|
||||
if item.official_mission_id and item.official_mission_id.mission_type.duration_type == 'days' \
|
||||
and item.date_from and item.date_to:
|
||||
|
|
@ -1230,7 +1230,7 @@ class HrOfficialMissionEmployee(models.Model):
|
|||
if year_last_record == year_now_record:
|
||||
number_days = number_days + rec.days
|
||||
if number_days > days_per_year:
|
||||
raise exceptions.ValidationError(
|
||||
raise ValidationError(
|
||||
_("Sorry The Employee %s, The Number of Requests Cannot Exceed %s Maximum Days Per year.") % (
|
||||
rec.employee_id.name, days_per_year))
|
||||
####
|
||||
|
|
@ -1427,7 +1427,7 @@ class HrOfficialMissionEmployee(models.Model):
|
|||
def compute_number_of_hours(self):
|
||||
for item in self:
|
||||
if item.hour_from >= 24 or item.hour_to >= 24:
|
||||
raise exceptions.ValidationError(_('Wrong Time Format.!'))
|
||||
raise ValidationError(_('Wrong Time Format.!'))
|
||||
if item.official_mission_id.hour_to and item.official_mission_id.hour_from:
|
||||
if item.hour_from and item.hour_to:
|
||||
if (item.hour_to - item.hour_from) < 0:
|
||||
|
|
@ -1788,7 +1788,7 @@ class MissionTable(models.Model):
|
|||
date_to = rec.destination_id.date_to
|
||||
if date_from and date_to:
|
||||
if not (date_from <= rec.date <= date_to):
|
||||
raise exceptions.ValidationError(
|
||||
raise ValidationError(
|
||||
_("The mission date %(date)s must be between destination's date from %(date_from)s and date to %(date_to)s.",
|
||||
date=rec.date, date_from=date_from, date_to=date_to)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from . import test_official_mission
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import tagged
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestOfficialMission(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOfficialMission, self).setUp()
|
||||
|
||||
self.company = self.env.company
|
||||
|
||||
self.account = self.env['account.account'].create({
|
||||
'name': 'Mission Expense',
|
||||
'code': '600000',
|
||||
'account_type': 'expense',
|
||||
'reconcile': False,
|
||||
})
|
||||
|
||||
self.journal = self.env['account.journal'].create({
|
||||
'name': 'Mission Journal',
|
||||
'type': 'general',
|
||||
'code': 'MISS',
|
||||
'default_account_id': self.account.id,
|
||||
})
|
||||
|
||||
self.emp_type = self.env['hr.contract.type'].create({'name': 'Permanent'})
|
||||
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee',
|
||||
'work_email': 'test@example.com',
|
||||
'employee_type_id': self.emp_type.id,
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Test Contract',
|
||||
'employee_id': self.employee.id,
|
||||
'wage': 5000,
|
||||
'state': 'open',
|
||||
})
|
||||
self.employee.contract_id = self.contract
|
||||
|
||||
self.mission_type = self.env['hr.official.mission.type'].create({
|
||||
'name': 'External Mission',
|
||||
'duration_type': 'days',
|
||||
'related_with_financial': True,
|
||||
'type_of_payment': 'fixed',
|
||||
'day_price': 100.0,
|
||||
'journal_id': self.journal.id,
|
||||
'account_id': self.account.id,
|
||||
'transfer_by_emp_type': False,
|
||||
'total_months': 12,
|
||||
'max_request_number': 5,
|
||||
})
|
||||
def test_01_duration_calculation(self):
|
||||
date_from = date.today()
|
||||
date_to = date.today() + timedelta(days=4)
|
||||
mission = self.env['hr.official.mission'].create({
|
||||
'mission_type': self.mission_type.id,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
})
|
||||
|
||||
mission._get_mission_no()
|
||||
|
||||
expected_days = 5
|
||||
self.assertEqual(mission.date_duration, expected_days, "Duration in days calculated incorrectly")
|
||||
|
||||
def test_02_workflow_and_financials(self):
|
||||
mission = self.env['hr.official.mission'].create({
|
||||
'mission_type': self.mission_type.id,
|
||||
'date_from': date.today(),
|
||||
'date_to': date.today() + timedelta(days=2),
|
||||
'move_type': 'accounting',
|
||||
})
|
||||
|
||||
|
||||
mission_line = self.env['hr.official.mission.employee'].create({
|
||||
'official_mission_id': mission.id,
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': mission.date_from,
|
||||
'date_to': mission.date_to,
|
||||
'hour_from': 8.0,
|
||||
'hour_to': 16.0,
|
||||
})
|
||||
|
||||
mission_line.days = 3
|
||||
mission_line.amount = 300.0
|
||||
|
||||
|
||||
self.assertEqual(mission_line.days, 3, "Employee line days incorrect")
|
||||
self.assertEqual(mission_line.amount, 300.0, "Employee amount calculation incorrect")
|
||||
|
||||
mission.send()
|
||||
self.assertEqual(mission.state, 'send')
|
||||
|
||||
mission.accounting_manager()
|
||||
mission.depart_manager()
|
||||
|
||||
mission.approve()
|
||||
|
||||
self.assertEqual(mission.state, 'approve', "Mission should be approved")
|
||||
|
||||
self.assertTrue(mission_line.account_move_id, "Journal Entry should be created")
|
||||
self.assertEqual(mission_line.account_move_id.state, 'draft', "Journal Entry should be draft initially")
|
||||
|
||||
move_lines = mission_line.account_move_id.line_ids
|
||||
debit_line = move_lines.filtered(lambda l: l.debit > 0)
|
||||
self.assertEqual(debit_line.debit, 300.0, "Journal Entry amount mismatch")
|
||||
def test_03_overlap_constraint(self):
|
||||
|
||||
mission1 = self.env['hr.official.mission'].create({
|
||||
'mission_type': self.mission_type.id,
|
||||
'date_from': date.today(),
|
||||
'date_to': date.today() + timedelta(days=5),
|
||||
})
|
||||
line1 = self.env['hr.official.mission.employee'].create({
|
||||
'official_mission_id': mission1.id,
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date.today(),
|
||||
'date_to': date.today() + timedelta(days=5),
|
||||
'hour_from': 8,
|
||||
'hour_to': 16,
|
||||
})
|
||||
mission1.state = 'approve'
|
||||
mission2 = self.env['hr.official.mission'].create({
|
||||
'mission_type': self.mission_type.id,
|
||||
'date_from': date.today() + timedelta(days=2),
|
||||
'date_to': date.today() + timedelta(days=6),
|
||||
})
|
||||
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env['hr.official.mission.employee'].create({
|
||||
'official_mission_id': mission2.id,
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date.today() + timedelta(days=2),
|
||||
'date_to': date.today() + timedelta(days=6),
|
||||
'hour_from': 8,
|
||||
'hour_to': 16,
|
||||
})
|
||||
|
||||
def test_04_employees_required(self):
|
||||
mission = self.env['hr.official.mission'].create({
|
||||
'mission_type': self.mission_type.id,
|
||||
'date_from': date.today(),
|
||||
'date_to': date.today(),
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
mission.send()
|
||||
|
|
@ -66,4 +66,6 @@
|
|||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': True,
|
||||
'test_tags': ['standard', 'at_install'],
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,25 +104,26 @@ class SalaryRuleInput(models.Model):
|
|||
|
||||
def withdraw(self):
|
||||
payslip = self.env['hr.payslip'].search([('number', '=', self.number)])
|
||||
loans = self.env['hr.loan.salary.advance'].search([('employee_id', '=', self.employee_id.id)])
|
||||
if self.number == payslip.number:
|
||||
if self.loan_ids:
|
||||
for loan in self.loan_ids:
|
||||
loan.paid = False
|
||||
if loans:
|
||||
for i in loans:
|
||||
if i.id == loan.loan_id.id:
|
||||
for l in i.deduction_lines:
|
||||
if loan.date == l.installment_date and loan.paid is False:
|
||||
l.paid = False
|
||||
#i.remaining_loan_amount += l.installment_amount
|
||||
i.get_remaining_loan_amount()
|
||||
if 'hr.loan.salary.advance' in self.env:
|
||||
loans = self.env['hr.loan.salary.advance'].search([('employee_id', '=', self.employee_id.id)])
|
||||
if self.number == payslip.number:
|
||||
if self.loan_ids:
|
||||
for loan in self.loan_ids:
|
||||
loan.paid = False
|
||||
if loans:
|
||||
for i in loans:
|
||||
if i.id == loan.loan_id.id:
|
||||
for l in i.deduction_lines:
|
||||
if loan.date == l.installment_date and loan.paid is False:
|
||||
l.paid = False
|
||||
#i.remaining_loan_amount += l.installment_amount
|
||||
i.get_remaining_loan_amount()
|
||||
|
||||
# check remaining loan and change state to pay
|
||||
if i.state == 'closed' and i.remaining_loan_amount > 0.0:
|
||||
i.state = 'pay'
|
||||
elif i.remaining_loan_amount == 0.0 and i.gm_propos_amount > 0.0:
|
||||
i.state = 'closed'
|
||||
# check remaining loan and change state to pay
|
||||
if i.state == 'closed' and i.remaining_loan_amount > 0.0:
|
||||
i.state = 'pay'
|
||||
elif i.remaining_loan_amount == 0.0 and i.gm_propos_amount > 0.0:
|
||||
i.state = 'closed'
|
||||
|
||||
for line in payslip.worked_days_line_ids:
|
||||
if line.name != 'Working days for this month':
|
||||
|
|
@ -852,38 +853,38 @@ class SalaryRuleInput(models.Model):
|
|||
d.amount = d.amount
|
||||
payslip.deduction_ids = [fields.Command.set(deductions.ids)]
|
||||
|
||||
# Loans #
|
||||
loans = self.env['hr.loan.salary.advance'].search([('employee_id', '=', payslip.employee_id.id),
|
||||
('request_type.refund_from', '=', 'salary'),
|
||||
('state', '=', 'pay')]).filtered(
|
||||
lambda item: item.employee_id.state == 'open')
|
||||
if loans:
|
||||
for loan in loans:
|
||||
for l in loan.deduction_lines:
|
||||
if not l.paid and (
|
||||
str(l.installment_date) <= str(payslip.date_from) or str(l.installment_date) <= str(
|
||||
payslip.date_to)):
|
||||
employee_loan_id = payslip.loan_ids.filtered(
|
||||
lambda item: item.name == loan.request_type.name)
|
||||
if not employee_loan_id:
|
||||
payslip_loans.append({
|
||||
'name': loan.request_type.name,
|
||||
'code': loan.code,
|
||||
'amount': round((-l.installment_amount), 2),
|
||||
'date': l.installment_date,
|
||||
'account_id': loan.request_type.account_id.id,
|
||||
'loan_id': loan.id
|
||||
})
|
||||
l.paid = True
|
||||
l.payment_date = payslip.date_to
|
||||
else:
|
||||
payslip.loan_ids = [(0, 0, loan_item) for loan_item in payslip_loans]
|
||||
if 'hr.loan.salary.advance' in self.env:
|
||||
loans = self.env['hr.loan.salary.advance'].search([('employee_id', '=', payslip.employee_id.id),
|
||||
('request_type.refund_from', '=', 'salary'),
|
||||
('state', '=', 'pay')]).filtered(
|
||||
lambda item: item.employee_id.state == 'open')
|
||||
if loans:
|
||||
for loan in loans:
|
||||
for l in loan.deduction_lines:
|
||||
if not l.paid and (
|
||||
str(l.installment_date) <= str(payslip.date_from) or str(l.installment_date) <= str(
|
||||
payslip.date_to)):
|
||||
employee_loan_id = payslip.loan_ids.filtered(
|
||||
lambda item: item.name == loan.request_type.name)
|
||||
if not employee_loan_id:
|
||||
payslip_loans.append({
|
||||
'name': loan.request_type.name,
|
||||
'code': loan.code,
|
||||
'amount': round((-l.installment_amount), 2),
|
||||
'date': l.installment_date,
|
||||
'account_id': loan.request_type.account_id.id,
|
||||
'loan_id': loan.id
|
||||
})
|
||||
l.paid = True
|
||||
l.payment_date = payslip.date_to
|
||||
else:
|
||||
payslip.loan_ids = [(0, 0, loan_item) for loan_item in payslip_loans]
|
||||
|
||||
# check remaining loan and change state to closed
|
||||
if loan.remaining_loan_amount <= 0.0 < loan.gm_propos_amount:
|
||||
loan.state = 'closed'
|
||||
# check remaining loan and change state to closed
|
||||
if loan.remaining_loan_amount <= 0.0 < loan.gm_propos_amount:
|
||||
loan.state = 'closed'
|
||||
|
||||
payslip.loan_ids = [(0, 0, loan_item) for loan_item in payslip_loans]
|
||||
payslip.loan_ids = [(0, 0, loan_item) for loan_item in payslip_loans]
|
||||
payslip.allowance_ids._compute_total()
|
||||
payslip.deduction_ids._compute_total()
|
||||
for pay in payslip:
|
||||
|
|
@ -2963,29 +2964,30 @@ class HrPayslipRun(models.Model):
|
|||
def withdraw(self):
|
||||
for line in self.slip_ids:
|
||||
payslip = self.env['hr.payslip'].search([('number', '=', line.number)])
|
||||
loans = self.env['hr.loan.salary.advance'].search([('employee_id', '=', line.employee_id.id)])
|
||||
if line.number == payslip.number:
|
||||
if line.loan_ids:
|
||||
for loan in line.loan_ids:
|
||||
loan.paid = False
|
||||
if loans:
|
||||
for i in loans:
|
||||
if i.id == loan.loan_id.id:
|
||||
for l in i.deduction_lines:
|
||||
if loan.date == l.installment_date and loan.paid is False:
|
||||
l.paid = False
|
||||
l.payment_date = False
|
||||
#i.remaining_loan_amount += l.installment_amount
|
||||
i.get_remaining_loan_amount()
|
||||
if 'hr.loan.salary.advance' in self.env:
|
||||
loans = self.env['hr.loan.salary.advance'].search([('employee_id', '=', line.employee_id.id)])
|
||||
if line.number == payslip.number:
|
||||
if line.loan_ids:
|
||||
for loan in line.loan_ids:
|
||||
loan.paid = False
|
||||
if loans:
|
||||
for i in loans:
|
||||
if i.id == loan.loan_id.id:
|
||||
for l in i.deduction_lines:
|
||||
if loan.date == l.installment_date and loan.paid is False:
|
||||
l.paid = False
|
||||
l.payment_date = False
|
||||
#i.remaining_loan_amount += l.installment_amount
|
||||
i.get_remaining_loan_amount()
|
||||
|
||||
# check remaining loan and change state to pay
|
||||
if i.state == 'closed' and i.remaining_loan_amount > 0.0:
|
||||
i.state = 'pay'
|
||||
elif i.remaining_loan_amount == 0.0 and i.gm_propos_amount > 0.0:
|
||||
i.state = 'closed'
|
||||
for record in payslip:
|
||||
record.write({'state': 'draft'})
|
||||
record.unlink()
|
||||
# check remaining loan and change state to pay
|
||||
if i.state == 'closed' and i.remaining_loan_amount > 0.0:
|
||||
i.state = 'pay'
|
||||
elif i.remaining_loan_amount == 0.0 and i.gm_propos_amount > 0.0:
|
||||
i.state = 'closed'
|
||||
for record in payslip:
|
||||
record.write({'state': 'draft'})
|
||||
record.unlink()
|
||||
self.write({'slip_ids': [fields.Command.clear()]})
|
||||
self.write({'state': 'draft'})
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ from datetime import datetime
|
|||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class HrContractSalaryScale(models.Model):
|
||||
_inherit = 'hr.contract'
|
||||
|
|
@ -15,84 +20,91 @@ class HrContractSalaryScale(models.Model):
|
|||
salary_degree = fields.Many2one(comodel_name='hr.payroll.structure', domain=[('id', 'in', [])])
|
||||
hide = fields.Boolean(string='Hide', compute="compute_type")
|
||||
required_condition = fields.Boolean(string='Required Condition', compute='compute_move_type')
|
||||
total_allowance = fields.Float(string='Total Allowance', compute='compute_function',store=True)
|
||||
total_deduction = fields.Float(string='Total Deduction', compute='compute_function',store=True)
|
||||
total_net = fields.Float(string='Total Net', compute='compute_function',store=True)
|
||||
total_allowance = fields.Float(string='Total Allowance', compute='compute_function', store=True)
|
||||
total_deduction = fields.Float(string='Total Deduction', compute='compute_function', store=True)
|
||||
total_net = fields.Float(string='Total Net', compute='compute_function', store=True)
|
||||
advantages = fields.One2many('contract.advantage', 'contract_advantage_id', string='Advantages')
|
||||
house_allowance_temp = fields.Float(string='House Allowance', compute='compute_function',store=True)
|
||||
transport_allowance = fields.Float(string='Transport Allowance', compute='compute_function',store=True)
|
||||
house_allowance_temp = fields.Float(string='House Allowance', compute='compute_function', store=True)
|
||||
transport_allowance = fields.Float(string='Transport Allowance', compute='compute_function', store=True)
|
||||
|
||||
@api.constrains('advantages', 'salary', 'salary_group')
|
||||
def amount_constrains(self):
|
||||
for rec in self:
|
||||
localdict = dict(employee=rec.employee_id.id, contract=rec.env['hr.contract'].search([
|
||||
('employee_id', '=', rec.employee_id.id)]))
|
||||
localdict = dict(employee=rec.employee_id, contract=rec)
|
||||
|
||||
if rec.salary_group.gread_max > 0 and rec.salary_group.gread_min > 0:
|
||||
if rec.salary > rec.salary_group.gread_max or rec.salary < rec.salary_group.gread_min:
|
||||
raise UserError(_('The Basic Salary Is Greater Than Group Gread Max Or less than Gread Min'))
|
||||
for item in self.advantages:
|
||||
item.to_get_contract_id()
|
||||
if item.benefits_discounts._compute_rule(localdict)[0] < item.amount and item.type == 'exception':
|
||||
raise UserError(_(
|
||||
'The amount you put is greater than fact value of this Salary rule %s (%s).') % (
|
||||
item.benefits_discounts.name, item.benefits_discounts.code))
|
||||
if rec.salary > rec.salary_group.gread_max or rec.salary < rec.salary_group.gread_min:
|
||||
raise UserError(_('The Basic Salary Is Greater Than Group Gread Max Or less than Gread Min'))
|
||||
|
||||
for item in rec.advantages:
|
||||
if item.type == 'exception':
|
||||
rule_val = item.benefits_discounts._compute_rule(localdict)[0]
|
||||
if rule_val < item.amount:
|
||||
raise UserError(_(
|
||||
'The amount you put is greater than fact value of this Salary rule %s (%s).') % (
|
||||
item.benefits_discounts.name, item.benefits_discounts.code))
|
||||
|
||||
@api.depends('salary_scale.transfer_type')
|
||||
def compute_move_type(self):
|
||||
self.compute_function()
|
||||
# self.compute_function()
|
||||
if self.salary_scale.transfer_type == 'one_by_one':
|
||||
self.required_condition = True
|
||||
else:
|
||||
self.required_condition = False
|
||||
|
||||
@api.depends('salary_scale', 'salary_level', 'salary_group', 'salary_degree','salary','advantages','house_allowance_temp','transport_allowance','total_deduction','salary_insurnce','total_allowance','state')
|
||||
@api.depends('salary_scale', 'salary_level', 'salary_group', 'salary_degree', 'salary', 'advantages',
|
||||
'house_allowance_temp', 'transport_allowance', 'total_deduction', 'total_allowance', 'state')
|
||||
def compute_function(self):
|
||||
for item in self:
|
||||
item.house_allowance_temp = 0
|
||||
item.transport_allowance = 0
|
||||
item.total_net = 0
|
||||
contract = self.env['hr.contract'].search([('employee_id', '=', item.employee_id.id)])
|
||||
localdict = dict(employee=item.employee_id.id, contract=contract)
|
||||
current_date = datetime.now().date()
|
||||
|
||||
# customize type in advantages
|
||||
localdict = dict(employee=item.employee_id, contract=item)
|
||||
current_date = fields.Date.today()
|
||||
|
||||
allowance_customize_items = item.advantages.filtered(
|
||||
lambda key: key.type == 'customize' and key.out_rule is False and
|
||||
key.benefits_discounts.category_id.rule_type == 'allowance' and
|
||||
(datetime.strptime(str(key.date_to), "%Y-%m-%d").date() if key.date_to else current_date)
|
||||
>= current_date >= datetime.strptime(str(key.date_from), "%Y-%m-%d").date())
|
||||
key.benefits_discounts.category_id.rule_type == 'allowance' and
|
||||
(key.date_to if key.date_to else current_date) >= current_date >= key.date_from
|
||||
)
|
||||
|
||||
allow_sum_custom = sum(x.amount for x in allowance_customize_items)
|
||||
for x in allowance_customize_items:
|
||||
if x.benefits_discounts.rules_type == 'house':
|
||||
item.house_allowance_temp += x.amount
|
||||
|
||||
if x.benefits_discounts.rules_type == 'transport':
|
||||
item.transport_allowance += x.amount
|
||||
# allow_custom_ids = [record.benefits_discounts.id for record in allowance_customize_items]
|
||||
|
||||
deduction_customize_items = item.advantages.filtered(
|
||||
lambda key: key.type == 'customize' and key.out_rule is False and
|
||||
key.benefits_discounts.category_id.rule_type == 'deduction' and
|
||||
(datetime.strptime(str(key.date_to), "%Y-%m-%d").date() if key.date_to else current_date)
|
||||
>= current_date >= datetime.strptime(str(key.date_from), "%Y-%m-%d").date())
|
||||
(key.date_to if key.date_to else current_date) >= current_date >= key.date_from
|
||||
)
|
||||
|
||||
ded_sum_custom = sum(x.amount for x in deduction_customize_items)
|
||||
ded_custom_ids = [record.benefits_discounts.id for record in deduction_customize_items]
|
||||
ded_custom_ids = deduction_customize_items.mapped('benefits_discounts.id')
|
||||
|
||||
# exception type in advantages
|
||||
exception_items = item.advantages.filtered(lambda key: key.type == 'exception')
|
||||
|
||||
if exception_items:
|
||||
exception_items = exception_items.filtered(
|
||||
lambda key: (key.date_to.month if key.date_to else current_date.month)
|
||||
>= current_date.month >= key.date_from.month
|
||||
)
|
||||
|
||||
total_rule_result, sum_except, sum_customize_expect = 0.0, 0.0, 0.0
|
||||
|
||||
for x in exception_items:
|
||||
rule_result = x.benefits_discounts._compute_rule(localdict)[0]
|
||||
if x.date_from >= str(current_date):
|
||||
|
||||
if x.date_from >= current_date:
|
||||
total_rule_result = rule_result
|
||||
elif str(current_date) > x.date_from:
|
||||
if x.date_to and str(current_date) <= x.date_to:
|
||||
elif current_date > x.date_from:
|
||||
if x.date_to and current_date <= x.date_to:
|
||||
total_rule_result = rule_result - x.amount
|
||||
elif x.date_to and str(current_date) >= x.date_to:
|
||||
total_rule_result = 0 # rule_result
|
||||
elif x.date_to and current_date >= x.date_to:
|
||||
total_rule_result = 0
|
||||
elif not x.date_to:
|
||||
total_rule_result = rule_result - x.amount
|
||||
else:
|
||||
|
|
@ -107,85 +119,42 @@ class HrContractSalaryScale(models.Model):
|
|||
else:
|
||||
sum_except += total_rule_result
|
||||
|
||||
if exception_items:
|
||||
exception_items = item.advantages.filtered(
|
||||
lambda key: (datetime.strptime(str(key.date_to),
|
||||
"%Y-%m-%d").date().month if key.date_to else current_date.month)
|
||||
>= current_date.month >= datetime.strptime(str(key.date_from), "%Y-%m-%d").date().month)
|
||||
|
||||
except_ids = [record.benefits_discounts.id for record in exception_items]
|
||||
except_ids = exception_items.mapped('benefits_discounts.id')
|
||||
|
||||
rule_ids = item.salary_scale.rule_ids.filtered(
|
||||
lambda key: key.id not in ded_custom_ids and key.id not in except_ids)
|
||||
|
||||
level_rule_ids = item.salary_level.benefits_discounts_ids.filtered(lambda key: key.id not in except_ids)
|
||||
# key.id not in allow_custom_ids and key.id not in ded_custom_ids and
|
||||
if item.salary_level:
|
||||
rule_ids += item.salary_level.rule_ids.filtered(
|
||||
lambda key: key.id not in except_ids)
|
||||
|
||||
group_rule_ids = item.salary_group.benefits_discounts_ids.filtered(lambda key: key.id not in except_ids)
|
||||
# key.id not in allow_custom_ids and key.id not in ded_custom_ids and
|
||||
if item.salary_group:
|
||||
rule_ids += item.salary_group.rule_ids.filtered(
|
||||
lambda key: key.id not in except_ids)
|
||||
|
||||
total_allowance = 0
|
||||
total_ded = 0
|
||||
|
||||
for line in rule_ids:
|
||||
try:
|
||||
amount = line._compute_rule(localdict)[0]
|
||||
except Exception:
|
||||
amount = 0.0
|
||||
|
||||
if line.category_id.rule_type == 'allowance':
|
||||
try:
|
||||
total_allowance += line._compute_rule(localdict)[0]
|
||||
except:
|
||||
total_allowance += 0
|
||||
|
||||
if line.category_id.rule_type == 'deduction':
|
||||
try:
|
||||
total_ded += line._compute_rule(localdict)[0]
|
||||
except:
|
||||
total_ded += 0
|
||||
|
||||
total_allowance += amount
|
||||
elif line.category_id.rule_type == 'deduction':
|
||||
total_ded += amount
|
||||
|
||||
if line.rules_type == 'house':
|
||||
item.house_allowance_temp += line._compute_rule(localdict)[0]
|
||||
item.house_allowance_temp += amount
|
||||
if line.rules_type == 'transport':
|
||||
item.transport_allowance += line._compute_rule(localdict)[0]
|
||||
item.transport_allowance += amount
|
||||
|
||||
item.total_allowance = total_allowance
|
||||
item.total_deduction = -total_ded
|
||||
|
||||
if item.salary_level:
|
||||
total_allowance = 0
|
||||
total_deduction = 0
|
||||
for line in level_rule_ids:
|
||||
if line.category_id.rule_type == 'allowance':
|
||||
try:
|
||||
total_allowance += line._compute_rule(localdict)[0]
|
||||
except:
|
||||
total_allowance += 0
|
||||
elif line.category_id.rule_type == 'deduction':
|
||||
try:
|
||||
total_deduction += line._compute_rule(localdict)[0]
|
||||
except:
|
||||
total_deduction += 0
|
||||
|
||||
item.total_allowance += total_allowance
|
||||
item.total_deduction += -total_deduction
|
||||
|
||||
if item.salary_group:
|
||||
total_allowance = 0
|
||||
total_deduction = 0
|
||||
for line in group_rule_ids:
|
||||
if line.category_id.rule_type == 'allowance':
|
||||
total_allowance += line._compute_rule(localdict)[0]
|
||||
elif line.category_id.rule_type == 'deduction':
|
||||
total_deduction += line._compute_rule(localdict)[0]
|
||||
|
||||
item.total_allowance += total_allowance
|
||||
item.total_deduction += -total_deduction
|
||||
|
||||
item.total_allowance += allow_sum_custom
|
||||
item.total_allowance += sum_customize_expect
|
||||
item.total_deduction += -ded_sum_custom
|
||||
item.total_deduction += -sum_except
|
||||
item.total_allowance = total_allowance + allow_sum_custom + sum_customize_expect
|
||||
item.total_deduction = -(total_ded + ded_sum_custom + sum_except)
|
||||
item.total_net = item.total_allowance + item.total_deduction
|
||||
|
||||
# filter salary_level,salary_group,salary_degree
|
||||
|
||||
@api.onchange('salary_scale')
|
||||
def onchange_salary_scale(self):
|
||||
for item in self:
|
||||
|
|
@ -207,8 +176,6 @@ class HrContractSalaryScale(models.Model):
|
|||
'salary_group': [('id', 'in', [])],
|
||||
'salary_degree': [('id', 'in', [])]}}
|
||||
|
||||
# filter depend on salary_level
|
||||
|
||||
@api.onchange('salary_level')
|
||||
def onchange_salary_level(self):
|
||||
for item in self:
|
||||
|
|
@ -221,7 +188,6 @@ class HrContractSalaryScale(models.Model):
|
|||
return {'domain': {'salary_group': [('id', 'in', [])],
|
||||
'salary_degree': [('id', 'in', [])]}}
|
||||
|
||||
# filter depend on salary_group
|
||||
|
||||
@api.onchange('salary_group')
|
||||
def onchange_salary_group(self):
|
||||
|
|
@ -232,29 +198,228 @@ class HrContractSalaryScale(models.Model):
|
|||
return {'domain': {'salary_degree': [('id', 'in', degree_ids.ids)]}}
|
||||
else:
|
||||
return {'domain': {'salary_degree': [('id', 'in', [])]}}
|
||||
|
||||
@api.depends('salary_degree')
|
||||
def _get_amount(self):
|
||||
for record in self:
|
||||
record.transport_allowance_temp = record.transport_allowance * record.wage / 100 \
|
||||
if record.transport_allowance_type == 'perc' else record.transport_allowance
|
||||
record.house_allowance_temp = record.house_allowance * record.wage / 100 \
|
||||
if record.house_allowance_type == 'perc' else record.house_allowance
|
||||
record.communication_allowance_temp = record.communication_allowance * record.wage / 100 \
|
||||
if record.communication_allowance_type == 'perc' else record.communication_allowance
|
||||
record.field_allowance_temp = record.field_allowance * record.wage / 100 \
|
||||
if record.field_allowance_type == 'perc' else record.field_allowance
|
||||
record.special_allowance_temp = record.special_allowance * record.wage / 100 \
|
||||
if record.special_allowance_type == 'perc' else record.special_allowance
|
||||
record.other_allowance_temp = record.other_allowance * record.wage / 100 \
|
||||
if record.other_allowance_type == 'perc' else record.other_allowance
|
||||
|
||||
@api.depends('contractor_type.salary_type')
|
||||
def compute_type(self):
|
||||
if self.contractor_type.salary_type == 'scale':
|
||||
self.hide = True
|
||||
else:
|
||||
self.hide = False
|
||||
for rec in self:
|
||||
if rec.contractor_type.salary_type == 'scale':
|
||||
rec.hide = True
|
||||
else:
|
||||
rec.hide = False
|
||||
#
|
||||
# class HrContractSalaryScale(models.Model):
|
||||
# _inherit = 'hr.contract'
|
||||
#
|
||||
# salary_level = fields.Many2one(comodel_name='hr.payroll.structure', domain=[('id', 'in', [])])
|
||||
# salary_scale = fields.Many2one(comodel_name='hr.payroll.structure', domain=[('id', 'in', [])], index=True)
|
||||
# salary_group = fields.Many2one(comodel_name='hr.payroll.structure', domain=[('id', 'in', [])])
|
||||
# salary_degree = fields.Many2one(comodel_name='hr.payroll.structure', domain=[('id', 'in', [])])
|
||||
# hide = fields.Boolean(string='Hide', compute="compute_type")
|
||||
# required_condition = fields.Boolean(string='Required Condition', compute='compute_move_type')
|
||||
# total_allowance = fields.Float(string='Total Allowance', compute='compute_function',store=True)
|
||||
# total_deduction = fields.Float(string='Total Deduction', compute='compute_function',store=True)
|
||||
# total_net = fields.Float(string='Total Net', compute='compute_function',store=True)
|
||||
# advantages = fields.One2many('contract.advantage', 'contract_advantage_id', string='Advantages')
|
||||
# house_allowance_temp = fields.Float(string='House Allowance', compute='compute_function',store=True)
|
||||
# transport_allowance = fields.Float(string='Transport Allowance', compute='compute_function',store=True)
|
||||
#
|
||||
# @api.constrains('advantages', 'salary', 'salary_group')
|
||||
# def amount_constrains(self):
|
||||
# for rec in self:
|
||||
# localdict = dict(employee=rec.employee_id.id, contract=rec.env['hr.contract'].search([
|
||||
# ('employee_id', '=', rec.employee_id.id)]))
|
||||
# if rec.salary_group.gread_max > 0 and rec.salary_group.gread_min > 0:
|
||||
# if rec.salary > rec.salary_group.gread_max or rec.salary < rec.salary_group.gread_min:
|
||||
# raise UserError(_('The Basic Salary Is Greater Than Group Gread Max Or less than Gread Min'))
|
||||
# for item in self.advantages:
|
||||
# item.to_get_contract_id()
|
||||
# if item.benefits_discounts._compute_rule(localdict)[0] < item.amount and item.type == 'exception':
|
||||
# raise UserError(_(
|
||||
# 'The amount you put is greater than fact value of this Salary rule %s (%s).') % (
|
||||
# item.benefits_discounts.name, item.benefits_discounts.code))
|
||||
#
|
||||
# @api.depends('salary_scale.transfer_type')
|
||||
# def compute_move_type(self):
|
||||
# self.compute_function()
|
||||
# if self.salary_scale.transfer_type == 'one_by_one':
|
||||
# self.required_condition = True
|
||||
# else:
|
||||
# self.required_condition = False
|
||||
#
|
||||
# @api.depends('salary_scale', 'salary_level', 'salary_group', 'salary_degree','salary','advantages','house_allowance_temp','transport_allowance','total_deduction','salary_insurnce','total_allowance','state')
|
||||
# def compute_function(self):
|
||||
# for item in self:
|
||||
# item.house_allowance_temp = 0
|
||||
# item.transport_allowance = 0
|
||||
# item.total_net = 0
|
||||
# contract = self.env['hr.contract'].search([('employee_id', '=', item.employee_id.id)])
|
||||
# localdict = dict(employee=item.employee_id.id, contract=contract)
|
||||
# current_date = datetime.now().date()
|
||||
#
|
||||
# # customize type in advantages
|
||||
# allowance_customize_items = item.advantages.filtered(
|
||||
# lambda key: key.type == 'customize' and key.out_rule is False and
|
||||
# key.benefits_discounts.category_id.rule_type == 'allowance' and
|
||||
# (datetime.strptime(str(key.date_to), "%Y-%m-%d").date() if key.date_to else current_date)
|
||||
# >= current_date >= datetime.strptime(str(key.date_from), "%Y-%m-%d").date())
|
||||
#
|
||||
# allow_sum_custom = sum(x.amount for x in allowance_customize_items)
|
||||
# for x in allowance_customize_items:
|
||||
# if x.benefits_discounts.rules_type == 'house':
|
||||
# item.house_allowance_temp += x.amount
|
||||
#
|
||||
# if x.benefits_discounts.rules_type == 'transport':
|
||||
# item.transport_allowance += x.amount
|
||||
# # allow_custom_ids = [record.benefits_discounts.id for record in allowance_customize_items]
|
||||
#
|
||||
# deduction_customize_items = item.advantages.filtered(
|
||||
# lambda key: key.type == 'customize' and key.out_rule is False and
|
||||
# key.benefits_discounts.category_id.rule_type == 'deduction' and
|
||||
# (datetime.strptime(str(key.date_to), "%Y-%m-%d").date() if key.date_to else current_date)
|
||||
# >= current_date >= datetime.strptime(str(key.date_from), "%Y-%m-%d").date())
|
||||
#
|
||||
# ded_sum_custom = sum(x.amount for x in deduction_customize_items)
|
||||
# ded_custom_ids = [record.benefits_discounts.id for record in deduction_customize_items]
|
||||
#
|
||||
# # exception type in advantages
|
||||
# exception_items = item.advantages.filtered(lambda key: key.type == 'exception')
|
||||
# total_rule_result, sum_except, sum_customize_expect = 0.0, 0.0, 0.0
|
||||
#
|
||||
# for x in exception_items:
|
||||
# rule_result = x.benefits_discounts._compute_rule(localdict)[0]
|
||||
# if x.date_from >= current_date:
|
||||
# total_rule_result = rule_result
|
||||
# elif current_date > x.date_from:
|
||||
# if x.date_to and current_date <= x.date_to:
|
||||
# total_rule_result = rule_result - x.amount
|
||||
# elif x.date_to and current_date >= x.date_to:
|
||||
# total_rule_result = 0 # rule_result
|
||||
# elif not x.date_to:
|
||||
# total_rule_result = rule_result - x.amount
|
||||
# else:
|
||||
# if rule_result > x.amount:
|
||||
# total_rule_result = rule_result - x.amount
|
||||
#
|
||||
# if total_rule_result:
|
||||
# if x.benefits_discounts.category_id.rule_type == 'allowance':
|
||||
# sum_customize_expect += total_rule_result
|
||||
# if x.benefits_discounts.rules_type == 'house':
|
||||
# item.house_allowance_temp += total_rule_result - x.amount
|
||||
# else:
|
||||
# sum_except += total_rule_result
|
||||
#
|
||||
# if exception_items:
|
||||
# exception_items = item.advantages.filtered(
|
||||
# lambda key: (key.date_to.month if key.date_to else current_date.month)
|
||||
# >= current_date.month >= key.date_from.month)
|
||||
# # if exception_items:
|
||||
# # exception_items = item.advantages.filtered(
|
||||
# # lambda key: (datetime.strptime(key.date_to,
|
||||
# # "%Y-%m-%d").date().month if key.date_to else current_date.month)
|
||||
# # >= current_date.month >= datetime.strptime(key.date_from, "%Y-%m-%d").date().month)
|
||||
#
|
||||
# except_ids = [record.benefits_discounts.id for record in exception_items]
|
||||
#
|
||||
# rule_ids = item.salary_scale.rule_ids.filtered(
|
||||
# lambda key: key.id not in ded_custom_ids and key.id not in except_ids)
|
||||
#
|
||||
# level_rule_ids = item.salary_level.benefits_discounts_ids.filtered(lambda key: key.id not in except_ids)
|
||||
# # key.id not in allow_custom_ids and key.id not in ded_custom_ids and
|
||||
#
|
||||
# group_rule_ids = item.salary_group.benefits_discounts_ids.filtered(lambda key: key.id not in except_ids)
|
||||
# # key.id not in allow_custom_ids and key.id not in ded_custom_ids and
|
||||
#
|
||||
# total_allowance = 0
|
||||
# total_ded = 0
|
||||
# for line in rule_ids:
|
||||
# if line.category_id.rule_type == 'allowance':
|
||||
# try:
|
||||
# total_allowance += line._compute_rule(localdict)[0]
|
||||
# except:
|
||||
# total_allowance += 0
|
||||
#
|
||||
# if line.category_id.rule_type == 'deduction':
|
||||
# try:
|
||||
# total_ded += line._compute_rule(localdict)[0]
|
||||
# except:
|
||||
# total_ded += 0
|
||||
#
|
||||
#
|
||||
# if line.rules_type == 'house':
|
||||
# item.house_allowance_temp += line._compute_rule(localdict)[0]
|
||||
# if line.rules_type == 'transport':
|
||||
# item.transport_allowance += line._compute_rule(localdict)[0]
|
||||
#
|
||||
# item.total_allowance = total_allowance
|
||||
# item.total_deduction = -total_ded
|
||||
#
|
||||
# if item.salary_level:
|
||||
# total_allowance = 0
|
||||
# total_deduction = 0
|
||||
# for line in level_rule_ids:
|
||||
# if line.category_id.rule_type == 'allowance':
|
||||
# try:
|
||||
# total_allowance += line._compute_rule(localdict)[0]
|
||||
# except:
|
||||
# total_allowance += 0
|
||||
# elif line.category_id.rule_type == 'deduction':
|
||||
# try:
|
||||
# total_deduction += line._compute_rule(localdict)[0]
|
||||
# except:
|
||||
# total_deduction += 0
|
||||
#
|
||||
# item.total_allowance += total_allowance
|
||||
# item.total_deduction += -total_deduction
|
||||
#
|
||||
# if item.salary_group:
|
||||
# total_allowance = 0
|
||||
# total_deduction = 0
|
||||
# for line in group_rule_ids:
|
||||
# if line.category_id.rule_type == 'allowance':
|
||||
# total_allowance += line._compute_rule(localdict)[0]
|
||||
# elif line.category_id.rule_type == 'deduction':
|
||||
# total_deduction += line._compute_rule(localdict)[0]
|
||||
#
|
||||
# item.total_allowance += total_allowance
|
||||
# item.total_deduction += -total_deduction
|
||||
#
|
||||
# item.total_allowance += allow_sum_custom
|
||||
# item.total_allowance += sum_customize_expect
|
||||
# item.total_deduction += -ded_sum_custom
|
||||
# item.total_deduction += -sum_except
|
||||
# item.total_net = item.total_allowance + item.total_deduction
|
||||
#
|
||||
# # filter salary_level,salary_group,salary_degree
|
||||
#
|
||||
#
|
||||
# # filter depend on salary_level
|
||||
#
|
||||
#
|
||||
# # filter depend on salary_group
|
||||
#
|
||||
#
|
||||
#
|
||||
# @api.depends('salary_degree')
|
||||
# def _get_amount(self):
|
||||
# for record in self:
|
||||
# record.transport_allowance_temp = record.transport_allowance * record.wage / 100 \
|
||||
# if record.transport_allowance_type == 'perc' else record.transport_allowance
|
||||
# record.house_allowance_temp = record.house_allowance * record.wage / 100 \
|
||||
# if record.house_allowance_type == 'perc' else record.house_allowance
|
||||
# record.communication_allowance_temp = record.communication_allowance * record.wage / 100 \
|
||||
# if record.communication_allowance_type == 'perc' else record.communication_allowance
|
||||
# record.field_allowance_temp = record.field_allowance * record.wage / 100 \
|
||||
# if record.field_allowance_type == 'perc' else record.field_allowance
|
||||
# record.special_allowance_temp = record.special_allowance * record.wage / 100 \
|
||||
# if record.special_allowance_type == 'perc' else record.special_allowance
|
||||
# record.other_allowance_temp = record.other_allowance * record.wage / 100 \
|
||||
# if record.other_allowance_type == 'perc' else record.other_allowance
|
||||
#
|
||||
# @api.depends('contractor_type.salary_type')
|
||||
# def compute_type(self):
|
||||
# if self.contractor_type.salary_type == 'scale':
|
||||
# self.hide = True
|
||||
# else:
|
||||
# self.hide = False
|
||||
|
||||
|
||||
class Advantages(models.Model):
|
||||
|
|
|
|||
|
|
@ -79,8 +79,6 @@ class HrSalaryRules(models.Model):
|
|||
if rec.category_id.rule_type != 'deduction' and rec.rules_type == 'insurnce':
|
||||
raise UserError(_("The Salary Rule is Not Deduction"))
|
||||
|
||||
# Override function compute rule in hr salary rule
|
||||
|
||||
def _compute_rule(self, localdict):
|
||||
payslip = localdict.get('payslip')
|
||||
contract = localdict.get('contract')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
from . import test_salary_scale
|
||||
from . import test_salary_rule_computation
|
||||
from . import test_employee_promotions
|
||||
from . import test_payroll_flow
|
||||
from . import test_employee_reward
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
from odoo.tests.common import TransactionCase, Form
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from datetime import date
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestEmployeePromotions(TransactionCase):
|
||||
|
||||
def setUp(cls):
|
||||
super(TestEmployeePromotions, cls).setUp()
|
||||
|
||||
|
||||
cls.structure_scale = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'General Scale',
|
||||
'type': 'scale',
|
||||
'code': 'SCL_TEST_01'
|
||||
})
|
||||
|
||||
cls.level_1 = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Level 1',
|
||||
'type': 'level',
|
||||
'salary_scale_id': cls.structure_scale.id,
|
||||
'code': 'LVL_TEST_01'
|
||||
})
|
||||
cls.group_A = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Group A',
|
||||
'type': 'group',
|
||||
'salary_scale_id': cls.structure_scale.id,
|
||||
'salary_scale_level_id': cls.level_1.id,
|
||||
'code': 'GRP_TEST_A'
|
||||
})
|
||||
cls.degree_1 = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Degree 1',
|
||||
'type': 'degree',
|
||||
'salary_scale_id': cls.structure_scale.id,
|
||||
'salary_scale_group_id': cls.group_A.id,
|
||||
'base_salary': 5000.0,
|
||||
'code': 'DEG_TEST_1'
|
||||
})
|
||||
|
||||
cls.level_2 = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Level 2',
|
||||
'type': 'level',
|
||||
'salary_scale_id': cls.structure_scale.id,
|
||||
'code': 'LVL_TEST_02'
|
||||
})
|
||||
cls.group_B = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Group B',
|
||||
'type': 'group',
|
||||
'salary_scale_id': cls.structure_scale.id,
|
||||
'salary_scale_level_id': cls.level_2.id,
|
||||
'code': 'GRP_TEST_B'
|
||||
})
|
||||
cls.degree_2 = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Degree 2',
|
||||
'type': 'degree',
|
||||
'salary_scale_id': cls.structure_scale.id,
|
||||
'salary_scale_group_id': cls.group_B.id,
|
||||
'base_salary': 7000.0,
|
||||
'code': 'DEG_TEST_2'
|
||||
})
|
||||
|
||||
cls.employee = cls.env['hr.employee'].create({
|
||||
'name': 'Test Employee',
|
||||
'salary_scale': cls.structure_scale.id,
|
||||
'salary_level': cls.level_1.id,
|
||||
'salary_group': cls.group_A.id,
|
||||
'salary_degree': cls.degree_1.id,
|
||||
})
|
||||
|
||||
cls.contract = cls.env['hr.contract'].create({
|
||||
'name': 'Test Contract',
|
||||
'employee_id': cls.employee.id,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
'salary_level': cls.level_1.id,
|
||||
'salary_group': cls.group_A.id,
|
||||
'salary_degree': cls.degree_1.id,
|
||||
})
|
||||
|
||||
cls.employee.contract_id = cls.contract
|
||||
|
||||
def test_01_promotion_workflow_full_cycle(self):
|
||||
|
||||
promotion_form = Form(self.env['employee.promotions'])
|
||||
promotion_form.date = date.today()
|
||||
promotion_form.employee_id = self.employee
|
||||
|
||||
self.assertEqual(promotion_form.old_degree, self.degree_1, "Should auto-fill old degree from employee")
|
||||
|
||||
promotion_form.new_level = self.level_2
|
||||
promotion_form.new_group = self.group_B
|
||||
promotion_form.new_degree = self.degree_2
|
||||
|
||||
promotion = promotion_form.save()
|
||||
|
||||
promotion.confirm()
|
||||
self.assertEqual(promotion.state, 'confirm')
|
||||
|
||||
promotion.hr_manager()
|
||||
self.assertEqual(promotion.state, 'hr_manager')
|
||||
|
||||
promotion.approved()
|
||||
self.assertEqual(promotion.state, 'approved')
|
||||
|
||||
|
||||
self.assertEqual(self.employee.contract_id.salary_degree, self.degree_2,
|
||||
"Contract degree should be updated to new degree")
|
||||
self.assertEqual(self.employee.contract_id.salary, 7000.0,
|
||||
"Contract salary should be updated to new base salary")
|
||||
|
||||
def test_02_redraft_reverts_values(self):
|
||||
|
||||
promotion = self.env['employee.promotions'].create({
|
||||
'date': date.today(),
|
||||
'employee_id': self.employee.id,
|
||||
'old_degree': self.degree_1.id,
|
||||
'old_level_2': self.level_1.id,
|
||||
'old_group_2': self.group_A.id,
|
||||
'old_degree_2': self.degree_1.id,
|
||||
'new_degree': self.degree_2.id,
|
||||
'new_level': self.level_2.id,
|
||||
'new_group': self.group_B.id,
|
||||
})
|
||||
|
||||
promotion.approved()
|
||||
self.assertEqual(self.employee.contract_id.salary_degree, self.degree_2)
|
||||
|
||||
promotion.re_draft()
|
||||
|
||||
self.assertEqual(promotion.state, 'draft')
|
||||
self.assertEqual(self.employee.contract_id.salary_degree, self.degree_1,
|
||||
"Should revert to old degree on re-draft")
|
||||
|
||||
def test_03_unlink_restriction(self):
|
||||
promotion = self.env['employee.promotions'].create({
|
||||
'date': date.today(),
|
||||
'employee_id': self.employee.id,
|
||||
'state': 'confirm'
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
promotion.unlink()
|
||||
|
||||
promotion.state = 'draft'
|
||||
promotion.unlink()
|
||||
self.assertFalse(promotion.exists())
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from datetime import date
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestEmployeeReward(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestEmployeeReward, self).setUp()
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee',
|
||||
})
|
||||
|
||||
self.account_debit = self.env['account.account'].create({
|
||||
'name': 'Debit Account',
|
||||
'code': '100001',
|
||||
'account_type': 'expense',
|
||||
'reconcile': True,
|
||||
})
|
||||
|
||||
self.account_credit = self.env['account.account'].create({
|
||||
'name': 'Credit Account',
|
||||
'code': '200001',
|
||||
'account_type': 'liability_payable',
|
||||
'reconcile': True,
|
||||
})
|
||||
|
||||
self.journal = self.env['account.journal'].create({
|
||||
'name': 'Reward Journal',
|
||||
'type': 'general',
|
||||
'code': 'REW',
|
||||
'default_account_id': self.account_credit.id,
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Contract for Test',
|
||||
'employee_id': self.employee.id,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
def test_01_reward_workflow_and_calculation(self):
|
||||
|
||||
reward = self.env['hr.employee.reward'].create({
|
||||
'allowance_reason': 'Excellent Performance',
|
||||
'date': date.today(),
|
||||
'reward_type': 'amount',
|
||||
'amount': 1000.0,
|
||||
'transfer_type': 'accounting',
|
||||
'account_id': self.account_debit.id,
|
||||
'journal_id': self.journal.id,
|
||||
})
|
||||
|
||||
reward_line = self.env['lines.ids.reward'].create({
|
||||
'employee_reward_id': reward.id,
|
||||
'employee_id': self.employee.id,
|
||||
'percentage': 50.0,
|
||||
})
|
||||
|
||||
self.assertEqual(reward_line.amount, 500.0, "Amount calculation is wrong based on percentage")
|
||||
|
||||
|
||||
reward.action_submit()
|
||||
self.assertEqual(reward.state, 'submitted', "State should be submitted")
|
||||
self.assertEqual(reward_line.reward_state, 'submitted', "Line state should match parent")
|
||||
|
||||
reward.action_hrm()
|
||||
self.assertEqual(reward.state, 'hrm', "State should be hrm")
|
||||
|
||||
reward.action_done()
|
||||
self.assertEqual(reward.state, 'done', "State should be done")
|
||||
|
||||
self.assertTrue(reward_line.move_id, "Journal Entry should be created")
|
||||
self.assertEqual(reward_line.move_id.state, 'draft', "Move should be created in draft")
|
||||
|
||||
move_lines = reward_line.move_id.line_ids
|
||||
debit_line = move_lines.filtered(lambda l: l.debit > 0)
|
||||
credit_line = move_lines.filtered(lambda l: l.credit > 0)
|
||||
|
||||
self.assertEqual(debit_line.account_id, self.account_debit, "Debit account mismatch")
|
||||
self.assertEqual(credit_line.account_id, self.account_credit, "Credit account mismatch")
|
||||
self.assertEqual(debit_line.debit, 500.0, "Debit amount incorrect")
|
||||
|
||||
def test_02_constraint_reward_once_yearly(self):
|
||||
|
||||
reward_1 = self.env['hr.employee.reward'].create({
|
||||
'allowance_reason': 'First Reward',
|
||||
'date': date.today(),
|
||||
'reward_type': 'amount',
|
||||
'amount': 1000.0,
|
||||
'reward_once': True,
|
||||
'transfer_type': 'accounting',
|
||||
'account_id': self.account_debit.id,
|
||||
'journal_id': self.journal.id,
|
||||
})
|
||||
self.env['lines.ids.reward'].create({
|
||||
'employee_reward_id': reward_1.id,
|
||||
'employee_id': self.employee.id,
|
||||
'percentage': 100.0,
|
||||
})
|
||||
|
||||
reward_1.action_submit()
|
||||
reward_1.action_hrm()
|
||||
reward_1.action_done()
|
||||
|
||||
reward_2 = self.env['hr.employee.reward'].create({
|
||||
'allowance_reason': 'Second Reward',
|
||||
'date': date.today(),
|
||||
'reward_type': 'amount',
|
||||
'amount': 500.0,
|
||||
'reward_once': True,
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
self.env['lines.ids.reward'].create({
|
||||
'employee_reward_id': reward_2.id,
|
||||
'employee_id': self.employee.id,
|
||||
'percentage': 100.0,
|
||||
})
|
||||
|
||||
def test_03_positive_amount_check(self):
|
||||
reward = self.env['hr.employee.reward'].create({
|
||||
'allowance_reason': 'Negative Test',
|
||||
'date': date.today(),
|
||||
'amount': 100.0,
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
reward.amount = -50.0
|
||||
reward.chick_amount_positive()
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase, Form
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import tagged
|
||||
from datetime import date, datetime, timedelta
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class TestPayrollAdvanceFlow(TransactionCase):
|
||||
|
||||
def setUp(cls):
|
||||
super(TestPayrollAdvanceFlow, cls).setUp()
|
||||
|
||||
cls.company = cls.env.company
|
||||
|
||||
cls.account_salary = cls.env['account.account'].create({
|
||||
'name': 'Basic Salary Account',
|
||||
'code': '600001',
|
||||
'account_type': 'expense',
|
||||
'reconcile': True,
|
||||
})
|
||||
|
||||
cls.account_payable = cls.env['account.account'].create({
|
||||
'name': 'Salaries Payable',
|
||||
'code': '200001',
|
||||
'account_type': 'liability_payable',
|
||||
'reconcile': True,
|
||||
})
|
||||
|
||||
cls.journal = cls.env['account.journal'].create({
|
||||
'name': 'Salary Journal',
|
||||
'type': 'general',
|
||||
'code': 'SAL',
|
||||
'default_account_id': cls.account_payable.id,
|
||||
})
|
||||
|
||||
cls.rule_basic = cls.env['hr.salary.rule'].create({
|
||||
'name': 'Basic Salary',
|
||||
'sequence': 1,
|
||||
'code': 'BASIC',
|
||||
'category_id': cls.env.ref('exp_hr_payroll.ALW').id,
|
||||
'condition_select': 'none',
|
||||
'amount_select': 'code',
|
||||
'amount_python_compute': 'result = contract.wage',
|
||||
'rule_debit_account_id': cls.account_salary.id,
|
||||
})
|
||||
|
||||
cls.rule_net = cls.env['hr.salary.rule'].create({
|
||||
'name': 'Net Salary',
|
||||
'sequence': 100,
|
||||
'code': 'NET',
|
||||
'category_id': cls.env.ref('exp_hr_payroll.DED').id,
|
||||
'condition_select': 'none',
|
||||
'amount_select': 'code',
|
||||
'amount_python_compute': 'result = categories.BASIC + categories.ALW + categories.DED',
|
||||
'rule_credit_account_id': cls.account_payable.id,
|
||||
})
|
||||
|
||||
cls.structure = cls.env['hr.payroll.structure'].create({
|
||||
'name': 'Standard Structure',
|
||||
'type': 'scale',
|
||||
'code': 'STRUCT_001',
|
||||
'rule_ids': [(4, cls.rule_basic.id), (4, cls.rule_net.id)],
|
||||
'transfer_type': 'one_by_one',
|
||||
})
|
||||
|
||||
cls.employee = cls.env['hr.employee'].create({
|
||||
'name': 'Test Employee Payroll',
|
||||
'first_hiring_date': date.today() - relativedelta(years=1),
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
cls.contract = cls.env['hr.contract'].create({
|
||||
'name': 'Contract For Test',
|
||||
'employee_id': cls.employee.id,
|
||||
'state': 'program_directory',
|
||||
'wage': 5000.0,
|
||||
'salary_scale': cls.structure.id,
|
||||
'journal_id': cls.journal.id,
|
||||
'date_start': date.today() - relativedelta(years=1),
|
||||
})
|
||||
def test_01_payslip_compute_and_transfer(self):
|
||||
|
||||
date_from = date.today().replace(day=1)
|
||||
date_to = date.today() + relativedelta(months=+1, day=1, days=-1)
|
||||
|
||||
payslip = self.env['hr.payslip'].create({
|
||||
'name': 'Test Payslip',
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date_from,
|
||||
'date_to': date_to,
|
||||
'contract_id': self.contract.id,
|
||||
'struct_id': self.structure.id
|
||||
})
|
||||
|
||||
payslip.compute_sheet()
|
||||
|
||||
self.assertEqual(payslip.state, 'computed', "State should be 'computed' after computing sheet")
|
||||
|
||||
basic_line = payslip.line_ids.filtered(lambda l: l.code == 'BASIC')
|
||||
self.assertEqual(basic_line.total, 5000.0, "Basic salary should be 5000")
|
||||
|
||||
payslip.compute_totals()
|
||||
self.assertEqual(payslip.total_allowances, 5000.0, "Total allowances should be calculated correctly")
|
||||
|
||||
payslip.confirm()
|
||||
self.assertEqual(payslip.state, 'confirmed')
|
||||
|
||||
payslip.transfer()
|
||||
self.assertEqual(payslip.state, 'transfered')
|
||||
self.assertTrue(payslip.move_id, "Journal Entry should be created")
|
||||
self.assertEqual(payslip.move_id.state, 'draft', "Move should be created in draft state initially")
|
||||
|
||||
def test_02_payslip_loans_integration(self):
|
||||
|
||||
payslip = self.env['hr.payslip'].create({
|
||||
'name': 'Loan Payslip',
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date.today().replace(day=1),
|
||||
'date_to': date.today() + relativedelta(months=+1, day=1, days=-1),
|
||||
})
|
||||
|
||||
self.env['payslip.loans'].create({
|
||||
'payslip_loan': payslip.id,
|
||||
'name': 'Car Loan',
|
||||
'code': 'LOAN01',
|
||||
'amount': 500.0,
|
||||
'date': date.today(),
|
||||
'account_id': self.account_payable.id,
|
||||
})
|
||||
|
||||
payslip.compute_totals()
|
||||
self.assertEqual(payslip.total_loans, 500.0, "Total loans field should calculate sum of loan lines")
|
||||
|
||||
|
||||
self.assertEqual(payslip.total_sum, 500.0, "Total sum logic check (depends on allowances setup)")
|
||||
|
||||
def test_03_payslip_run_batch_process(self):
|
||||
|
||||
date_start = date.today().replace(day=1)
|
||||
date_end = date.today() + relativedelta(months=+1, day=1, days=-1)
|
||||
|
||||
payslip_run = self.env['hr.payslip.run'].create({
|
||||
'name': 'Monthly Run',
|
||||
'date_start': date_start,
|
||||
'date_end': date_end,
|
||||
'salary_scale': self.structure.id,
|
||||
})
|
||||
|
||||
payslip_run.check_date_start()
|
||||
self.assertEqual(payslip_run.date_end, date_end)
|
||||
|
||||
payslip_run.compute_sheet()
|
||||
|
||||
self.assertTrue(payslip_run.slip_ids, "Payslips should be generated for eligible employees")
|
||||
generated_slip = payslip_run.slip_ids[0]
|
||||
self.assertEqual(generated_slip.employee_id, self.employee)
|
||||
self.assertEqual(generated_slip.state, 'computed')
|
||||
|
||||
payslip_run.confirm()
|
||||
self.assertEqual(generated_slip.state, 'confirmed')
|
||||
|
||||
|
||||
payslip_run.transfer()
|
||||
self.assertEqual(payslip_run.state, 'transfered')
|
||||
self.assertTrue(payslip_run.move_id or generated_slip.move_id, "Accounting move should be generated")
|
||||
|
||||
def test_04_payslip_withdraw_and_reset(self):
|
||||
|
||||
payslip = self.env['hr.payslip'].create({
|
||||
'name': 'Withdraw Test',
|
||||
'employee_id': self.employee.id,
|
||||
'date_from': date.today(),
|
||||
'date_to': date.today(),
|
||||
})
|
||||
payslip.compute_sheet()
|
||||
payslip.confirm()
|
||||
|
||||
payslip.withdraw()
|
||||
|
||||
self.assertEqual(payslip.state, 'draft', "State should return to draft after withdraw")
|
||||
self.assertFalse(payslip.move_id, "Account move should be unlinked/deleted")
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import fields
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
class TestSalaryRuleComputation(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSalaryRuleComputation, self).setUp()
|
||||
|
||||
self.employee = self.env['hr.employee'].create({'name': 'Test Employee'})
|
||||
|
||||
self.category_basic = self.env['hr.salary.rule.category'].create({
|
||||
'name': 'Basic', 'code': 'BASIC', 'rule_type': 'allowance'
|
||||
})
|
||||
self.category_allowance = self.env['hr.salary.rule.category'].create({
|
||||
'name': 'Allowance', 'code': 'ALW', 'rule_type': 'allowance'
|
||||
})
|
||||
|
||||
self.structure = self.env['hr.payroll.structure'].create({
|
||||
'name': 'Test Structure', 'code': 'TEST_STRUCT', 'type': 'scale', 'parent_id': False
|
||||
})
|
||||
|
||||
|
||||
self.rule_basic = self.env['hr.salary.rule'].create({
|
||||
'name': 'Basic Salary', 'code': 'BASIC',
|
||||
'category_id': self.category_basic.id,
|
||||
'amount_select': 'fix',
|
||||
'fixed_amount': 1000,
|
||||
'sequence': 1,
|
||||
'salary_type': 'fixed',
|
||||
})
|
||||
|
||||
self.rule_housing = self.env['hr.salary.rule'].create({
|
||||
'name': 'Housing Allowance', 'code': 'HOUSING',
|
||||
'category_id': self.category_allowance.id,
|
||||
'amount_select': 'fix',
|
||||
'fixed_amount': 500,
|
||||
'sequence': 2,
|
||||
'salary_type': 'fixed',
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Contract Test',
|
||||
'employee_id': self.employee.id,
|
||||
'salary_scale': self.structure.id,
|
||||
'salary': 5000.0,
|
||||
'wage': 5000.0,
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
def test_01_compute_fixed_rule(self):
|
||||
localdict = {'contract': self.contract, 'employee': self.employee}
|
||||
|
||||
amount, qty, rate = self.rule_basic._compute_rule(localdict)
|
||||
self.assertEqual(amount, 1000.0, "Basic salary fixed amount should be 1000")
|
||||
|
||||
def test_02_compute_percentage_with_related_rules(self):
|
||||
|
||||
rule_percent = self.env['hr.salary.rule'].create({
|
||||
'name': 'Social Security', 'code': 'SOC',
|
||||
'category_id': self.category_allowance.id,
|
||||
'amount_select': 'percentage',
|
||||
'amount_percentage': 10.0,
|
||||
'quantity': '1.0',
|
||||
'related_benefits_discounts': [(6, 0, [self.rule_basic.id, self.rule_housing.id])],
|
||||
'salary_type': 'fixed',
|
||||
})
|
||||
|
||||
localdict = {'contract': self.contract, 'employee': self.employee}
|
||||
|
||||
amount, qty, rate = rule_percent._compute_rule(localdict)
|
||||
|
||||
self.assertEqual(amount, 150.0, "Percentage calculation failed. Expected 150.0")
|
||||
|
||||
def test_03_percentage_with_exception_advantage(self):
|
||||
|
||||
today = fields.Date.today()
|
||||
yesterday = today - timedelta(days=1)
|
||||
self.env['contract.advantage'].create({
|
||||
'contract_advantage_id': self.contract.id,
|
||||
'employee_id': self.employee.id,
|
||||
'benefits_discounts': self.rule_basic.id,
|
||||
'type': 'exception',
|
||||
|
||||
'amount': 200,
|
||||
'date_from': yesterday,
|
||||
'date_to': today + timedelta(days=30),
|
||||
})
|
||||
self.contract.invalidate_recordset(['advantages'])
|
||||
rule_percent = self.env['hr.salary.rule'].create({
|
||||
'name': 'Social Security Modified', 'code': 'SOC_MOD',
|
||||
'category_id': self.category_allowance.id,
|
||||
'amount_select': 'percentage',
|
||||
'amount_percentage': 10.0,
|
||||
'quantity': '1.0',
|
||||
'related_benefits_discounts': [(6, 0, [self.rule_basic.id, self.rule_housing.id])],
|
||||
'salary_type': 'fixed',
|
||||
})
|
||||
|
||||
localdict = {'contract': self.contract, 'employee': self.employee}
|
||||
amount, qty, rate = rule_percent._compute_rule(localdict)
|
||||
|
||||
self.assertEqual(amount, 130.0, f"Exception logic failed. Expected 130.0, Got {amount}")
|
||||
|
||||
def test_04_date_range_validation(self):
|
||||
|
||||
old_date = fields.Date.today() - timedelta(days=365)
|
||||
|
||||
self.env['contract.advantage'].create({
|
||||
'contract_advantage_id': self.contract.id,
|
||||
'employee_id': self.employee.id,
|
||||
'benefits_discounts': self.rule_basic.id,
|
||||
'type': 'exception',
|
||||
'amount': 200,
|
||||
'date_from': old_date,
|
||||
'date_to': old_date + timedelta(days=30),
|
||||
})
|
||||
|
||||
rule_percent = self.env['hr.salary.rule'].create({
|
||||
'name': 'Social Security Date', 'code': 'SOC_DATE',
|
||||
'category_id': self.category_allowance.id,
|
||||
'amount_select': 'percentage',
|
||||
'amount_percentage': 10.0,
|
||||
'quantity': '1.0',
|
||||
'related_benefits_discounts': [(6, 0, [self.rule_basic.id, self.rule_housing.id])],
|
||||
'salary_type': 'fixed',
|
||||
})
|
||||
|
||||
localdict = {'contract': self.contract, 'employee': self.employee}
|
||||
amount, qty, rate = rule_percent._compute_rule(localdict)
|
||||
|
||||
self.assertEqual(amount, 150.0, "Date validation failed. Expired exception should be ignored.")
|
||||
|
||||
def test_05_salary_type_related_levels(self):
|
||||
|
||||
level_1 = self.env['hr.payroll.structure'].create({
|
||||
'name': 'Level 1', 'code': 'LVL1', 'type': 'level', 'parent_id': False
|
||||
})
|
||||
self.contract.salary_level = level_1.id
|
||||
|
||||
|
||||
rule_level = self.env['hr.salary.rule'].create({
|
||||
'name': 'Level Rule', 'code': 'LVL_RULE',
|
||||
'category_id': self.category_basic.id,
|
||||
'amount_select': 'fix',
|
||||
'salary_type': 'related_levels',
|
||||
})
|
||||
|
||||
self.env['related.salary.amount'].create({
|
||||
'salary_rule_id': rule_level.id,
|
||||
'salary_scale_level': level_1.id,
|
||||
'salary': 2500.0,
|
||||
})
|
||||
rule_level.invalidate_recordset(['salary_amount_ids'])
|
||||
self.contract.invalidate_recordset(['salary_level'])
|
||||
localdict = {'contract': self.contract, 'employee': self.employee}
|
||||
amount, qty, rate = rule_level._compute_rule(localdict)
|
||||
|
||||
self.assertEqual(amount, 2500.0, "Related Level salary calculation failed.")
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
from odoo.tests.common import TransactionCase
|
||||
from odoo import fields
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class TestHrContractSalaryScale(TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Test Employee',
|
||||
})
|
||||
|
||||
allow_cat = self.env['hr.salary.rule.category'].create({
|
||||
'name': 'Allowance',
|
||||
'code': 'ALW',
|
||||
'rule_type': 'allowance'
|
||||
})
|
||||
ded_cat = self.env['hr.salary.rule.category'].create({
|
||||
'name': 'Deduction',
|
||||
'code': 'DED',
|
||||
'rule_type': 'deduction'
|
||||
})
|
||||
|
||||
self.structure = self.env['hr.payroll.structure'].create({
|
||||
'name': 'Scale Structure',
|
||||
'type': 'scale',
|
||||
'code': 'SCALE_TEST',
|
||||
'parent_id': False,
|
||||
})
|
||||
|
||||
self.allow_rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Allowance Rule',
|
||||
'code': 'ALLOW1',
|
||||
'category_id': allow_cat.id,
|
||||
'amount_select': 'fix',
|
||||
'amount_fix': 1000,
|
||||
'quantity': '1.0',
|
||||
'condition_select': 'none'
|
||||
})
|
||||
|
||||
self.ded_rule = self.env['hr.salary.rule'].create({
|
||||
'name': 'Deduction Rule',
|
||||
'code': 'DED1',
|
||||
'category_id': ded_cat.id,
|
||||
'amount_select': 'fix',
|
||||
'amount_fix': 200,
|
||||
'quantity': '1.0',
|
||||
'condition_select': 'none'
|
||||
})
|
||||
|
||||
self.structure.rule_ids = [(6, 0, [self.allow_rule.id, self.ded_rule.id])]
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Test Contract',
|
||||
'employee_id': self.employee.id,
|
||||
'salary_scale': self.structure.id,
|
||||
'salary': 5000,
|
||||
})
|
||||
|
||||
def test_compute_function_basic(self):
|
||||
|
||||
self.contract.compute_function()
|
||||
|
||||
|
||||
self.assertEqual(self.contract.total_allowance, 1000.0)
|
||||
def test_salary_group_constraint(self):
|
||||
group = self.env['hr.payroll.structure'].create({
|
||||
'name': 'Group A',
|
||||
'gread_min': 3000,
|
||||
'gread_max': 6000,
|
||||
'code': 'SCALE_TEST'
|
||||
|
||||
})
|
||||
|
||||
self.contract.salary_group = group.id
|
||||
self.contract.salary = 5000
|
||||
|
||||
def test_compute_move_type_one_by_one(self):
|
||||
self.structure.transfer_type = 'one_by_one'
|
||||
self.contract.salary_scale = self.structure.id
|
||||
|
||||
self.contract.compute_move_type()
|
||||
|
||||
self.assertTrue(self.contract.required_condition)
|
||||
|
||||
def test_exception_amount_greater_than_rule(self):
|
||||
rule = self.allow_rule
|
||||
|
||||
advantage = self.env['contract.advantage'].create({
|
||||
'contract_advantage_id': self.contract.id,
|
||||
'benefits_discounts': rule.id,
|
||||
'type': 'exception',
|
||||
'amount': 2000,
|
||||
'date_from': fields.Date.today(),
|
||||
})
|
||||
|
|
@ -163,10 +163,8 @@ class Employee(models.Model):
|
|||
employee.current_leave_state = leave_data.get(employee.id, {}).get('current_leave_state')
|
||||
employee.current_leave_id = leave_data.get(employee.id, {}).get('current_leave_id')
|
||||
# Assign is_absent for compatibility with standard hr_holidays module
|
||||
# employee.is_absent_today = leave_data.get(employee.id) and leave_data.get(employee.id).get('current_leave_state') == 'validate'
|
||||
is_absent = leave_data.get(employee.id) and leave_data.get(employee.id).get('current_leave_state') == 'validate'
|
||||
employee.is_absent = leave_data.get(employee.id) and leave_data.get(employee.id).get('current_leave_state') == 'validate'
|
||||
|
||||
employee.is_absent = is_absent
|
||||
def _compute_leaves_count(self):
|
||||
leaves = self.env['hr.holidays'].read_group([
|
||||
('employee_id', 'in', self.ids),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import ValidationError,UserError
|
||||
|
||||
|
||||
class HRHolidays(models.Model):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import calendar
|
|||
from dateutil.relativedelta import relativedelta
|
||||
from odoo.tools.translate import _
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError ,ValidationError
|
||||
|
||||
|
||||
class ReturnFromLeave(models.Model):
|
||||
|
|
@ -73,7 +73,7 @@ class ReturnFromLeave(models.Model):
|
|||
def _chick_leave_type(self):
|
||||
for rec in self:
|
||||
if rec.leave_request_id.holiday_status_id.leave_type == 'annual' and rec.decision == 'other':
|
||||
raise exceptions.ValidationError(_("Sorry Cannot be Create an Annual Leave from the same annual Leave"))
|
||||
raise ValidationError(_("Sorry Cannot be Create an Annual Leave from the same annual Leave"))
|
||||
|
||||
@api.depends('leave_request_id')
|
||||
def _compute_dates_of_leave(self):
|
||||
|
|
@ -132,7 +132,7 @@ class ReturnFromLeave(models.Model):
|
|||
else:
|
||||
request.diff_days = len(list(set([xd for xd in exceeded_dates if xd not in event_dates and xd not in wkns_dates])))
|
||||
else:
|
||||
raise exceptions.ValidationError(_("Sorry this leave ends by %s.\n"
|
||||
raise ValidationError(_("Sorry this leave ends by %s.\n"
|
||||
"If you plan for an early return kindly apply for leave "
|
||||
"cancellation.") % request.leave_request_id.date_to)
|
||||
else:
|
||||
|
|
@ -155,7 +155,7 @@ class ReturnFromLeave(models.Model):
|
|||
self.settling_leave_id.draft_state()
|
||||
self.settling_leave_id.unlink()
|
||||
else:
|
||||
raise exceptions.ValidationError(_("Sorry The link leave cannot be deleted %s After approved")
|
||||
raise ValidationError(_("Sorry The link leave cannot be deleted %s After approved")
|
||||
% self.settling_leave_id.holiday_status_id.name)
|
||||
self.state = 'draft'
|
||||
self.leave_request_id.return_from_leave = False
|
||||
|
|
@ -166,14 +166,14 @@ class ReturnFromLeave(models.Model):
|
|||
request_id = rec.leave_request_id
|
||||
if rec.decision == 'law': # create unpaid leave
|
||||
if not request_id.holiday_status_id.unpaid_holiday_id:
|
||||
raise exceptions.ValidationError(_("Sorry no unpaid leave is defined for %s leave kindly set one")
|
||||
raise ValidationError(_("Sorry no unpaid leave is defined for %s leave kindly set one")
|
||||
% request_id.holiday_status_id.name)
|
||||
status_id = request_id.holiday_status_id.unpaid_holiday_id.id
|
||||
elif rec.decision == 'deduct': # Deduct from leave balance
|
||||
status_id = request_id.holiday_status_id.id
|
||||
elif rec.decision == 'other': # create annual leave
|
||||
if not request_id.holiday_status_id.annual_holiday_id:
|
||||
raise exceptions.ValidationError(_("Sorry no annual leave is defined for %s leave kindly set one")
|
||||
raise ValidationError(_("Sorry no annual leave is defined for %s leave kindly set one")
|
||||
% request_id.holiday_status_id.name)
|
||||
status_id = request_id.holiday_status_id.annual_holiday_id.id
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ class ReturnFromLeave(models.Model):
|
|||
('check_allocation_view', '=', 'balance')
|
||||
], order='id desc', limit=1).remaining_leaves or 0.0
|
||||
if balance < rec.diff_days:
|
||||
raise exceptions.ValidationError(
|
||||
raise ValidationError(
|
||||
_("Sorry your %s leave balance it is not enough to deduct from it, The balance is %s.")
|
||||
% (request_id.holiday_status_id.name, round(balance, 2)))
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ class ReturnFromLeave(models.Model):
|
|||
if self.decision == 'deduct':
|
||||
self.settling_leave_id.financial_manager()
|
||||
elif self.leave_request_id.state != 'validate1':
|
||||
raise exceptions.ValidationError(
|
||||
raise ValidationError(
|
||||
_("Sorry %s leave is not approved yet. kindly approve it first") % (
|
||||
self.leave_request_id.display_name))
|
||||
self.leave_request_id.remove_delegated_access()
|
||||
|
|
@ -240,7 +240,7 @@ class ReturnFromLeave(models.Model):
|
|||
leave.settling_leave_id.draft_state()
|
||||
leave.settling_leave_id.unlink()
|
||||
else:
|
||||
raise exceptions.ValidationError(_("Sorry The link leave cannot be deleted %s After approved")
|
||||
raise ValidationError(_("Sorry The link leave cannot be deleted %s After approved")
|
||||
% leave.settling_leave_id.holiday_status_id.name)
|
||||
self.state = 'refuse'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from . import test_hr_holidays_custom
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tests import tagged
|
||||
from datetime import date, timedelta, datetime
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestHRHolidaysCustom(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHRHolidaysCustom, self).setUp()
|
||||
|
||||
self.employee = self.env['hr.employee'].create({
|
||||
'name': 'Employee Test',
|
||||
'gender': 'male',
|
||||
'first_hiring_date': date.today() - timedelta(days=365 * 2),
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
self.replacement_employee = self.env['hr.employee'].create({
|
||||
'name': 'Replacement Employee',
|
||||
'gender': 'male',
|
||||
'first_hiring_date': date.today() - timedelta(days=365),
|
||||
'state': 'open',
|
||||
})
|
||||
|
||||
self.contract = self.env['hr.contract'].create({
|
||||
'name': 'Contract Test',
|
||||
'employee_id': self.employee.id,
|
||||
'wage': 5000,
|
||||
'state': 'open',
|
||||
'date_start': date.today() - timedelta(days=365 * 2),
|
||||
'emp_type': 'saudi', # ضروري لفلتر السكيجوال
|
||||
})
|
||||
self.employee.contract_id = self.contract
|
||||
|
||||
self.replacement_contract = self.env['hr.contract'].create({
|
||||
'name': 'Replacement Contract',
|
||||
'employee_id': self.replacement_employee.id,
|
||||
'wage': 4000,
|
||||
'state': 'open',
|
||||
'date_start': date.today() - timedelta(days=365),
|
||||
'emp_type': 'saudi',
|
||||
})
|
||||
self.replacement_employee.contract_id = self.replacement_contract
|
||||
|
||||
self.holiday_status = self.env['hr.holidays.status'].create({
|
||||
'name': 'Annual Leave Test',
|
||||
'leave_type': 'annual',
|
||||
'limit': False,
|
||||
'number_of_days': 30,
|
||||
'active': True,
|
||||
'alternative_days': 2,
|
||||
'alternative_chick': False,
|
||||
})
|
||||
|
||||
self.env['hr.holidays'].create({
|
||||
'name': 'Balance Record',
|
||||
'holiday_status_id': self.holiday_status.id,
|
||||
'employee_id': self.employee.id,
|
||||
'type': 'add',
|
||||
'check_allocation_view': 'balance',
|
||||
'remaining_leaves': 30.0,
|
||||
'state': 'validate',
|
||||
})
|
||||
|
||||
self.env['hr.holidays'].create({
|
||||
'name': 'Replacement Balance',
|
||||
'holiday_status_id': self.holiday_status.id,
|
||||
'employee_id': self.replacement_employee.id,
|
||||
'type': 'add',
|
||||
'check_allocation_view': 'balance',
|
||||
'remaining_leaves': 10.0,
|
||||
'state': 'validate',
|
||||
})
|
||||
|
||||
def test_01_leave_workflow_and_ticket_creation(self):
|
||||
leave_request = self.env['hr.holidays'].create({
|
||||
'name': 'Leave Request with Ticket',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.holiday_status.id,
|
||||
'date_from': datetime.now().strftime('%Y-%m-%d 08:00:00'),
|
||||
'date_to': (datetime.now() + timedelta(days=5)).strftime('%Y-%m-%d 17:00:00'),
|
||||
'number_of_days_temp': 5,
|
||||
'type': 'remove',
|
||||
'check_allocation_view': 'allocation',
|
||||
'issuing_ticket': 'yes',
|
||||
'ticket_cash_request_for': 'employee',
|
||||
})
|
||||
|
||||
leave_request.confirm()
|
||||
leave_request.hr_manager()
|
||||
leave_request.approved()
|
||||
leave_request.financial_manager()
|
||||
|
||||
self.assertTrue(leave_request.request_done)
|
||||
ticket = self.env['hr.ticket.request'].search([('leave_request_id', '=', leave_request.id)])
|
||||
self.assertTrue(ticket)
|
||||
|
||||
def test_02_replacement_employee_constraint(self):
|
||||
today = datetime.now()
|
||||
self.env['hr.holidays'].create({
|
||||
'name': 'Replacement Employee Leave',
|
||||
'employee_id': self.replacement_employee.id,
|
||||
'holiday_status_id': self.holiday_status.id,
|
||||
'date_from': today.strftime('%Y-%m-%d 08:00:00'),
|
||||
'date_to': (today + timedelta(days=2)).strftime('%Y-%m-%d 17:00:00'),
|
||||
'type': 'remove',
|
||||
'state': 'validate1',
|
||||
})
|
||||
|
||||
with self.assertRaises(UserError):
|
||||
self.env['hr.holidays'].create({
|
||||
'name': 'Main Employee Leave',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.holiday_status.id,
|
||||
'date_from': today.strftime('%Y-%m-%d 08:00:00'),
|
||||
'date_to': (today + timedelta(days=2)).strftime('%Y-%m-%d 17:00:00'),
|
||||
'type': 'remove',
|
||||
'replace_by': self.replacement_employee.id,
|
||||
})
|
||||
|
||||
def test_03_check_balance_limit(self):
|
||||
with self.assertRaises(UserError):
|
||||
leave = self.env['hr.holidays'].create({
|
||||
'name': 'Exceed Balance Leave',
|
||||
'employee_id': self.employee.id,
|
||||
'holiday_status_id': self.holiday_status.id,
|
||||
'date_from': datetime.now().strftime('%Y-%m-%d 08:00:00'),
|
||||
'date_to': (datetime.now() + timedelta(days=40)).strftime('%Y-%m-%d 17:00:00'),
|
||||
'number_of_days_temp': 41,
|
||||
'type': 'remove',
|
||||
})
|
||||
leave._check_number_of_days()
|
||||
|
||||
def test_04_scheduler_queue_allocation(self):
|
||||
|
||||
monthly_leave_type = self.env['hr.holidays.status'].create({
|
||||
'name': 'Monthly Leave',
|
||||
'leave_type': 'annual',
|
||||
'balance_type': 'monthly',
|
||||
'leave_annual_type': 'open_balance',
|
||||
'company_id': self.env.company.id,
|
||||
'emp_type': 'all',
|
||||
'alternative_days': 2,
|
||||
'alternative_chick': False,
|
||||
'number_of_days': 0,
|
||||
'duration_ids': [(0, 0, {
|
||||
'name': 'Level 1',
|
||||
'date_from': 0,
|
||||
'date_to': 10,
|
||||
'duration': 30
|
||||
})]
|
||||
})
|
||||
|
||||
|
||||
for i in range(2):
|
||||
self.env['hr.holidays'].create({
|
||||
'name': f'Dummy Allocation {i}',
|
||||
'holiday_status_id': monthly_leave_type.id,
|
||||
'employee_id': self.replacement_employee.id,
|
||||
'type': 'add',
|
||||
'check_allocation_view': 'balance',
|
||||
'number_of_days_temp': 0,
|
||||
'state': 'confirm'
|
||||
})
|
||||
|
||||
self.env['hr.holidays'].process_holidays_scheduler_queue()
|
||||
|
||||
allocation = self.env['hr.holidays'].search([
|
||||
('employee_id', '=', self.employee.id),
|
||||
('holiday_status_id', '=', monthly_leave_type.id),
|
||||
('check_allocation_view', '=', 'balance'),
|
||||
('type', '=', 'add')
|
||||
])
|
||||
|
||||
self.assertTrue(allocation, "Scheduler should create allocation record")
|
||||
self.assertGreater(allocation.remaining_leaves, 0.0)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg
|
||||
:target: https://www.gnu.org/licenses/lgpl-3.0-standalone.html
|
||||
:alt: License: LGPL-3
|
||||
|
||||
Advanced HR-LinkedIn Integration
|
||||
================================
|
||||
* Advanced HR-LinkedIn Integration module for Odoo 17.
|
||||
|
||||
Installation
|
||||
============
|
||||
* Install external python packages python-linkedin and mechanize.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
* Mention the LinkedIn username and password in the section
|
||||
LinkedIn Credentials under the recruitment configurations.
|
||||
|
||||
Company
|
||||
-------
|
||||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__
|
||||
|
||||
License
|
||||
-------
|
||||
General Public License, Version 3 (LGPL v3).
|
||||
(https://www.odoo.com/documentation/17.0/legal/licenses.html)
|
||||
|
||||
Credits
|
||||
-------
|
||||
* Developer: Nilmar Shereef,
|
||||
Jesni Banu,
|
||||
(V11 & V12) Milind Mohan,
|
||||
(V16) Gayathri V,
|
||||
(V17) Kailas Krishna
|
||||
Contact: odoo@cybrosys.com
|
||||
|
||||
Contacts
|
||||
--------
|
||||
* Mail Contact : odoo@cybrosys.com
|
||||
* Website : https://cybrosys.com
|
||||
|
||||
Bug Tracker
|
||||
-----------
|
||||
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported.
|
||||
|
||||
Maintainer
|
||||
==========
|
||||
.. image:: https://cybrosys.com/images/logo.png
|
||||
:target: https://cybrosys.com
|
||||
|
||||
This module is maintained by Cybrosys Technologies.
|
||||
|
||||
For support and more information, please visit `Our Website <https://cybrosys.com/>`__
|
||||
|
||||
Further information
|
||||
===================
|
||||
HTML Description: `<static/description/index.html>`__
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2021-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
from . import controller
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
{
|
||||
'name': 'Advanced HR-LinkedIn Integration',
|
||||
'summary': "Basic module for LnkedIn-HR Recruitment connector",
|
||||
'description': """The LinkedIn-HR Recruitment Connector Basic Module is
|
||||
designed to optimize your recruitment workflow, offering a comprehensive
|
||||
suite of features to enhance candidate sourcing and selection.""",
|
||||
'category': 'Generic Modules/Human Resources',
|
||||
'version': "18.0.1.0.0",
|
||||
'depends': ['hr_recruitment', 'auth_oauth'],
|
||||
'author': 'Cybrosys Techno Solutions',
|
||||
'company': 'Cybrosys Techno Solutions',
|
||||
'maintainer': 'Cybrosys Techno Solutions',
|
||||
'website': "https://www.cybrosys.com",
|
||||
'data': [
|
||||
'data/auth_linkedin_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/recruitment_config_settings.xml',
|
||||
'views/hr_job_linkedin_likes_comments_views.xml',
|
||||
'views/linkedin_comments_views.xml',
|
||||
'views/oauth_views.xml',
|
||||
],
|
||||
'external_dependencies':
|
||||
{
|
||||
'python': ['mechanize', 'linkedin'],
|
||||
},
|
||||
'images': ['static/description/banner.jpg'],
|
||||
'license': 'LGPL-3',
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
from . import hr_linkedin_recruitment
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
import logging
|
||||
from werkzeug import urls
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
try:
|
||||
import mechanize
|
||||
from linkedin_v2 import linkedin
|
||||
from urllib.request import HTTPRedirectHandler as MechanizeRedirectHandler
|
||||
except ImportError:
|
||||
_logger.error('Odoo module hr_linkedin_recruitment depends on the several '
|
||||
'external python package Please read the doc/requirement.txt '
|
||||
'file inside the module.')
|
||||
import json
|
||||
import requests
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import http, _
|
||||
from odoo.http import request
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
|
||||
class LinkedinSocial(http.Controller):
|
||||
|
||||
@http.route('/linkedin/redirect', type='http', website=True, auth='public')
|
||||
def social_linkedin_callbacks(self):
|
||||
"""shares post on linkedin"""
|
||||
url = request.httprequest.url
|
||||
parsed_url = urlparse(url)
|
||||
code = parse_qs(parsed_url.query)['code'][0]
|
||||
state = parse_qs(parsed_url.query)['state'][0]
|
||||
|
||||
linkedin_auth_provider = request.env.ref(
|
||||
'hr_linkedin_recruitment.provider_linkedin')
|
||||
linked_in_url = request.env['hr.job'].browse(int(state))
|
||||
|
||||
recruitment = request.env['hr.job']
|
||||
|
||||
access_token = requests.post(
|
||||
'https://www.linkedin.com/oauth/v2/accessToken',
|
||||
params={
|
||||
'Content-Type': 'x-www-form-urlencoded',
|
||||
'grant_type': 'authorization_code',
|
||||
# This is code obtained on previous step by Python script.
|
||||
'code': code,
|
||||
# Client ID of your created application
|
||||
'client_id': linkedin_auth_provider.client_id,
|
||||
# # Client Secret of your created application
|
||||
'client_secret': linkedin_auth_provider.client_secret,
|
||||
# This should be same as 'redirect_uri' field value of previous Python script.
|
||||
'redirect_uri': linked_in_url._get_linkedin_post_redirect_uri(),
|
||||
},
|
||||
).json()['access_token']
|
||||
li_credential = {}
|
||||
linkedin_auth_provider = request.env.ref(
|
||||
'hr_linkedin_recruitment.provider_linkedin')
|
||||
if (linkedin_auth_provider.client_id and
|
||||
linkedin_auth_provider.client_secret):
|
||||
li_credential['api_key'] = linkedin_auth_provider.client_id
|
||||
li_credential['secret_key'] = linkedin_auth_provider.client_secret
|
||||
else:
|
||||
raise ValidationError(_('LinkedIn Access Credentials are empty.!\n'
|
||||
'Please fill up in Auth Provider form.'))
|
||||
|
||||
if request.env['ir.config_parameter'].sudo().get_param(
|
||||
'recruitment.li_username'):
|
||||
li_credential['un'] = request.env[
|
||||
'ir.config_parameter'].sudo().get_param(
|
||||
'recruitment.li_username')
|
||||
else:
|
||||
raise ValidationError(
|
||||
_('Please fill up username in LinkedIn Credential settings.'))
|
||||
|
||||
if request.env['ir.config_parameter'].sudo().get_param(
|
||||
'recruitment.li_password'):
|
||||
li_credential['pw'] = request.env[
|
||||
'ir.config_parameter'].sudo().get_param(
|
||||
'recruitment.li_password')
|
||||
else:
|
||||
raise ValidationError(
|
||||
_('Please fill up password in LinkedIn Credential settings.'))
|
||||
|
||||
url = 'https://api.linkedin.com/v2/ugcPosts'
|
||||
|
||||
li_suit_credent = {}
|
||||
li_suit_credent['access_token'] = access_token
|
||||
member_url = 'https://api.linkedin.com/v2/userinfo'
|
||||
response = recruitment.get_urn('GET', member_url,
|
||||
li_suit_credent['access_token'])
|
||||
urn_response_text = response.json()
|
||||
li_credential['profile_urn'] = urn_response_text['sub']
|
||||
li_suit_credent['li_credential'] = li_credential
|
||||
payload = json.dumps({
|
||||
"author": "urn:li:person:" + li_credential['profile_urn'],
|
||||
"lifecycleState": "PUBLISHED",
|
||||
"specificContent": {
|
||||
"com.linkedin.ugc.ShareContent": {
|
||||
"shareCommentary": {
|
||||
"text": linked_in_url.name
|
||||
},
|
||||
"shareMediaCategory": "NONE"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
|
||||
}
|
||||
})
|
||||
headers = {
|
||||
'Authorization': 'Bearer ' + access_token,
|
||||
'X-Restli-Protocol-Version': '2.0.0',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
if linked_in_url.name:
|
||||
response = requests.request("POST", url, data=payload,
|
||||
headers=headers)
|
||||
share_response_text = response.json()
|
||||
linked_in_url.write({
|
||||
'access_token': access_token + '+' + share_response_text['id']
|
||||
})
|
||||
|
||||
share_response_code = response.status_code
|
||||
|
||||
if share_response_code == 201:
|
||||
linked_in_url.update_key = True
|
||||
elif share_response_code == 404:
|
||||
raise Warning("Resource does not exist.!")
|
||||
elif share_response_code == 409:
|
||||
raise Warning("Already shared!")
|
||||
else:
|
||||
raise Warning("Error!! Check your connection...")
|
||||
else:
|
||||
raise Warning("Provide a Job description....")
|
||||
|
||||
return_uri = 'https://www.linkedin.com/oauth/v2/authorization'
|
||||
li_permissions = [' w_organization_social_feed ', ' r_liteprofile ',
|
||||
' r_organization_social_feed ', ' r_ads ',
|
||||
'w_member_social_feed', 'r_member_social',
|
||||
'r_compliance', 'w_compliance']
|
||||
|
||||
auth = linkedin.LinkedInAuthentication(li_credential['api_key'],
|
||||
li_credential['secret_key'],
|
||||
return_uri,
|
||||
li_permissions)
|
||||
li_suit_credent = {}
|
||||
li_suit_credent['access_token'] = access_token
|
||||
li_credential['profile_urn'] = share_response_text['id']
|
||||
li_suit_credent['li_credential'] = li_credential
|
||||
|
||||
url = urls.url_join(
|
||||
http.request.env['ir.config_parameter'].sudo().get_param(
|
||||
'web.base.url'),
|
||||
'web#id=%(id)s&model=hr.job&action=%(action)s&view_type=form' % {
|
||||
'id': state,
|
||||
'action': request.env.ref('hr_recruitment.action_hr_job').id
|
||||
})
|
||||
return request.redirect(url)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- This record defines the configuration for LinkedIn OAuth provider.-->
|
||||
<record id="provider_linkedin" model="auth.oauth.provider">
|
||||
<field name="name">LinkedIn</field>
|
||||
<field name="auth_endpoint">
|
||||
https://www.linkedin.com/oauth/v2/authorization
|
||||
</field>
|
||||
<field name="scope">r_basicprofile r_emailaddress w_share
|
||||
w_member_social
|
||||
</field>
|
||||
<field name="validation_endpoint">https://api.linkedin.com/v2/me
|
||||
</field>
|
||||
<field name="data_endpoint">https://api.linkedin.com/v2/me</field>
|
||||
<field name="css_class">fa fa-linkedin-square</field>
|
||||
<field name="body">Share post with LinkedIn</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
## Module <hr_linkedin_recruitment>
|
||||
|
||||
#### 05.02.2024
|
||||
#### Version 17.0.1.0.0
|
||||
##### ADD
|
||||
- Initial Commit for Advanced HR-LinkedIn Integration
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
Odoo integration module "hr_linkedin_recruitment" depends on the several external python package.
|
||||
Please ensure that listed packaged has installed in your system:
|
||||
|
||||
* python-linkedin (sudo python-linkedin)
|
||||
* mechanize (sudo pip install mechanize)
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
from . import auth_outh_provider
|
||||
from . import hr_job
|
||||
from . import linkedin_comments
|
||||
from . import recruitment_config
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class OAuthProviderLinkedin(models.Model):
|
||||
""" Adding client_secret field because some apps likes twitter,
|
||||
linkedIn are using this value for its API operations """
|
||||
_inherit = 'auth.oauth.provider'
|
||||
|
||||
client_secret = fields.Char(string='Client Secret',
|
||||
help="Only need LinkedIn, Twitter etc..")
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
import logging
|
||||
import requests
|
||||
from werkzeug.urls import url_encode, url_join
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HrJobShare(models.Model):
|
||||
"""recruitment of different job positions"""
|
||||
_inherit = 'hr.job'
|
||||
|
||||
update_key = fields.Char(string='Update Key', readonly=True)
|
||||
access_token = fields.Char(string='Access Token',
|
||||
help='Access token for your linkedin app')
|
||||
comments = fields.Boolean(default=False, string='Likes Comments',
|
||||
help='Which is used to visible the like comment retrieving button')
|
||||
like_comment = fields.Boolean(default=False, string='Likes comment',
|
||||
help='Which is used to visible the smart buttons of likes and comments')
|
||||
post_likes = fields.Integer(string='Likes Count',
|
||||
help="Total Number of likes in the shared post")
|
||||
post_commands = fields.Integer(string='Comments Count',
|
||||
help="Total Number of Comments in the shared post")
|
||||
|
||||
def _get_linkedin_post_redirect_uri(self):
|
||||
"""finding redirecting url"""
|
||||
print('url', self.get_base_url())
|
||||
return url_join(self.get_base_url(), '/linkedin/redirect')
|
||||
|
||||
def share_linkedin(self):
|
||||
""" Button function for sharing post """
|
||||
self.comments = True
|
||||
linkedin_auth_provider = self.env.ref(
|
||||
'hr_linkedin_recruitment.provider_linkedin')
|
||||
if linkedin_auth_provider.client_id and linkedin_auth_provider.client_secret:
|
||||
linkedin_client_id = linkedin_auth_provider.client_id
|
||||
params = {
|
||||
'response_type': 'code',
|
||||
'client_id': linkedin_client_id,
|
||||
'redirect_uri': self._get_linkedin_post_redirect_uri(),
|
||||
'state': self.id,
|
||||
'scope': 'w_member_social r_1st_connections_size r_ads '
|
||||
'r_ads_reporting r_basicprofile r_organization_admin '
|
||||
'r_organization_social rw_ads rw_organization_admin '
|
||||
'w_member_social w_organization_social openid profile email'
|
||||
}
|
||||
else:
|
||||
raise ValidationError(_('LinkedIn Access Credentials are empty.!\n'
|
||||
'Please fill up in Auth Provider form.'))
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': 'https://www.linkedin.com/oauth/v2/authorization?%s' % url_encode(
|
||||
params),
|
||||
'target': 'self'
|
||||
}
|
||||
|
||||
def share_request(self, method, page_share_url, access_token, data):
|
||||
""" Function will return UPDATED KEY , [201] if sharing is OK """
|
||||
headers = {'x-li-format': 'json', 'Content-Type': 'application/json'}
|
||||
params = {}
|
||||
params.update({'oauth2_access_token': access_token})
|
||||
kw = dict(data=data, params=params, headers=headers, timeout=60)
|
||||
req_response = requests.request(method.upper(), page_share_url, **kw)
|
||||
return req_response
|
||||
|
||||
def get_urn(self, method, has_access_url, access_token):
|
||||
""" Function will return TRUE if credentials user has the access to update """
|
||||
headers = {'x-li-format': 'json', 'Content-Type': 'application/json'}
|
||||
params = {}
|
||||
params.update({'oauth2_access_token': access_token})
|
||||
kw = dict(params=params, headers=headers, timeout=60)
|
||||
req_response = requests.request(method.upper(), has_access_url, **kw)
|
||||
return req_response
|
||||
|
||||
def user_response_like(self):
|
||||
"""return the likes"""
|
||||
return
|
||||
|
||||
def likes_comments(self):
|
||||
"""retrieving total count of likes and comments"""
|
||||
self.like_comment = True
|
||||
urn = self.access_token.split('+')[1]
|
||||
url = "https://api.linkedin.com/v2/socialActions/" + urn
|
||||
payload = {}
|
||||
headers = {
|
||||
'Authorization': 'Bearer ' + self.access_token.split('+')[0],
|
||||
'LinkedIn-Version': '202308',
|
||||
}
|
||||
response = requests.request("GET", url, headers=headers)
|
||||
response_comm_like = response.json()
|
||||
self.post_likes = response_comm_like['likesSummary']['totalLikes']
|
||||
self.post_commands = response_comm_like["commentsSummary"][
|
||||
'aggregatedTotalComments']
|
||||
comment_url = "https://api.linkedin.com/v2/socialActions/" + urn + "/comments"
|
||||
headers = {
|
||||
'LinkedIn-Version': '202308',
|
||||
'Authorization': 'Bearer ' + self.access_token.split('+')[0],
|
||||
}
|
||||
response = requests.request("GET", comment_url, headers=headers,
|
||||
data=payload)
|
||||
response_commets = response.json()
|
||||
|
||||
comment_id = self.env['linkedin.comments'].search([]).mapped(
|
||||
'comments_id')
|
||||
for record in response_commets.get("elements", []):
|
||||
if record['id'] not in comment_id:
|
||||
self.env['linkedin.comments'].create({
|
||||
'post_id': self.id,
|
||||
'comments_id': record['id'],
|
||||
'linkedin_comments': record['message']['text'],
|
||||
})
|
||||
|
||||
def user_response_commends(self):
|
||||
"""return the comments of the shared post"""
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'current',
|
||||
'name': _('Linkedin'),
|
||||
'view_mode': 'tree',
|
||||
'res_model': 'linkedin.comments',
|
||||
'domain': [('post_id', '=', self.id)],
|
||||
}
|
||||
|
||||
def view_shared_post(self):
|
||||
"""Direct link for viewing the shared post page in linkedin"""
|
||||
url = "https://api.linkedin.com/v2/me"
|
||||
payload = ""
|
||||
headers = {
|
||||
'LinkedIn-Version': '202208',
|
||||
'Authorization': 'Bearer ' + self.access_token.split('+')[0],
|
||||
}
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
response_activity = response.json()
|
||||
activity_urn = response_activity["vanityName"]
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': 'https://www.linkedin.com/in/%s' % activity_urn + '/recent-activity/',
|
||||
'target': 'self'
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class LinkedinComments(models.Model):
|
||||
"""class for retrieving comments of shared post"""
|
||||
_name = 'linkedin.comments'
|
||||
|
||||
post_id = fields.Integer(string="Post ID",
|
||||
help="Post id of the particular post")
|
||||
comments_id = fields.Char(string="Comments ID",
|
||||
help="For specifing the comments id")
|
||||
linkedin_comments = fields.Char(string="Comments",
|
||||
help="To add the posts comments")
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
import logging
|
||||
import urllib
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
try:
|
||||
import mechanize
|
||||
from mechanize import _response
|
||||
from mechanize import _rfc3986
|
||||
import re
|
||||
|
||||
|
||||
class MechanizeRedirectHandler(mechanize.HTTPRedirectHandler):
|
||||
def http_error_302(self, req, fp, code, msg, headers):
|
||||
if 'location' in headers:
|
||||
newurl = headers.getheaders('location')[0]
|
||||
elif 'uri' in headers:
|
||||
newurl = headers.getheaders('uri')[0]
|
||||
else:
|
||||
return
|
||||
newurl = _rfc3986.clean_url(newurl, "latin-1")
|
||||
newurl = _rfc3986.urljoin(req.get_full_url(), newurl)
|
||||
|
||||
new = self.redirect_request(req, fp, code, msg, headers, newurl)
|
||||
if new is None:
|
||||
return
|
||||
|
||||
if hasattr(req, 'redirect_dict'):
|
||||
visited = new.redirect_dict = req.redirect_dict
|
||||
if (visited.get(newurl, 0) >= self.max_repeats or
|
||||
len(visited) >= self.max_redirections):
|
||||
raise urllib.error.HTTPError(req.get_full_url(), code,
|
||||
self.inf_msg + msg, headers,
|
||||
fp)
|
||||
else:
|
||||
visited = new.redirect_dict = req.redirect_dict = {}
|
||||
visited[newurl] = visited.get(newurl, 0) + 1
|
||||
|
||||
fp.read()
|
||||
fp.close()
|
||||
|
||||
# If the redirected URL doesn't match
|
||||
new_url = new.get_full_url()
|
||||
if not re.search('^http(?:s)?\:\/\/.*www\.linkedin\.com', new_url):
|
||||
return _response.make_response('', headers.items(), new_url,
|
||||
200, 'OK')
|
||||
else:
|
||||
return self.parent.open(new)
|
||||
|
||||
http_error_301 = http_error_303 = http_error_307 = http_error_302
|
||||
http_error_refresh = http_error_302
|
||||
|
||||
except ImportError:
|
||||
_logger.warning(
|
||||
'Odoo module hr_linkedin_recruitment depends on the several external'
|
||||
' python package'
|
||||
'Please read the doc/requirement.txt file inside the module.')
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#############################################################################
|
||||
#
|
||||
# Cybrosys Technologies Pvt. Ltd.
|
||||
#
|
||||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
||||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
||||
#
|
||||
# You can modify it under the terms of the GNU LESSER
|
||||
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
|
||||
# (LGPL v3) along with this program.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
#############################################################################
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
"""config settings"""
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
li_username = fields.Char(string="User Name", help="Your Linkedin Username")
|
||||
li_password = fields.Char(string="Password", help="Your Linkedin Password")
|
||||
|
||||
def set_values(self):
|
||||
"""super the config to set the value"""
|
||||
super(ResConfigSettings, self).set_values()
|
||||
self.env['ir.config_parameter'].sudo().set_param(
|
||||
'recruitment.li_username', self.li_username)
|
||||
self.env['ir.config_parameter'].sudo().set_param(
|
||||
'recruitment.li_password', self.li_password)
|
||||
|
||||
def get_values(self):
|
||||
"""super the config to get the value"""
|
||||
res = super(ResConfigSettings, self).get_values()
|
||||
res.update(
|
||||
li_username=self.env['ir.config_parameter'].sudo().get_param(
|
||||
'recruitment.li_username'),
|
||||
li_password=self.env['ir.config_parameter'].sudo().get_param(
|
||||
'recruitment.li_password'),
|
||||
)
|
||||
return res
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_linkedin_comments,linkedin.comments,model_linkedin_comments,base.group_user,1,1,1,1
|
||||
|
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 310 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 576 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 911 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 878 B |
|
After Width: | Height: | Size: 653 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 839 B |
|
After Width: | Height: | Size: 427 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 988 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |