diff --git a/employee_requests/models/employee_overtime_request.py b/employee_requests/models/employee_overtime_request.py index 7c60e8a..d0301d0 100644 --- a/employee_requests/models/employee_overtime_request.py +++ b/employee_requests/models/employee_overtime_request.py @@ -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 diff --git a/employee_requests/models/hr_personal_permission.py b/employee_requests/models/hr_personal_permission.py index fbc1d1c..cd64996 100644 --- a/employee_requests/models/hr_personal_permission.py +++ b/employee_requests/models/hr_personal_permission.py @@ -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: diff --git a/employee_requests/tests/__init__.py b/employee_requests/tests/__init__.py new file mode 100644 index 0000000..7430a82 --- /dev/null +++ b/employee_requests/tests/__init__.py @@ -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 \ No newline at end of file diff --git a/employee_requests/tests/test_employee_department_jobs.py b/employee_requests/tests/test_employee_department_jobs.py new file mode 100644 index 0000000..56ac3fc --- /dev/null +++ b/employee_requests/tests/test_employee_department_jobs.py @@ -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") \ No newline at end of file diff --git a/employee_requests/tests/test_hr_clearance_form.py b/employee_requests/tests/test_hr_clearance_form.py new file mode 100644 index 0000000..8ded97f --- /dev/null +++ b/employee_requests/tests/test_hr_clearance_form.py @@ -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 \ No newline at end of file diff --git a/employee_requests/tests/test_hr_personal_permission.py b/employee_requests/tests/test_hr_personal_permission.py new file mode 100644 index 0000000..d1c0e89 --- /dev/null +++ b/employee_requests/tests/test_hr_personal_permission.py @@ -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() \ No newline at end of file diff --git a/employee_requests/tests/test_overtime_process.py b/employee_requests/tests/test_overtime_process.py new file mode 100644 index 0000000..c89b8df --- /dev/null +++ b/employee_requests/tests/test_overtime_process.py @@ -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") + diff --git a/exp_employee_custody/tests/__init__.py b/exp_employee_custody/tests/__init__.py new file mode 100644 index 0000000..dc1dd70 --- /dev/null +++ b/exp_employee_custody/tests/__init__.py @@ -0,0 +1 @@ +from . import test_custody_receiving \ No newline at end of file diff --git a/exp_employee_custody/tests/test_custody_receiving.py b/exp_employee_custody/tests/test_custody_receiving.py new file mode 100644 index 0000000..9b6381c --- /dev/null +++ b/exp_employee_custody/tests/test_custody_receiving.py @@ -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() \ No newline at end of file diff --git a/exp_hr_appraisal_kpi/__init__.py b/exp_hr_appraisal_kpi/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/exp_hr_appraisal_kpi/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/exp_hr_appraisal_kpi/__manifest__.py b/exp_hr_appraisal_kpi/__manifest__.py new file mode 100644 index 0000000..f275bfd --- /dev/null +++ b/exp_hr_appraisal_kpi/__manifest__.py @@ -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, +} diff --git a/exp_hr_appraisal_kpi/i18n/ar_001.po b/exp_hr_appraisal_kpi/i18n/ar_001.po new file mode 100644 index 0000000..989e30f --- /dev/null +++ b/exp_hr_appraisal_kpi/i18n/ar_001.po @@ -0,0 +1,1097 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * exp_hr_appraisal_kpi +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-06-12 10:54+0000\n" +"PO-Revision-Date: 2024-06-12 10:54+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__employee_performance_evaluation__state__approve +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__skill_appraisal__state__approve +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +msgid "Accept" +msgstr "موافقة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__kpi_item__method_of_calculate__accumulative +msgid "Accumulative" +msgstr "تراكمي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_needaction +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_needaction +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_needaction +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_needaction +msgid "Action Needed" +msgstr "تحتاج إلى تدخل" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_form +msgid "Add a Item" +msgstr "اضافة عنصر" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_form +msgid "Add a note" +msgstr "اضافة ملاحظة" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_form +msgid "Add a section" +msgstr "اضافة قسم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_hr_employee_appraisal +#: model:ir.model,name:exp_hr_appraisal_kpi.model_hr_group_employee_appraisal +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_group_employee_appraisal__appraisal_ids +msgid "Appraisal" +msgstr "تقـييم الموظف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_job_class_apprisal +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_job__appraisal_percentages_id +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_kpi_percentage +msgid "Appraisal Percentage" +msgstr "نسبة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__years_employee_goals__state__apprisal +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.group_employee_apprisal_extend +msgid "Apprisal" +msgstr "التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__date_apprisal +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__date_apprisal +msgid "Apprisal Date" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.actions.act_window,name:exp_hr_appraisal_kpi.apprisal_percentag_act_window1 +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.apprisal_percentag_form_view +msgid "Apprisal Percentag" +msgstr "نسبة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__apprisal_result +msgid "Apprisal Result" +msgstr "نتيجة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_attachment_count +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_attachment_count +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_attachment_count +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_attachment_count +msgid "Attachment Count" +msgstr "عدد المرفقات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__kpi_item__method_of_calculate__avrerage +msgid "Average" +msgstr "متوسط" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__category_id +msgid "Category" +msgstr "الهدف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__choiec +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__choiec +msgid "Choiec" +msgstr "الدرجة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__years_employee_goals__state__close +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_goals_form +msgid "Close" +msgstr "مغلق" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.employee_apprisal_extend +msgid "Compute Apprisal" +msgstr "حساب التقييم" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.kpi_period_form_extend +msgid "Create Apprisal" +msgstr "انشاء تقيييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__create_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__create_uid +msgid "Created by" +msgstr "تم الإنشاء بواسطة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__create_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__create_date +msgid "Created on" +msgstr "تم الإنشاء في" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__department_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__department_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item__department_item_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__department_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__department_id +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.group_employee_apprisal_extend +msgid "Department" +msgstr "القسم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_employee_table__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__description +msgid "Description" +msgstr "الوصف" + + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_group_employee_appraisal__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_job__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_category__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__display_name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__done +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__done +msgid "Done" +msgstr "المحقق" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__employee_performance_evaluation__state__draft +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__skill_appraisal__state__draft +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__years_employee_goals__state__draft +msgid "Draft" +msgstr "مسودة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__employee_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__employee_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__employee_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__employee_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__employee_id +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.group_employee_apprisal_extend +msgid "Employee" +msgstr "الموظف" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/employee_apprisal.py:0 +#, python-format +msgid "Employee Apprisal must be unique per Employee, Year, and Period!" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__employee_appraisal2 +msgid "Employee Appraisal2" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__employee_apprisal_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__employee_apprisal_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__employee_apprisal_id +msgid "Employee Apprisal" +msgstr "تقييم الموظفين" + +#. module: exp_hr_appraisal_kpi +#: model:ir.actions.act_window,name:exp_hr_appraisal_kpi.action_year_goal_emp +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__emp_goal_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__employee_goals_id +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_year_employee_goals_list +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +msgid "Employee Goals" +msgstr "أهداف الموظف" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/years_employee_goals.py:0 +#, python-format +msgid "Employee Goals must be unique per Employee, Year, and kpi!" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_evalution_employee_goals_ +msgid "Employee Goals Appraisal" +msgstr "تقييم أهداف الموظف" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/employee_performance_evaluation.py:0 +#, python-format +msgid "Employee Goals Apprisal must be unique per Employee, Year, and Period!" +msgstr "" +"تقييم اهداف الموظف يجب ان يكون فريدا حيث لايتكرر الموظف مع السنة مع الفترة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_skill_appraisal_list +msgid "Employee Skill Appraisal" +msgstr "تقييم جدارات الموظف" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/skill_apprisal.py:0 +#, python-format +msgid "Employee Skill Apprisal must be unique per Employee, Year, and Period!" +msgstr "" +"تقييم جدارات الموظف يجب ان يكون فريدا بحيث لايتكرر السنة مع الموظف مع الفترة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__manager_id +msgid "Employee m" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.group_employee_apprisal_extend +msgid "Employee name" +msgstr "اسم الموظف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_employee_performance_evaluation +msgid "Employee performance evaluation" +msgstr "تقييم أداء الموظف" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.group_employee_apprisal_extend +msgid "Employees" +msgstr "الموظفين" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__date_end_k +msgid "End Date" +msgstr "تاريخ الانتهاء" + +#. module: exp_hr_appraisal_kpi +#: model:ir.actions.act_window,name:exp_hr_appraisal_kpi.action_evalution_goal_emp +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +msgid "Evaluation Employee Goals" +msgstr "تقييم أهداف الموظف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__mark_evaluation +msgid "Evaluation Mark" +msgstr "درجة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_follower_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_follower_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_follower_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_follower_ids +msgid "Followers" +msgstr "متابعون" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_channel_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_channel_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_channel_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_channel_ids +msgid "Followers (Channels)" +msgstr "المتابعون (القنوات)" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_partner_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_partner_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_partner_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_partner_ids +msgid "Followers (Partners)" +msgstr "المتابعون (الشركاء)" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__target +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.hpi_item_extend +msgid "From(Done)" +msgstr "من (المنجز)" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__goal_ids +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_kpi_categories +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.employee_apprisal_extend +msgid "Goals" +msgstr "الأهداف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__goals_mark +msgid "Goals Apprisal Mark" +msgstr "درجة تقييم الاهداف" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.kpi_period_form_extend +msgid "Goals Period" +msgstr "فترة الأهداف" + +#. module: exp_hr_appraisal_kpi +#: model:res.groups,name:exp_hr_appraisal_kpi.group_appraisal_responsabil +msgid "Goals Responsible" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_kpi_goal_skill +msgid "Goals and Skills" +msgstr "الأهداف والجدارات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_group_employee_appraisal__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_job__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_category__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__id +msgid "ID" +msgstr "المُعرف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_needaction +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_unread +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_needaction +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_unread +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_needaction +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_unread +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_needaction +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_unread +msgid "If checked, new messages require your attention." +msgstr "إذا تم التحقق، فإن الرسائل الجديدة تحتاج إلى اهتمامك." + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_has_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_has_sms_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_has_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_has_sms_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_has_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_has_sms_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_has_error +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "إذا تم التحقق، فإن بعض الرسائل بها خطأ في التسليم." + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_is_follower +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_is_follower +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_is_follower +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_is_follower +msgid "Is Follower" +msgstr "متابع" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__item_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_employee_table__item_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__item_id +msgid "Item" +msgstr "العنصر" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__items_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__items_ids +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_form +msgid "Items" +msgstr "العناصر" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_hr_job +msgid "Job Position" +msgstr "المنصب الوظيفي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__job_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__job_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__job_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__job_id +msgid "Job Title" +msgstr "الوظيفة" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.group_employee_apprisal_extend +msgid "Job title" +msgstr "الوظيفة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__job_ids +msgid "Jobs" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_kpi_item +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__employee_eval_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__kpi_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__kpi_id +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_kpi_kpi +msgid "KPI" +msgstr "المؤشر" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_kpi_category +msgid "KPI Category" +msgstr "فئة مؤشر الأداء الرئيسي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_kpi_period +msgid "KPI Period" +msgstr "فترة مؤشر الأدا" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__kip_id +msgid "Kip_id" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__kpi_goal_period_id +msgid "Kpi Goal Period" +msgstr "فترة أهداف مؤشر الأداء " + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period__kpi_goals_periods_ids +msgid "Kpi Goals Periods" +msgstr "فترات أهداف مؤشر الأداء" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__kpi_period_id +msgid "Kpi Period" +msgstr "فترة مؤشر الأداء" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period__kpi_periods_ids +msgid "Kpi Periods" +msgstr "فترات مؤشر الأداء " + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_group_employee_appraisal____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_job____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_category____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill____last_update +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__write_uid +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__write_uid +msgid "Last Updated by" +msgstr "آخر تحديث بواسطة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__write_date +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__write_date +msgid "Last Updated on" +msgstr "آخر تحديث في" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__level +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__level +msgid "Level" +msgstr "المستوى" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_main_attachment_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_main_attachment_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_main_attachment_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_main_attachment_id +msgid "Main Attachment" +msgstr "المرفق الرئيسي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__manager_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__manager_id +msgid "Manager" +msgstr "المدير" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item__mark_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__mark +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__mark_avg +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_employee_table__mark +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_employee_table__mark_avg +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__mark +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__mark_avg +msgid "Mark" +msgstr "الدرجة" + + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__mark_apprisal +msgid "Mark Apprisal" +msgstr "درجة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.hpi_item_extend +msgid "Marks" +msgstr "الدرجات" + +#. module: exp_hr_appraisal_kpi +#: model:res.groups,name:exp_hr_appraisal_kpi.apprisal_kpi_group +msgid "Menu apprisal hide/show" +msgstr "إخفاء/إظهار قائمة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_has_error +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_has_error +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_has_error +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_has_error +msgid "Message Delivery error" +msgstr "خطأ في تسليم الرسالة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_ids +msgid "Messages" +msgstr "الرسائل" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item__method_of_calculate +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__method_of_calculate +msgid "Method Of Calculate" +msgstr "طريقة الحساب" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.apprisal_percentag_tree_view +msgid "ModelTitle" +msgstr "عنوان النموذج" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_item_item__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__name +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__name +msgid "Name" +msgstr "الاسم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__notes +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.employee_apprisal_extend +msgid "Notes" +msgstr "ملاحظات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_needaction_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_needaction_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_needaction_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_needaction_counter +msgid "Number of Actions" +msgstr "عدد الإجراءات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_has_error_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_has_error_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_has_error_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_has_error_counter +msgid "Number of errors" +msgstr "عدد الأخطاء" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_needaction_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_needaction_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_needaction_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_needaction_counter +msgid "Number of messages which requires an action" +msgstr "عدد الرسائل التي تتطلب إجراء" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_has_error_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_has_error_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_has_error_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "عدد الرسائل ذات الأخطاء في التسليم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_unread_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__message_unread_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__message_unread_counter +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__message_unread_counter +msgid "Number of unread messages" +msgstr "عدد الرسائل غير المقروءة" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/kpi_period.py:0 +#: code:addons/exp_hr_appraisal_kpi/models/kpi_period.py:0 +#, python-format +msgid "Overlap detected between periods!" +msgstr "تم اكتشاف تداخل بين الفترات!" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__percentage_skills +msgid "Percentage of Skills Appraisal%" +msgstr "نسبة تقييم المهارات%" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_job_class_apprisal__percentage_kpi +msgid "Percentage of indicator Appraisal%" +msgstr "نسبة تقييم الأهداف%" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__period_goals_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__period_goals_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__period +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__goals_period_ids +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_goals_form +msgid "Period" +msgstr "الفترة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__period_goals_id +msgid "Period Of Goals" +msgstr "فترة الأهداف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.menu_kpi_period +msgid "Periods" +msgstr "الفترات" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/employee_apprisal.py:0 +#, python-format +msgid "" +"Please check appraisal result configuration , there is more than result for " +"percentage %s are %s " +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/employee_apprisal.py:0 +#, python-format +msgid "Please select at least one employee to make appraisal." +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__reason +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__reason +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__reason +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__reason +msgid "Reason/Justification" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__recommendations +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__recommendations +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +msgid "Recommendations" +msgstr "التوصيات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__employee_performance_evaluation__state__refuse +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__skill_appraisal__state__refuse +msgid "Refused" +msgstr "مرفوض" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__user_id +msgid "Related user name for the resource to manage its access." +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +msgid "Reset To Draft" +msgstr "إعادة تعيين إلى مسودة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_item__responsible_item_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__responsible_item_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__user_id +msgid "Responsible" +msgstr "المسؤول" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__avarage +msgid "Result" +msgstr "النتيجة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_has_sms_error +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_has_sms_error +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_has_sms_error +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_has_sms_error +msgid "SMS Delivery error" +msgstr "خطأ في تسليم الرسائل النصية" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +msgid "Select Goals" +msgstr "جلب الاهداف" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +msgid "Send" +msgstr "إرسال" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__sequence +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__sequence +msgid "Sequence" +msgstr "متسلسل" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__skill_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__skill_id +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.skill_search_view +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_form +msgid "Skill" +msgstr "الجدارات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.actions.act_window,name:exp_hr_appraisal_kpi.action_skill_appraisal +#: model:ir.model,name:exp_hr_appraisal_kpi.model_skill_appraisal +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item__skill_appraisal_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_item_table__skill_appraisal_id +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_tree +msgid "Skill Appraisal" +msgstr "تقييم الجدارات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.actions.act_window,name:exp_hr_appraisal_kpi.skill_act_window +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__skill_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_job__item_job_ids +#: model:ir.ui.menu,name:exp_hr_appraisal_kpi.skill_menu +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.employee_apprisal_extend +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.hr_job_from_extend +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_tree +msgid "Skills" +msgstr "الجدارات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__skill_mark +msgid "Skills Apprisal Mark" +msgstr "درجة تقييم الجدارات" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.kpi_period_form_extend +msgid "Skills Period" +msgstr "فترة الجدارات" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_kpi_period_notes__date_start_k +msgid "Star Date" +msgstr "تاريخ البدء" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_goals_form +msgid "Start Apprisal" +msgstr "بدء التقييم" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__state +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__state +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__state +msgid "State" +msgstr "الحالة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__target +msgid "Target" +msgstr "المستهدف" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/kpi_item.py:0 +#, python-format +msgid "The To value must be greater than the From value." +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.actions.act_window,help:exp_hr_appraisal_kpi.apprisal_percentag_act_window1 +msgid "There is no examples click here to add new ModelTitle." +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.actions.act_window,help:exp_hr_appraisal_kpi.skill_act_window +msgid "There is no examples click here to add new Skill." +msgstr "لا يوجد أمثلة انقر هنا لإضافة جدارات جديدة." + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_mark_mark__to +msgid "To(Target)" +msgstr "الي (المستهدف)" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_evalution_goals_employee_tree1 +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_year_goals_employee_tree1 +msgid "Total" +msgstr "الإجمالي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__total +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__total_score +msgid "Total Mark" +msgstr "درجة التقييم" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.employee_apprisal_extend +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +msgid "Total Target" +msgstr "الهدف الإجمالي" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.employee_apprisal_extend +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +msgid "Total Weight" +msgstr "الوزن الإجمالي" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/appraisal_percentage.py:0 +#, python-format +msgid "Total percentage should be 100." +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_goals_form +msgid "Totat Traget" +msgstr "الهدف الإجمالي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__kpi_item__method_of_calculate__undefined +msgid "Undefined" +msgstr "غير محدد" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_unread +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_unread +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_unread +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_unread +msgid "Unread Messages" +msgstr "الرسائل غير المقروءة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__message_unread_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__message_unread_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__message_unread_counter +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__message_unread_counter +msgid "Unread Messages Counter" +msgstr "عداد الرسائل غير المقروءة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__employee_performance_evaluation__state__dir_manager +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__skill_appraisal__state__dir_manager +msgid "Wait Employee Accept" +msgstr "انتظار قبول الموظف" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__employee_performance_evaluation__state__wait_hr_manager +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__skill_appraisal__state__wait_hr_manager +msgid "Wait HR Manager Accept" +msgstr "انتظار قبول مدير الموارد البشرية" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__employee_performance_evaluation__state__wait_dir_manager +#: model:ir.model.fields.selection,name:exp_hr_appraisal_kpi.selection__skill_appraisal__state__wait_dir_manager +msgid "Wait Manager Accept" +msgstr "انتظار قبول المدير" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__website_message_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__website_message_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_skill__website_message_ids +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__website_message_ids +msgid "Website Messages" +msgstr "رسائل الموقع الإلكتروني" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_employee_performance_evaluation__website_message_ids +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_appraisal__website_message_ids +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_skill_skill__website_message_ids +#: model:ir.model.fields,help:exp_hr_appraisal_kpi.field_years_employee_goals__website_message_ids +msgid "Website communication history" +msgstr "سجلات التواصل عبر الموقع الإلكتروني" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__weight +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__weight +msgid "Weight" +msgstr "الوزن" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_employee_performance_evaluation__year_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_employee_appraisal__year_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_hr_group_employee_appraisal__year_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_period_goals__year_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_skill_appraisal__year_id +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__year_id +msgid "Year" +msgstr "السنة" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_goals_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_evalution_goals_employee_tree1 +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_year_goals_employee_tree1 +msgid "Year Employee Goals" +msgstr "أهداف موظف العام" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__year_target +msgid "Year Target" +msgstr "المستهدف" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/employee_performance_evaluation.py:0 +#, python-format +msgid "" +"You can't delete a Goal apprisal not in Draft State , archive it instead." +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: code:addons/exp_hr_appraisal_kpi/models/skill_apprisal.py:0 +#, python-format +msgid "" +"You can't delete a Skill apprisal not in Draft State , archive it instead." +msgstr "لايمكن المسح حيث تم الارسال يمكن الارشفة بدلا من هذا" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_item_item +msgid "item.item" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.item_tree_view_tree +msgid "item_tree_tree" +msgstr "عنصر_شجرة_عرض" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_kpi_period_notes +msgid "kpi.period.notes" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_mark_mark +msgid "mark.mark" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_period_goals +msgid "period.goals" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_evalu_form +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_skill_appraisal_form +msgid "refuse" +msgstr "رفض" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_skill_item +msgid "skill.item" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_skill_item_table +msgid "skill.item.table" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_skill_skill +msgid "skill.skill" +msgstr "" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model,name:exp_hr_appraisal_kpi.model_years_employee_goals +msgid "years employee goals" +msgstr "أهداف الموظفين السنوية" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__first_period_traget +msgid "First Period Traget" +msgstr "مستهدف الفترة الاولي" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__second_period_traget +msgid "Second Period Traget" +msgstr "مستهدف الفترة الثانية" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__third_period_traget +msgid "Third Period Traget" +msgstr "مستهدف الفترة الثالثة" + +#. module: exp_hr_appraisal_kpi +#: model:ir.model.fields,field_description:exp_hr_appraisal_kpi.field_years_employee_goals__fourth_period_traget +msgid "Fourth Period Traget" +msgstr "مستهدف الفترة الرابعة" + +#. module: exp_hr_appraisal_kpi +#: model_terms:ir.ui.view,arch_db:exp_hr_appraisal_kpi.view_emplo_goals_form +msgid "Set To Dratt" +msgstr "اعادة التعيين لمسودة" diff --git a/exp_hr_appraisal_kpi/models/__init__.py b/exp_hr_appraisal_kpi/models/__init__.py new file mode 100644 index 0000000..99fdec4 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/__init__.py @@ -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 + diff --git a/exp_hr_appraisal_kpi/models/appraisal_percentage.py b/exp_hr_appraisal_kpi/models/appraisal_percentage.py new file mode 100644 index 0000000..b0f98d3 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/appraisal_percentage.py @@ -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 \ No newline at end of file diff --git a/exp_hr_appraisal_kpi/models/employee_apprisal.py b/exp_hr_appraisal_kpi/models/employee_apprisal.py new file mode 100644 index 0000000..7193c46 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/employee_apprisal.py @@ -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 diff --git a/exp_hr_appraisal_kpi/models/employee_performance_evaluation.py b/exp_hr_appraisal_kpi/models/employee_performance_evaluation.py new file mode 100644 index 0000000..56a4722 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/employee_performance_evaluation.py @@ -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() diff --git a/exp_hr_appraisal_kpi/models/kpi_item.py b/exp_hr_appraisal_kpi/models/kpi_item.py new file mode 100644 index 0000000..58fdfee --- /dev/null +++ b/exp_hr_appraisal_kpi/models/kpi_item.py @@ -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.')) diff --git a/exp_hr_appraisal_kpi/models/kpi_period.py b/exp_hr_appraisal_kpi/models/kpi_period.py new file mode 100644 index 0000000..74fe018 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/kpi_period.py @@ -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!")) diff --git a/exp_hr_appraisal_kpi/models/kpi_skill.py b/exp_hr_appraisal_kpi/models/kpi_skill.py new file mode 100644 index 0000000..cfc9950 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/kpi_skill.py @@ -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') diff --git a/exp_hr_appraisal_kpi/models/skill_apprisal.py b/exp_hr_appraisal_kpi/models/skill_apprisal.py new file mode 100644 index 0000000..6e91cae --- /dev/null +++ b/exp_hr_appraisal_kpi/models/skill_apprisal.py @@ -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() diff --git a/exp_hr_appraisal_kpi/models/years_employee_goals.py b/exp_hr_appraisal_kpi/models/years_employee_goals.py new file mode 100644 index 0000000..c6ac9b1 --- /dev/null +++ b/exp_hr_appraisal_kpi/models/years_employee_goals.py @@ -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 diff --git a/exp_hr_appraisal_kpi/security/group.xml b/exp_hr_appraisal_kpi/security/group.xml new file mode 100644 index 0000000..e47eec5 --- /dev/null +++ b/exp_hr_appraisal_kpi/security/group.xml @@ -0,0 +1,95 @@ + + + + + Menu apprisal hide/show + + + + Goals Responsible + + + + + + Extended KPI Category Rule + + [ + '|', + ('company_id','=', False), + ('company_id', 'in', company_ids), + ] + + + + + Extended KPI Category Rule + + [ + '|', + ('company_id','=', False), + ('company_id', 'in', company_ids), + ] + + + + + + Employee: views its Skill appraisals only + + [('employee_id.user_id','=',user.id)] + + + + + Employee: views its Goal appraisals only + + [('employee_id.user_id','=',user.id)] + + + + + Manager: views Skill appraisals of its subordinates + + ['|','|',('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])] + + + + + Manager: views Goals appraisals of its subordinates + + ['|','|',('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])] + + + + + Manager: views Skills appraisals of all subordinates + + [(1 ,'=', 1)] + + + + + Manager: views Goals appraisals of all subordinates + + [(1 ,'=', 1)] + + + + + + \ No newline at end of file diff --git a/exp_hr_appraisal_kpi/security/ir.model.access.csv b/exp_hr_appraisal_kpi/security/ir.model.access.csv new file mode 100644 index 0000000..bf1cef1 --- /dev/null +++ b/exp_hr_appraisal_kpi/security/ir.model.access.csv @@ -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 diff --git a/exp_hr_appraisal_kpi/static/description/icon.png b/exp_hr_appraisal_kpi/static/description/icon.png new file mode 100644 index 0000000..4141f52 Binary files /dev/null and b/exp_hr_appraisal_kpi/static/description/icon.png differ diff --git a/exp_hr_appraisal_kpi/static/src/css/website_rtl.css b/exp_hr_appraisal_kpi/static/src/css/website_rtl.css new file mode 100644 index 0000000..85347c6 --- /dev/null +++ b/exp_hr_appraisal_kpi/static/src/css/website_rtl.css @@ -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; + } +} diff --git a/exp_hr_appraisal_kpi/views/appraisal_percentage.xml b/exp_hr_appraisal_kpi/views/appraisal_percentage.xml new file mode 100644 index 0000000..074adbd --- /dev/null +++ b/exp_hr_appraisal_kpi/views/appraisal_percentage.xml @@ -0,0 +1,69 @@ + + + + + apprisal.apprisal_percentag.form + job.class.apprisal + +
+ + + + + + + + + + + +
+
+
+ + + apprisal.apprisal_percentag.tree + job.class.apprisal + + + + + + + + + + + + + + + + + + + + + + + + Apprisal Percentag + ir.actions.act_window + job.class.apprisal + list,form + +

+ There is no examples click here to add new ModelTitle. +

+
+
+ + +
+
\ No newline at end of file diff --git a/exp_hr_appraisal_kpi/views/employee_apprisal.xml b/exp_hr_appraisal_kpi/views/employee_apprisal.xml new file mode 100644 index 0000000..e0297bb --- /dev/null +++ b/exp_hr_appraisal_kpi/views/employee_apprisal.xml @@ -0,0 +1,223 @@ + + + + + + employee.apprisal.form.extend + hr.employee.appraisal + + + + + + + + + + + + + + 1 + + + + 1 + + + 1 + + + + + + + + 1 + + + 1 + + + + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + + + + + +
+ +
+
+ +
+
+
+
+
diff --git a/hr_linkedin_recruitment/views/linkedin_comments_views.xml b/hr_linkedin_recruitment/views/linkedin_comments_views.xml new file mode 100644 index 0000000..c0082a6 --- /dev/null +++ b/hr_linkedin_recruitment/views/linkedin_comments_views.xml @@ -0,0 +1,21 @@ + + + + + linkedin.comments.view.tree + linkedin.comments + + + + + + + + + + Post Comments + linkedin.comments + list + + diff --git a/hr_linkedin_recruitment/views/oauth_views.xml b/hr_linkedin_recruitment/views/oauth_views.xml new file mode 100644 index 0000000..b126b3c --- /dev/null +++ b/hr_linkedin_recruitment/views/oauth_views.xml @@ -0,0 +1,32 @@ + + + + + + + + + + auth.oauth.provider.view.form.inherit.hr.linkedin.recruitment + + auth.oauth.provider + + + + + + + + diff --git a/hr_linkedin_recruitment/views/recruitment_config_settings.xml b/hr_linkedin_recruitment/views/recruitment_config_settings.xml new file mode 100644 index 0000000..a3de3e3 --- /dev/null +++ b/hr_linkedin_recruitment/views/recruitment_config_settings.xml @@ -0,0 +1,30 @@ + + + + + + + res.config.settings.view.form.inherit.hr.linkedin.recruitment + + res.config.settings + + + +

LinkedIn Credentials

+
+
+ + + + +
+
+
+
+
+
+
diff --git a/hr_multicompany_employee_number/__init__.py b/hr_multicompany_employee_number/__init__.py new file mode 100644 index 0000000..a0fdc10 --- /dev/null +++ b/hr_multicompany_employee_number/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/hr_multicompany_employee_number/__manifest__.py b/hr_multicompany_employee_number/__manifest__.py new file mode 100644 index 0000000..ec7d398 --- /dev/null +++ b/hr_multicompany_employee_number/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'HR Multi-Company Employee Number', + 'version': '18.0.1.0.0', + 'category': 'Human Resources', + 'summary': 'Fix employee number generation for multi-company setup', + 'description': """ + This module fixes the employee number generation issue when creating employees + from res.users in a multi-company environment. + """, + 'author': 'Custom Development', + 'depends': ['hr_base', 'hr'], + 'data': [ + 'security/ir.model.access.csv', + 'data/ir_sequence_data.xml', + ], + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/hr_multicompany_employee_number/data/ir_sequence_data.xml b/hr_multicompany_employee_number/data/ir_sequence_data.xml new file mode 100644 index 0000000..a49c6c1 --- /dev/null +++ b/hr_multicompany_employee_number/data/ir_sequence_data.xml @@ -0,0 +1,16 @@ + + + + + + Global Employee Number + hr.employee.global + EMP- + 4 + 1 + 1 + + + + + diff --git a/hr_multicompany_employee_number/models/__init__.py b/hr_multicompany_employee_number/models/__init__.py new file mode 100644 index 0000000..62e0430 --- /dev/null +++ b/hr_multicompany_employee_number/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import hr_employee diff --git a/hr_multicompany_employee_number/models/hr_employee.py b/hr_multicompany_employee_number/models/hr_employee.py new file mode 100644 index 0000000..8a34f51 --- /dev/null +++ b/hr_multicompany_employee_number/models/hr_employee.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +from datetime import date +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class HrEmployee(models.Model): + _inherit = "hr.employee" + + @api.model + def _default_emp_code(self): + seq = self.env['ir.sequence'].next_by_code('hr.employee.global') + if not seq: + # If sequence doesn't exist, create it + self._create_global_sequence() + seq = self.env['ir.sequence'].next_by_code('hr.employee.global') + return seq or '/' + + @api.model + def _create_global_sequence(self): + existing_sequence = self.env['ir.sequence'].search([ + ('code', '=', 'hr.employee.global') + ], limit=1) + + if not existing_sequence: + # Find the maximum employee number in the system + all_employees = self.env['hr.employee'].search([ + ('active', 'in', [False, True]) + ]) + + max_number = 0 + for emp in all_employees: + if emp.emp_no and emp.emp_no.startswith('EMP-'): + try: + number_part = emp.emp_no.replace('EMP-', '') + if number_part.isdigit(): + max_number = max(max_number, int(number_part)) + except (ValueError, AttributeError): + continue + + self.env['ir.sequence'].sudo().create({ + 'name': 'Global Employee Number', + 'code': 'hr.employee.global', + 'implementation': 'standard', + 'prefix': 'EMP-', + 'padding': 4, + 'number_increment': 1, + 'number_next_actual': max_number + 1, + 'company_id': False, + }) + + @api.model + def create(self, vals): + + if not vals.get('emp_no'): + vals['emp_no'] = self._default_emp_code() + + if 'company_id' not in vals: + vals['company_id'] = self.env.context.get('default_company_id') or self.env.company.id + + return super(HrEmployee, self).create(vals) + + @api.constrains("emp_no", "birthday", "attachment_ids") + def e_unique_field_name_constrains(self): + for rec in self: + # Check employee number uniqueness globally + if rec.emp_no and rec.emp_no != '/': + duplicate = self.search([ + ("emp_no", "=", rec.emp_no), + ("id", "!=", rec.id) + ], limit=1) + if duplicate: + raise ValidationError( + _("You cannot create Employee with the same employee number") + ) + + if rec.birthday and isinstance(rec.birthday, date) and rec.birthday >= date.today(): + raise ValidationError(_("Sorry, The Birthday Must Be Less than Date Today")) + + if rec.attachment_ids: + for att in rec.attachment_ids: + if not att.doc_name: + raise ValidationError(_('Attach the attachment to the Document %s') % att.name) diff --git a/hr_multicompany_employee_number/security/ir.model.access.csv b/hr_multicompany_employee_number/security/ir.model.access.csv new file mode 100644 index 0000000..f0dd6ce --- /dev/null +++ b/hr_multicompany_employee_number/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_employee_multicompany,hr.employee.multicompany,hr.model_hr_employee,base.group_user,1,1,1,1 diff --git a/hr_training_payment/__init__.py b/hr_training_payment/__init__.py new file mode 100644 index 0000000..f5ba686 --- /dev/null +++ b/hr_training_payment/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models \ No newline at end of file diff --git a/hr_training_payment/__manifest__.py b/hr_training_payment/__manifest__.py new file mode 100644 index 0000000..d56e8bc --- /dev/null +++ b/hr_training_payment/__manifest__.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +{ + 'name': "HR Training Payment", + + 'summary': """ + HR Training Payment + """, + + 'description': """ + HR Training Payment + """, + 'category': 'Odex25-HR/Odex25-HR', + 'version': '18.0.1.0.0', + 'sequence': 6, + 'website': 'http://exp-sa.com', + 'license': 'GPL-3', + 'author': 'Expert Co. Ltd.', + # 'depends': ['exp_official_mission','purchase_requisition_custom'], + 'depends': ['exp_official_mission'], + + 'data': [ + "views/hr_official_mission.xml", + "views/mission_type.xml", + + ], + +} diff --git a/hr_training_payment/i18n/ar_001.po b/hr_training_payment/i18n/ar_001.po new file mode 100644 index 0000000..94757e2 --- /dev/null +++ b/hr_training_payment/i18n/ar_001.po @@ -0,0 +1,62 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_training_payment +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-06 18:03+0000\n" +"PO-Revision-Date: 2025-02-06 18:03+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_training_payment +#: code:addons/hr_training_payment/models/hr_official_mission.py:0 +#, python-format +msgid "" +"Employee \"%s\" has no contract Please create contract to add line to " +"advantages" +msgstr "" + +#. module: hr_training_payment +#: model:ir.model,name:hr_training_payment.model_hr_official_mission +msgid "Official mission" +msgstr "" + +#. module: hr_training_payment +#: model:ir.model.fields,field_description:hr_training_payment.field_hr_official_mission_type__pr_product_id +msgid "PR Product" +msgstr "منتج طلب الشراء" + +#. module: hr_training_payment +#: model:ir.model.fields,field_description:hr_training_payment.field_hr_official_mission__purchase_request_id +msgid "Purchase Request" +msgstr "طلب الشراء" + +#. module: hr_training_payment +#: code:addons/hr_training_payment/models/hr_official_mission.py:0 +#, python-format +msgid "You do not have account or journal in mission type \"%s\" " +msgstr "" + +#. module: hr_training_payment +#: model:ir.model,name:hr_training_payment.model_hr_official_mission_type +msgid "hr.official.mission.type" +msgstr "" + +#. module: hr_training_payment +#: code:addons/hr_training_payment/models/hr_official_mission.py:0 +#, python-format +msgid "You must Enter Purchase Product in Training Type Configuration" +msgstr "يجب ادخال منتج طلب الشراء في إعداد نوع المهام" + +#. module: hr_training_payment +#: code:addons/hr_training_payment/models/hr_official_mission.py:0 +#, python-format +msgid "Training Cost Must be Bigger than Zero" +msgstr "تكلفة مركز التدريب يجب ان تكون اكبر من صفر" diff --git a/hr_training_payment/models/__init__.py b/hr_training_payment/models/__init__.py new file mode 100644 index 0000000..59af57a --- /dev/null +++ b/hr_training_payment/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import hr_official_mission +from . import mission_type \ No newline at end of file diff --git a/hr_training_payment/models/hr_official_mission.py b/hr_training_payment/models/hr_official_mission.py new file mode 100644 index 0000000..c9aca82 --- /dev/null +++ b/hr_training_payment/models/hr_official_mission.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api, exceptions,_ +from datetime import datetime, date, timedelta +from odoo.exceptions import ValidationError +import calendar + + +class HrOfficialMission(models.Model): + _inherit = 'hr.official.mission' + + purchase_request_id = fields.Many2one(comodel_name='purchase.request', string="Purchase Request") + + def approve(self): + # check if there is dealing with financial + self.employee_ids.chick_not_overtime() + if self.employee_ids and self.mission_type.related_with_financial: + # move amounts to journal entries + if self.move_type == 'accounting': + if self.mission_type.account_id and self.mission_type.journal_id: + for item in self.employee_ids: + if item.amount > 0.0: + debit_line_vals = { + 'name': item.employee_id.name + ' in official mission "%s" ' % self.mission_type.name, + 'debit': item.amount, + 'account_id': self.mission_type.account_id.id, + 'partner_id': item.employee_id.user_id.partner_id.id + } + credit_line_vals = { + 'name': item.employee_id.name + ' in official mission "%s" ' % self.mission_type.name, + 'credit': item.amount, + 'account_id': self.mission_type.journal_id.default_account_id.id, + 'partner_id': item.employee_id.user_id.partner_id.id + } + if not item.account_move_id: + move = self.env['account.move'].create({ + 'state': 'draft', + 'journal_id': self.mission_type.journal_id.id, + 'date': date.today(), + 'ref': 'Official mission for employee "%s" ' % item.employee_id.name, + 'line_ids': [(0, 0, debit_line_vals), (0, 0, credit_line_vals)], + 'res_model': 'hr.official.mission', + 'res_id': self.id + }) + # fill account move for each employee + item.write({'account_move_id': move.id}) + else: + raise exceptions.Warning( + _('You do not have account or journal in mission type "%s" ') % self.mission_type.name) + + # move amounts to advantages of employee in contract + elif self.move_type == 'payroll': + # get start and end date of the current month + current_date = date.today() + month_start = date(current_date.year, current_date.month, 1) + month_end = date(current_date.year, current_date.month, calendar.mdays[current_date.month]) + for line in self.employee_ids: + if line.sudo().employee_id.contract_id: + + advantage_arc = line.env['contract.advantage'].create({ + 'benefits_discounts': self.official_mission.id, + 'date_from': month_start, + 'date_to': month_end, + 'amount': line.amount, + 'official_mission_id': True, + 'employee_id': line.employee_id.id, + 'contract_advantage_id': line.sudo().employee_id.contract_id.id, + 'out_rule': True, + 'state': 'confirm', + 'comments': self.mission_purpose}) + line.advantage_id = advantage_arc.id + else: + raise exceptions.Warning(_( + 'Employee "%s" has no contract Please create contract to add line to advantages') + % line.employee_id.name) + + for item in self: + # create ticket request from all employee + if item.issuing_ticket == 'yes': + for emp in item.employee_ids: + ticket = self.env['hr.ticket.request'].create({ + 'employee_id': emp.employee_id.id, + 'mission_request_id': item.id, + 'mission_check': True, + 'request_for': item.ticket_cash_request_for, + 'request_type': item.ticket_cash_request_type.id, + 'cost_of_tickets': item.get_ticket_cost(emp.employee_id), + 'destination': item.destination.id, + }) + item.write({'ticket_request_id': ticket.id}) + + # move invoice training cost our trining center + if item.Training_cost > 0: + if not self.mission_type.pr_product_id.id: + raise ValidationError(_("You must Enter Purchase Product in Training Type Configuration")) + + product_line = { + 'product_id': self.mission_type.pr_product_id.id, + 'qty': 1, + 'expected_price': self.Training_cost, + } + + purchase_request = self.env['purchase.request'].create({ + 'state': 'draft', + 'department_id': self.department_id2.id, + 'date': date.today(), + 'employee_id': self.employee_id.id, + 'partner_id': self.partner_id.id, + 'product_category_ids': [(4, self.mission_type.pr_product_id.categ_id.id)], + 'purchase_purpose': self.training_details, + 'line_ids': [(0, 0, product_line)] + }) + + self.purchase_request_id = purchase_request.id + + self.state = "approve" + if self.mission_type.work_state and self.mission_type.duration_type == 'days': + for emp in self.employee_ids: + if emp.date_to >= fields.Date.today() >= emp.date_from: + emp.employee_id.write({'work_state': self.mission_type.work_state, 'active_mission_id': emp.id}) + self.call_cron_function() + + + + + + def draft_state(self): + res = super(HrOfficialMission, self).draft_state() + if self.purchase_request_id: + self.purchase_request_id.sudo().unlink() + + return res + + + diff --git a/hr_training_payment/models/mission_type.py b/hr_training_payment/models/mission_type.py new file mode 100644 index 0000000..ee23287 --- /dev/null +++ b/hr_training_payment/models/mission_type.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from odoo import models, fields, api + + +class HrOfficialMissionType(models.Model): + _inherit = 'hr.official.mission.type' + pr_product_id = fields.Many2one(comodel_name='product.product', string="PR Product") + diff --git a/hr_training_payment/views/hr_official_mission.xml b/hr_training_payment/views/hr_official_mission.xml new file mode 100644 index 0000000..b2b3a1d --- /dev/null +++ b/hr_training_payment/views/hr_official_mission.xml @@ -0,0 +1,23 @@ + + + + + + + hr.official.mission.view.form + hr.official.mission + + + + + + + + True + + + + + + + diff --git a/hr_training_payment/views/mission_type.xml b/hr_training_payment/views/mission_type.xml new file mode 100644 index 0000000..036f4e2 --- /dev/null +++ b/hr_training_payment/views/mission_type.xml @@ -0,0 +1,19 @@ + + + + + + + hr.official.mission.type.view.form + hr.official.mission.type + + + + + + + + + + + diff --git a/to_attendance_system/__init__.py b/to_attendance_system/__init__.py new file mode 100644 index 0000000..bbc5580 --- /dev/null +++ b/to_attendance_system/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models +from . import wizard diff --git a/to_attendance_system/__manifest__.py b/to_attendance_system/__manifest__.py new file mode 100644 index 0000000..661ac88 --- /dev/null +++ b/to_attendance_system/__manifest__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Attendance system", + + 'summary': """ + Short (1 phrase/line) summary of the module's purpose, used as + subtitle on modules listing or apps.openerp.com""", + + 'description': """ + Long description of module's purpose + """, + + 'author': "Expert", + 'website': "http://www.exp-sa.com", + + 'category':'Odex25-HR/Odex25-HR', + 'version': '18.0.0.1', + + # any module necessary for this one to work correctly + 'depends': ['base'], + + # always loaded + 'data': [ + 'security/ir.model.access.csv', + 'views/views.xml', + # 'wizard/attendaces_wizard.xml', + 'data/scheduler_data.xml', + ] +} diff --git a/to_attendance_system/controllers/__init__.py b/to_attendance_system/controllers/__init__.py new file mode 100644 index 0000000..b0f26a9 --- /dev/null +++ b/to_attendance_system/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers diff --git a/to_attendance_system/controllers/controllers.py b/to_attendance_system/controllers/controllers.py new file mode 100644 index 0000000..098fa26 --- /dev/null +++ b/to_attendance_system/controllers/controllers.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from odoo import http + +# class ToAttendanceSystem(http.Controller): +# @http.route('/to_attendance_system/to_attendance_system/', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/to_attendance_system/to_attendance_system/objects/', auth='public') +# def list(self, **kw): +# return http.request.render('to_attendance_system.listing', { +# 'root': '/to_attendance_system/to_attendance_system', +# 'objects': http.request.env['to_attendance_system.to_attendance_system'].search([]), +# }) + +# @http.route('/to_attendance_system/to_attendance_system/objects//', auth='public') +# def object(self, obj, **kw): +# return http.request.render('to_attendance_system.object', { +# 'object': obj +# }) diff --git a/to_attendance_system/data/scheduler_data.xml b/to_attendance_system/data/scheduler_data.xml new file mode 100644 index 0000000..23ac98a --- /dev/null +++ b/to_attendance_system/data/scheduler_data.xml @@ -0,0 +1,25 @@ + + + + + Synchronize attendances scheduler + + + code + 30 + minutes + model.cron_sync_attendance() + + + + Download attendances scheduler + + + code + 30 + minutes + model.download_system_attendance() + + + + \ No newline at end of file diff --git a/to_attendance_system/demo/demo.xml b/to_attendance_system/demo/demo.xml new file mode 100644 index 0000000..abe08c3 --- /dev/null +++ b/to_attendance_system/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/to_attendance_system/i18n/ar_001.po b/to_attendance_system/i18n/ar_001.po new file mode 100644 index 0000000..b34b780 --- /dev/null +++ b/to_attendance_system/i18n/ar_001.po @@ -0,0 +1,567 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * to_attendance_system +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-09-19 10:31+0000\n" +"PO-Revision-Date: 2022-09-19 10:31+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__alias +msgid "Alias" +msgstr "الاسم المستعار" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__area_alias +msgid "Area" +msgstr "المنطقة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__area_id +msgid "Area id" +msgstr "معرف المنطقة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__area_name +msgid "Area name" +msgstr "اسم المنطقة" + +#. module: to_attendance_system +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems +msgid "Attendance Systems" +msgstr "أنظمة الحضور والانصراف" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_attendance_wizard +msgid "Attendance Wizard" +msgstr "معالج الحضور" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__attendance_id +msgid "Attendance id" +msgstr "معرف الحضور" + +#. module: to_attendance_system +#: model:ir.ui.menu,name:to_attendance_system.attendace_system_menu +msgid "Attendance systems" +msgstr "أنظمة الحضور" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__attendance_ids +msgid "Attendances" +msgstr "سجلات الحضور" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/models/models.py:0 +#: model:ir.model.fields.selection,name:to_attendance_system.selection__finger_biotime_api__state__authentic +#, python-format +msgid "Authentic" +msgstr "مصادق عليه" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__crc +msgid "CRC" +msgstr "كود التحقق" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__code +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__code +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__code +msgid "Code" +msgstr "الكود" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/models/models.py:0 +#, python-format +msgid "Confirmed" +msgstr "مؤكد" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__create_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__create_uid +msgid "Created by" +msgstr "أنشئ بواسطة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__create_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__create_date +msgid "Created on" +msgstr "تاريخ الإنشاء" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__department_id +msgid "Department id" +msgstr "معرف القسم" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__description +msgid "Description" +msgstr "الوصف" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__display_name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Download areas" +msgstr "تنزيل المناطق" + +#. module: to_attendance_system +#: model:ir.actions.server,name:to_attendance_system.ir_cron_scheduler_download_system_attendance_ir_actions_server +#: model:ir.cron,cron_name:to_attendance_system.ir_cron_scheduler_download_system_attendance +#: model:ir.cron,name:to_attendance_system.ir_cron_scheduler_download_system_attendance +msgid "Download attendances scheduler" +msgstr "جدولة تنزيل سجلات الحضور" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Download attenence" +msgstr "تنزيل الحضور" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Download departments" +msgstr "تنزيل الأقسام" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Download employees" +msgstr "تنزيل الموظفين" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Download postions" +msgstr "تنزيل الوظائف" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Download terminals" +msgstr "تنزيل أجهزة البصمة" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/models/models.py:0 +#, python-format +msgid "Draft" +msgstr "مسودة" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_system_employee_position_action +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems_employee_position +msgid "Employee Positions" +msgstr "وظائف الموظفين" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_system_employee_areas_action +msgid "Employee areas" +msgstr "مناطق الموظفين" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_system_employee_department_action +msgid "Employee departments" +msgstr "أقسام الموظفين" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__emp_system_id +msgid "Employee id" +msgstr "معرف الموظف" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__employee_ids +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems_employee +msgid "Employees" +msgstr "الموظفون" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__hr_employee +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__hr_comployee_id +msgid "Empolyee Name" +msgstr "اسم الموظف" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__emp +msgid "Empolyee code" +msgstr "كود الموظف" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__server_id +msgid "Hr empolyee" +msgstr "موظف الموارد البشرية" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__id +msgid "ID" +msgstr "المعرف" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__serverUrl +msgid "IP / Domain Name" +msgstr "عنوان IP / اسم النطاق" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/models/models.py:0 +#, python-format +msgid "Invalid port number" +msgstr "رقم المنفذ غير صحيح" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/models/models.py:0 +#, python-format +msgid "Invalid server url" +msgstr "عنوان الخادم غير صحيح" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__is_attendance +msgid "Is attendance" +msgstr "حضور" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance____last_update +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__write_uid +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__write_uid +msgid "Last Updated by" +msgstr "آخر تحديث بواسطة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__write_date +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__write_date +msgid "Last Updated on" +msgstr "آخر تحديث في" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__last_activity +msgid "Last activity" +msgstr "آخر نشاط" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__loaded_employees +msgid "Loaded Employees" +msgstr "الموظفون المحملون" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Login" +msgstr "تسجيل الدخول" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Logout" +msgstr "تسجيل الخروج" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__name +msgid "Name" +msgstr "الاسم" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/models/models.py:0 +#: model:ir.model.fields.selection,name:to_attendance_system.selection__finger_biotime_api__state__unauthentic +#, python-format +msgid "Not authentic" +msgstr "غير مصادق عليه" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__password +msgid "Password" +msgstr "كلمة المرور" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__port +msgid "Port" +msgstr "المنفذ" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__position_id +msgid "Position id" +msgstr "معرف الوظيفة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__punch_state +msgid "Punch state" +msgstr "حالة البصمة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__punch_state_display +msgid "Punch state display" +msgstr "عرض حالة البصمة" + + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__punch_time +msgid "Punch time" +msgstr "وقت البصمة" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "Refresh" +msgstr "تحديث" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__sn +msgid "SN" +msgstr "الرقم التسلسلي" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__state +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__state +msgid "State" +msgstr "الحالة" + +#. module: to_attendance_system +#: model:ir.actions.server,name:to_attendance_system.ir_cron_scheduler_sync_system_attendance_ir_actions_server +#: model:ir.cron,cron_name:to_attendance_system.ir_cron_scheduler_sync_system_attendance +#: model:ir.cron,name:to_attendance_system.ir_cron_scheduler_sync_system_attendance +msgid "Synchronize attendances scheduler" +msgstr "جدولة مزامنة سجلات الحضور" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_attendance_wizard__system_ids +msgid "System" +msgstr "النظام" + +#. module: to_attendance_system +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems_employee_areas +msgid "System Areas" +msgstr "مناطق النظام" + +#. module: to_attendance_system +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems_employee_attendance +msgid "System Attendance" +msgstr "حضور النظام" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__department_id +msgid "System Deparment" +msgstr "قسم النظام" + +#. module: to_attendance_system +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems_employee_department +msgid "System Departments" +msgstr "أقسام النظام" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__name +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__emp_code +msgid "System Employee Name" +msgstr "اسم موظف النظام" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_system_employee_action +msgid "System Employees" +msgstr "موظفو النظام" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__position_id +msgid "System Position" +msgstr "وظيفة النظام" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_system_terminal_action +#: model:ir.ui.menu,name:to_attendance_system.attendance_systems_terminal +msgid "System Terminals" +msgstr "أجهزة النظام" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_system_attendance_action +msgid "System attendances" +msgstr "سجلات حضور النظام" + +#. module: to_attendance_system +#: model:ir.actions.act_window,name:to_attendance_system.biotime_action +msgid "System connector" +msgstr "موصل النظام" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__code +msgid "System employee Code" +msgstr "كود موظف النظام" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__terminal_id +msgid "Termainl id" +msgstr "معرف الجهاز" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__terminal_id +msgid "Terminal" +msgstr "الجهاز" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__name +msgid "Terminal name" +msgstr "اسم الجهاز" + +#. module: to_attendance_system +#: model_terms:ir.ui.view,arch_db:to_attendance_system.biotime_action_form +msgid "To system attendance" +msgstr "إلى حضور النظام" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__token +msgid "Token" +msgstr "الرمز المميز" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__upload_time +msgid "Upload time" +msgstr "وقت الرفع" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__username +msgid "Username" +msgstr "اسم المستخدم" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__verify_type +msgid "Verify type" +msgstr "نوع التحقق" + +#. module: to_attendance_system +#: code:addons/to_attendance_system/wizard/attendaces_wizard.py:0 +#, python-format +msgid "You must select at least one device to continue!" +msgstr "يجب اختيار جهاز واحد على الأقل للمتابعة!" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_employee__email +msgid "email" +msgstr "البريد الإلكتروني" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_area +msgid "finger.area" +msgstr "finger.area" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_biotime_api +msgid "finger.biotime_api" +msgstr "finger.biotime_api" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_employee_department +msgid "finger.employee.department" +msgstr "finger.employee.department" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_employee_employee +msgid "finger.employee.employee" +msgstr "finger.employee.employee" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_employee_position +msgid "finger.employee.position" +msgstr "finger.employee.position" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_system_attendance +msgid "finger.system_attendance" +msgstr "finger.system_attendance" + +#. module: to_attendance_system +#: model:ir.model,name:to_attendance_system.model_finger_terminal +msgid "finger.terminal" +msgstr "finger.terminal" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__ip_address +msgid "ip address" +msgstr "عنوان IP" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_area__server_id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_department__server_id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_employee_position__server_id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_system_attendance__server_id +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_terminal__server_id +msgid "server" +msgstr "الخادم" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__terminals_ids +msgid "terminals" +msgstr "الأجهزة" + +#. module: to_attendance_system +#: model:ir.model.fields.selection,name:to_attendance_system.selection__finger_system_attendance__state__confirmed +msgid "مؤكد" +msgstr "مؤكد" + +#. module: to_attendance_system +#: model:ir.model.fields.selection,name:to_attendance_system.selection__finger_system_attendance__state__draft +msgid "مسوده" +msgstr "مسودة" + +#. module: to_attendance_system +#: model:ir.model.fields,field_description:to_attendance_system.field_finger_biotime_api__start_sync_date +msgid "Start Sync Date" +msgstr "تاريخ بدء المزامنة" + + +#. module: to_attendance_system +#: code:addons/odoo/STANDARD_MODULES/test/odex25_hr/odex25_hr/to_attendance_system/models/models.py:0 +#: code:addons/to_attendance_system/models/models.py:0 +#, python-format +msgid "Start Sync Date cannot be in the future. Please select now or a past date." +msgstr "لا يمكن أن يكون تاريخ بدء المزامنة في المستقبل. الرجاء اختيار الوقت الحالي أو تاريخ سابق." diff --git a/to_attendance_system/models/__init__.py b/to_attendance_system/models/__init__.py new file mode 100644 index 0000000..cde864b --- /dev/null +++ b/to_attendance_system/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/to_attendance_system/models/helper.py b/to_attendance_system/models/helper.py new file mode 100644 index 0000000..ad764ab --- /dev/null +++ b/to_attendance_system/models/helper.py @@ -0,0 +1,106 @@ +import requests +import json +from urllib.parse import urlparse + + +def is_valid_port(port): + try: + port = int(port) + if 1 <= port <= 65535: + return True + else: + return False + except Exception: + return False + + +def is_valid_ip(url): + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + +headers = {'Content-Type': 'application/json'} + + +class ParsedRequest(object): + def __init__(self, data): + self.__dict__ = json.loads(data) + + +# serverIP = "http://160.153.0.122:8008" +# loginUrl = serverIP + "/jwt-api-token-auth/" +# refreshUrl = serverIP + "/jwt-api-token-refresh/" +# employeeUrl = serverIP + "/personnel/api/employee/" +# departmentsUrl = serverIP + "/personnel/api/departments/" +# terminalsUrl = serverIP + "/iclock/api/terminals/" +# areasUrl = serverIP + "/personnel/api/areas/" +# positionsUrl = serverIP + "/personnel/api/positions/" +# transctionsUrl = serverIP + "/iclock/api/transactions/" +# defaultHeaders = {'Content-Type': 'application/json'} +defaultHeaders = {'Content-Type': 'application/json'} + + +class HttpHelper(object): + + def login(self, username, password, url, headers=defaultHeaders): + data = {'username': username, 'password': password} + return requests.post(url, data=json.dumps(data), headers=headers) + + def refresh(self, token, url, headers=defaultHeaders): + data = {'token': token} + return requests.post(url, data=json.dumps(data), headers=headers) + + def fetch_employees(self, data, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, data=json.dumps(data), headers=headers) + + def fetch_departments(self, data, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, data=json.dumps(data), headers=headers) + + def fetch_terminals(self, data, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, data=json.dumps(data), headers=headers) + + def fetch_areas(self, data, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, data=json.dumps(data), headers=headers) + + def fetch_positions(self, data, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, data=json.dumps(data), headers=headers) + + def fetch_transctions(self, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, headers=headers) + + def fetch_empl_transctions(self, data, token, url): + headers = { + 'Authorization': 'JWT ' + token, + 'Content-Type': 'application/json' + } + return requests.get(url, data=json.dumps(data), headers=headers) + + +httpHelper = HttpHelper() diff --git a/to_attendance_system/models/models.py b/to_attendance_system/models/models.py new file mode 100644 index 0000000..eba1d6a --- /dev/null +++ b/to_attendance_system/models/models.py @@ -0,0 +1,587 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime, timedelta + +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +from .helper import httpHelper, is_valid_port, is_valid_ip, ParsedRequest + + +class SystemAttendance(models.Model): + _name = 'finger.system_attendance' + _order = 'punch_time DESC' + + punch_state = fields.Char("Punch state", readonly=True) + punch_state_display = fields.Char("Punch state display", readonly=True) + area_alias = fields.Char("Area", readonly=True) + crc = fields.Char("CRC", readonly=True) + verify_type = fields.Integer("Verify type", readonly=True) + emp = fields.Integer("Empolyee code", readonly=True) + is_attendance = fields.Integer("Is attendance", readonly=True) + attendance_id = fields.Integer("Attendance id", readonly=True) + upload_time = fields.Datetime(string='Upload time', readonly=True) + punch_time = fields.Datetime(string="Punch time", readonly=True) + emp_code = fields.Many2one('finger.employee.employee', string='System Employee Name', readonly=True) + terminal_id = fields.Many2one('finger.terminal', string='Terminal', readonly=True) + server_id = fields.Many2one('finger.biotime_api', string='server', readonly=True) + hr_comployee_id = fields.Many2one('hr.employee', string='Empolyee Name', readonly=True) + state = fields.Selection( + [('draft', _('Draft')), + ('confirmed', _('Confirmed'))], default="draft", readonly=True) + + +class SystemTerminal(models.Model): + _name = 'finger.terminal' + + name = fields.Char("Terminal name") + terminal_id = fields.Integer("Termainl id") + sn = fields.Char("SN") + area_name = fields.Char("Area name") + last_activity = fields.Char("Last activity") + ip_address = fields.Char("ip address") + alias = fields.Char("Alias") + server_id = fields.Many2one('finger.biotime_api', string='server') + + +class SystemEmployee(models.Model): + _name = 'finger.employee.employee' + + name = fields.Char("System Employee Name") + code = fields.Char("System employee Code") + emp_system_id = fields.Integer("Employee id") + email = fields.Char("email") + department_id = fields.Many2one('finger.employee.department', string='System Deparment') + position_id = fields.Many2one('finger.employee.position', string='System Position') + hr_employee = fields.Many2one('hr.employee', string='Empolyee Name') + server_id = fields.Many2one('finger.biotime_api', string='Hr empolyee') + + +class SystemEmployeePosition(models.Model): + _name = 'finger.employee.position' + + name = fields.Char("Name") + code = fields.Char("Code") + position_id = fields.Integer("Position id") + server_id = fields.Many2one('finger.biotime_api', string='server') + + +class SystemArea(models.Model): + _name = 'finger.area' + + name = fields.Char("Name") + code = fields.Char("Code") + area_id = fields.Integer("Area id") + server_id = fields.Many2one('finger.biotime_api', string='server') + + +class SystemDepartments(models.Model): + _name = 'finger.employee.department' + + name = fields.Char("Name") + code = fields.Char("Code") + department_id = fields.Integer("Department id") + server_id = fields.Many2one('finger.biotime_api', string='server') + + +class BiotimeAPI(models.Model): + _name = 'finger.biotime_api' + + name = fields.Char() + serverUrl = fields.Char(string="IP / Domain Name") + port = fields.Char(default="80") + username = fields.Char() + password = fields.Char() + token = fields.Char() + description = fields.Text() + loaded_employees = fields.Boolean(default=False) + + terminals_ids = fields.One2many( + string='terminals', + comodel_name='finger.terminal', + inverse_name='server_id', + ) + + attendance_ids = fields.One2many( + string='Attendances', + comodel_name='finger.system_attendance', + inverse_name='server_id', + ) + employee_ids = fields.One2many( + string='Employees', + comodel_name='finger.employee.employee', + inverse_name='server_id', + ) + + state = fields.Selection( + [('authentic', _('Authentic')), + ('unauthentic', _('Not authentic'))], default="unauthentic") + start_sync_date = fields.Datetime(string="Start Sync Date", default=lambda self: fields.Datetime.now()) + + @api.constrains('start_sync_date') + def _check_start_sync_date(self): + for record in self: + if record.start_sync_date and record.start_sync_date > fields.Datetime.now(): + raise ValidationError(_("Start Sync Date cannot be in the future. Please select now or a past date.")) + @api.model + def _calc_urls(self): + serverIP = self.serverUrl + ":" + self.port + loginUrl = serverIP + "/jwt-api-token-auth/" + refreshUrl = serverIP + "/jwt-api-token-refresh/" + employeeUrl = serverIP + "/personnel/api/employee/" + departmentsUrl = serverIP + "/personnel/api/departments/" + terminalsUrl = serverIP + "/iclock/api/terminals/" + areasUrl = serverIP + "/personnel/api/areas/" + positionsUrl = serverIP + "/personnel/api/positions/" + transctionsUrl = serverIP + "/iclock/api/transactions/" + return loginUrl, refreshUrl, employeeUrl, departmentsUrl, positionsUrl, is_valid_ip, areasUrl, terminalsUrl, ParsedRequest, transctionsUrl + + @api.depends('token') + def depends_token(self): + for srv in self: + if srv.token: + srv.state = 'authentic' + + @api.onchange('token') + def onchange_token(self): + for srv in self: + if srv.token: + srv.state = 'authentic' + + @api.constrains('serverUrl') + def check_is_valid_ip(self): + for srv in self: + if not is_valid_ip(srv.serverUrl): + raise ValidationError(_("Invalid server url")) + + @api.constrains('port') + def check_is_valid_ip(self): + for srv in self: + if not is_valid_port(srv.port): + raise ValidationError(_("Invalid port number")) + + def process_attendance_scheduler(self): + for tx in self.attendance_ids: + print(tx) + + def login(self): + loginUrl, _, _, _, _, _, _, _, _, _ = self._calc_urls() + res = httpHelper.login(self.username, self.password, loginUrl) + + if res.status_code == 200: + data = res.json() + token = data.get('token', False) + if token: + self.token = token + self.state = 'authentic' + else: + data = res.json() + err = "" + for key in data: + err += ' '.join(data[key]) + raise ValidationError(err) + + def refresh(self): + _, refreshUrl, _, _, _, _, _, _, _, _ = self._calc_urls() + res = httpHelper.refresh(self.token, refreshUrl) + if res.status_code == 200: + data = res.json() + token = data.get('token', False) + if token: + self.token = token + else: + data = res.json() + err = "" + for key in data: + err += ' '.join(data[key]) + raise ValidationError(err) + + def logout(self): + self.token = False + self.state = 'unauthentic' + + def to_attendace(self): + Attend = self.env['finger.system_attendance'] + attendance = self.env['attendance.attendance'] + HR = self.env['hr.employee'] + attens = Attend.search([('server_id', '=', self.id), ('state', '=', 'draft')]) + for tx in attens: + if tx.emp_code and tx.emp_code.hr_employee: + attendance.create({ + 'employee_id': tx.emp_code.hr_employee.id, + 'name': tx.punch_time, + 'action': 'sign_in' if tx.punch_state in ["0", "2", "4"] else 'sign_out', + 'action_date': tx.punch_time, + }) + tx.write({ + 'state': 'confirmed' + }) + + def sync_employees(self): + if not self.token: + self.refresh() + _, _, employeeUrl, _, _, _, _, _, _, _ = self._calc_urls() + res = httpHelper.fetch_employees({}, self.token, employeeUrl) + + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_employee(da.data) + next = 2 + while da.next: + r = httpHelper.fetch_employees( + {}, self.token, employeeUrl + "?page=" + str(next)) + if r.status_code == 200: + da = ParsedRequest(r.content) + self.create_employee(da.data) + else: + da.next = None + next = next + 1 + self.loaded_employees = True + else: + self.errorHandler(res) + + def errorHandler(self, res): + if res.status_code == 401: + self.logout() + else: + data = res.json() + err = "" + for key in data: + err += ' '.join(data[key]) + raise ValidationError(err) + + def sync_terminals(self): + if not self.token: + self.refresh() + _, _, _, _, _, _, _, terminalsUrl, _, _ = self._calc_urls() + res = httpHelper.fetch_terminals({}, self.token, terminalsUrl) + + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_termainl(da.data) + next = 2 + while da.next: + r = httpHelper.fetch_terminals( + {}, self.token, terminalsUrl + "?page=" + str(next)) + if r.status_code == 200: + da = ParsedRequest(r.content) + self.create_termainl(da.data) + else: + da.next = None + next = next + 1 + else: + self.errorHandler(res) + + def sync_departments(self): + if not self.token: + self.refresh() + _, _, _, departmentsUrl, _, _, _, _, _, _ = self._calc_urls() + res = httpHelper.fetch_departments({}, self.token, departmentsUrl) + + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_department(da.data) + next = 2 + while da.next: + r = httpHelper.fetch_departments( + {}, self.token, departmentsUrl + "?page=" + str(next)) + if r.status_code == 200: + da = ParsedRequest(r.content) + self.create_department(da.data) + else: + da.next = None + next = next + 1 + else: + self.errorHandler(res) + + def sync_areas(self): + if not self.token: + self.refresh() + _, _, _, _, _, _, areasUrl, _, _, _ = self._calc_urls() + res = httpHelper.fetch_areas({}, self.token, areasUrl) + try: + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_area(da.data) + next = 2 + while da.next: + res = httpHelper.fetch_areas( + {}, self.token, areasUrl + "?page=" + str(next)) + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_area(da.data) + else: + da.next = None + next = next + 1 + else: + self.errorHandler(res) + except Exception as e: + ValidationError(str(e)) + + def sync_positions(self): + if not self.token: + self.refresh() + _, _, _, _, positionsUrl, _, _, _, _, _ = self._calc_urls() + res = httpHelper.fetch_positions({}, self.token, positionsUrl) + + try: + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_position(da.data) + next = 2 + while da.next: + res = httpHelper.fetch_positions( + {}, self.token, positionsUrl + "?page=" + str(next)) + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_position(da.data) + else: + da.next = None + next = next + 1 + else: + self.errorHandler(res) + except Exception as e: + ValidationError(str(e)) + + def sync_employee_attenence(self, url): + if not self.token: + self.refresh() + res = httpHelper.fetch_empl_transctions({}, self.token, url) + + try: + if res.status_code == 200: + da = ParsedRequest(res.content) + next = 2 + self.create_attendance(da.data) + while da.next: + res = httpHelper.fetch_empl_transctions({}, self.token, da.next) + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_attendance(da.data) + else: + da.next = None + next = next + 1 + else: + self.errorHandler(res) + if res.status_code == 401: + self.refresh() + if self.token: + self.sync_employee_attenence(url) + except Exception as e: + ValidationError(str(e)) + + def sync_attenence(self): + self.login() + if not self.token: + self.refresh() + _, _, _, _, _, _, _, _, _, transctionsUrl = self._calc_urls() + + start_date = self.start_sync_date.strftime( + '%Y-%m-%d %H:%M:%S') if self.start_sync_date else "2025-10-01 00:00:00" + + url_with_filter = transctionsUrl + "?start_time=" + start_date + + res = httpHelper.fetch_empl_transctions({}, self.token, url_with_filter) + if not self.loaded_employees: + self.sync_employees() + + try: + if res.status_code == 200: + da = ParsedRequest(res.content) + next = 2 + self.create_attendance(da.data) + while da.next: + res = httpHelper.fetch_empl_transctions( + {}, self.token, transctionsUrl + "?start_time=" + start_date + "&page=" + str(next)) + if res.status_code == 200: + da = ParsedRequest(res.content) + self.create_attendance(da.data) + else: + da.next = None + next = next + 1 + else: + self.errorHandler(res) + except Exception as e: + ValidationError(str(e)) + + # def sync_attenence(self): + # self.login() + # if not self.token: + # self.refresh() + # _, _, _, _, _, _, _, _, _, transctionsUrl = self._calc_urls() + # res = httpHelper.fetch_empl_transctions({}, self.token, transctionsUrl) + # if not self.loaded_employees: + # self.sync_employees() + # + # try: + # if res.status_code == 200: + # da = ParsedRequest(res.content) + # next = 2 + # self.create_attendance(da.data) + # while da.next: + # res = httpHelper.fetch_empl_transctions( + # {}, self.token, transctionsUrl + "?page=" + str(next)) + # if res.status_code == 200: + # da = ParsedRequest(res.content) + # self.create_attendance(da.data) + # else: + # da.next = None + # next = next + 1 + # else: + # self.errorHandler(res) + # except Exception as e: + # ValidationError(str(e)) + + + def create_termainl(self, termainls): + TerminalsModel = self.env['finger.terminal'] + for tx in termainls: + tirm = TerminalsModel.search([('terminal_id', '=', tx['id'])]) + data = { + 'terminal_id': tx['id'], + 'name': tx['terminal_name'], + 'sn': tx['sn'], + 'area_name': tx['area_name'], + 'last_activity': tx['last_activity'], + 'ip_address': tx['ip_address'], + 'alias': tx['alias'], + 'server_id': self.id + } + if not tirm: + TerminalsModel.create(data) + else: + tirm.update(data) + + def create_area(self, areas): + TerminalsModel = self.env['finger.area'] + for tx in areas: + tirm = TerminalsModel.search([('area_id', '=', tx['id'])]) + data = { + 'area_id': tx['id'], + 'name': tx['area_name'], + 'code': tx['area_code'], + 'server_id': self.id + } + if not tirm: + TerminalsModel.create(data) + else: + tirm.update(data) + + def create_department(self, departments): + TerminalsModel = self.env['finger.employee.department'] + for tx in departments: + tirm = TerminalsModel.search([('department_id', '=', tx['id'])]) + data = { + 'department_id': tx['id'], + 'name': tx['dept_name'], + 'code': tx['dept_code'], + 'server_id': self.id + } + if not tirm: + TerminalsModel.create(data) + else: + tirm.update(data) + + def create_position(self, positions): + PostionModel = self.env['finger.employee.position'] + for tx in positions: + tirm = PostionModel.search([('position_id', '=', tx['id'])]) + data = { + 'position_id': tx['id'], + 'name': tx['position_name'], + 'code': tx['position_code'], + 'server_id': self.id + } + if not tirm: + PostionModel.create(data) + else: + tirm.update(data) + + def create_employee(self, employees): + EmployeeModel = self.env['finger.employee.employee'] + DepartmentModel = self.env['finger.employee.department'] + PositionModel = self.env['finger.employee.position'] + HR = self.env['hr.employee'] + + for tx in employees: + tirm = EmployeeModel.search([('emp_system_id', '=', tx['id'])]) + dep = None + pos = None + hrEmp = None + if tx['position']: + pos = PositionModel.search([('position_id', '=', tx['position'])], limit=1) + + if tx['department']: + dep = DepartmentModel.search([('department_id', '=', tx['department'])], limit=1) + + if tx['emp_code']: + hrEmp = HR.search([('emp_no', '=', tx['emp_code'])]) + + data = { + 'emp_system_id': tx['id'], + 'name': tx['first_name'], + 'code': tx['emp_code'], + 'department_id': dep.id if dep else False, + 'position_id': pos.id if pos else False, + 'hr_employee': hrEmp.id if hrEmp else False, + 'server_id': self.id, + } + + if not tirm and hrEmp: + EmployeeModel.create(data) + elif hrEmp and tirm: + tirm.update(data) + + def create_attendance(self, attendaces): + AttendanceModel = self.env['finger.system_attendance'] + TerminalModel = self.env['finger.terminal'] + EmployeeModel = self.env['finger.employee.employee'] + for tx in attendaces: + tirm = AttendanceModel.search([('attendance_id', '=', tx['id'])]) + empe = None + pos = None + # "2020-08-09 10:23:20" + new_punch_time = datetime.strptime(tx['punch_time'], "%Y-%m-%d %H:%M:%S") + timedelta(hours=-3) + new_upload_time = datetime.strptime(tx['upload_time'], "%Y-%m-%d %H:%M:%S") + timedelta(hours=-3) + # datetime_format = "%Y-%m-%d %H:%M:%S" + + if tx['emp_code']: + empe = EmployeeModel.search([('code', '=', tx['emp_code'])], limit=1) + + if tx['terminal_alias']: + dep = TerminalModel.search([('sn', '=', tx['terminal_sn'])], limit=1) + data = { + 'attendance_id': tx['id'], + 'punch_state': tx['punch_state'], + 'punch_state_display': tx['punch_state_display'], + 'area_alias': tx['area_alias'], + # 'crc': tx['crc'], + 'crc': tx['emp'], + 'verify_type': tx['verify_type'], + # 'is_attendance': tx['is_attendance'], + 'upload_time': new_upload_time, + 'punch_time': new_punch_time, + 'emp_code': empe.id if empe else None, + 'terminal_id': dep.id if dep else None, + 'server_id': self.id, + 'hr_comployee_id': empe.hr_employee.id if empe and empe.hr_employee else None, + 'emp': empe.code if empe else None + } + + if not tirm and empe: + # print('9999999999999999999999999999999999999999999999999',tirm) + AttendanceModel.create(data) + elif empe and tirm: + tirm.update(data) + + def action_attendance_download(self): + now = datetime.now() + yesterday = datetime.now() - timedelta(hours=48) + now = now.strftime("%Y-%m-%d %H:%M:%S") + yesterday = yesterday.strftime("%Y-%m-%d %H:%M:%S") + _, _, _, _, _, _, _, _, _, transctionsUrl = self._calc_urls() + for r in self: + for xm in r.employee_ids: + url = "{}?start_time={}&end_time={}&emp_code={}".format(transctionsUrl,yesterday,now, xm.code ) + # url = "{}?punch_time={}&punch_time={}&emp_code={}".format(transctionsUrl, yesterday, now, xm.code) + # print('################################# New Employee',url) + r.sync_employee_attenence(url) diff --git a/to_attendance_system/security/ir.model.access.csv b/to_attendance_system/security/ir.model.access.csv new file mode 100644 index 0000000..8db367f --- /dev/null +++ b/to_attendance_system/security/ir.model.access.csv @@ -0,0 +1,8 @@ +id,name,model_id:id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_finger_system_attendance,finger_system_attendance,to_attendance_system.model_finger_system_attendance,,1,1,1,0 +access_finger_biotime_api,finger_biotime_api,to_attendance_system.model_finger_biotime_api,,1,1,1,0 +access_finger_employee_department,finger_employee_department,to_attendance_system.model_finger_employee_department,,1,1,1,0 +access_finger_area,finger_area,to_attendance_system.model_finger_area,,1,1,1,0 +access_finger_employee_position,finger_employee_position,to_attendance_system.model_finger_employee_position,,1,1,1,0 +access_finger_employee_employee,finger_employee_employee,to_attendance_system.model_finger_employee_employee,,1,1,1,0 +access_finger_terminal,finger_terminal,to_attendance_system.model_finger_terminal,,1,1,1,0 diff --git a/to_attendance_system/views/views.xml b/to_attendance_system/views/views.xml new file mode 100644 index 0000000..a1e6355 --- /dev/null +++ b/to_attendance_system/views/views.xml @@ -0,0 +1,248 @@ + + + + + + + finger.biotime_api.form + finger.biotime_api + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + finger.biotime_api.tree + finger.biotime_api + + + + + + + + + + + + finger.terminal.tree + finger.terminal + + + + + + + + + + + + finger.terminal.tree + finger.terminal + + + + + + + + + + + + System connector + finger.biotime_api + list,form + + + + + System Terminals + finger.terminal + list,form + + + + + + + + + + + finger.area.tree + finger.area + + + + + + + + + + + + Employee areas + finger.area + list,form + + + + + + + + + finger.employee.department.tree + finger.employee.department + + + + + + + + + + + + Employee departments + finger.employee.department + list,form + + + + + + + + + finger.employee.position.tree + finger.employee.position + + + + + + + + + + + + Employee Positions + finger.employee.position + list,form + + + + + + + + + finger.employee.employee.tree + finger.employee.employee + + + + + + + + + + + + System Employees + finger.employee.employee + list,form + + + + + + + + + finger.system_attendance.tree + finger.system_attendance + + + + + + + + + + + + + + + + + + + + System attendances + finger.system_attendance + list,form + + + + + + +
+
diff --git a/to_attendance_system/wizard/__init__.py b/to_attendance_system/wizard/__init__.py new file mode 100644 index 0000000..5ad064f --- /dev/null +++ b/to_attendance_system/wizard/__init__.py @@ -0,0 +1 @@ +from . import attendaces_wizard \ No newline at end of file diff --git a/to_attendance_system/wizard/attendaces_wizard.py b/to_attendance_system/wizard/attendaces_wizard.py new file mode 100644 index 0000000..a2333ec --- /dev/null +++ b/to_attendance_system/wizard/attendaces_wizard.py @@ -0,0 +1,53 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + +import logging + +_logger = logging.getLogger(__name__) + + +class AttendanceWizard(models.TransientModel): + _name = 'finger.attendance_wizard' + _description = 'Attendance Wizard' + + @api.model + def _get_all_systems_ids(self): + all_systems = self.env['finger.biotime_api'].search([('state', '=', 'authentic')]) + if all_systems: + return all_systems.ids + else: + return [] + + system_ids = fields.Many2many('finger.biotime_api', string='System', default=_get_all_systems_ids, + domain=[('state', '=', 'confirmed')]) + + def download_attendance_manually(self): + if not self.system_ids: + raise UserError(_('You must select at least one device to continue!')) + self.system_ids.action_attendance_download() + + def download_system_attendance(self): + systems = self.env['finger.biotime_api'].search([('state', '=', 'authentic')]) + systems.action_attendance_download() + + def cron_sync_attendance(self): + self.with_context(synch_ignore_constraints=True).sync_attendance() + + def sync_attendance(self): + + synch_ignore_constraints = self.env.context.get('synch_ignore_constraints', False) + HR = self.env['hr.employee'] + attendance = self.env['attendance.attendance'] + attendance_ids = self.env['finger.system_attendance'].search([('state', '=', 'draft')]) + + for tx in attendance_ids: + if tx.emp_code and tx.emp_code.hr_employee: + attendance.create({ + 'employee_id': tx.emp_code.hr_employee.id, + 'name': tx.punch_time, + 'action': 'sign_in' if tx.punch_state in ["0", "2", "4"] else 'sign_out', + 'action_date': tx.punch_time, + }) + tx.write({ + 'state': 'confirmed' + }) diff --git a/to_attendance_system/wizard/attendaces_wizard.xml b/to_attendance_system/wizard/attendaces_wizard.xml new file mode 100644 index 0000000..3c48232 --- /dev/null +++ b/to_attendance_system/wizard/attendaces_wizard.xml @@ -0,0 +1,42 @@ + + + + + + Attendance Device Synchronization wizard + finger.attendance_wizard + +
+
+ This wizard will synchronize all data from all of your systems + into Odoo. +
+ Download employee into Odoo; Map those with Odoo + Employees and create + additional Employees from device data; Download attendance data + from the devices and create Odoo attendance data from such data +
+ + + + + +
+
+
+
+
+ + + + + +
+
\ No newline at end of file