├── nl_attendance_timesheet
├── config
│ ├── __init__.py
│ ├── desktop.py
│ └── docs.py
├── public
│ ├── .gitkeep
│ └── js
│ │ └── payroll_entry.js
├── controllers
│ ├── __init__.py
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_generate_overtime_timesheets.py
│ │ ├── test_add_attendance_to_salary_slip.py
│ │ └── test_get_employee_attendance.py
│ ├── calculate_duration.py
│ ├── get_employee_attendance.py
│ ├── add_attendance_to_salary_slip.py
│ └── generate_overtime_timesheets.py
├── templates
│ ├── __init__.py
│ └── pages
│ │ └── __init__.py
├── modules.txt
├── __init__.py
├── nl_attendance_timesheet
│ ├── __init__.py
│ └── doctype
│ │ ├── __init__.py
│ │ ├── holiday_overtime
│ │ ├── __init__.py
│ │ ├── holiday_overtime.py
│ │ └── holiday_overtime.json
│ │ ├── navari_attendance
│ │ ├── __init__.py
│ │ ├── navari_attendance.py
│ │ └── navari_attendance.json
│ │ ├── regular_overtime
│ │ ├── __init__.py
│ │ ├── regular_overtime.py
│ │ └── regular_overtime.json
│ │ ├── timesheet_center
│ │ ├── __init__.py
│ │ ├── timesheet_center.py
│ │ ├── timesheet_center.js
│ │ ├── timesheet_center.json
│ │ └── test_timesheet_center.py
│ │ ├── earned_bonus_vs_attained_score
│ │ ├── __init__.py
│ │ ├── earned_bonus_vs_attained_score.py
│ │ └── earned_bonus_vs_attained_score.json
│ │ └── navari_custom_payroll_settings
│ │ ├── __init__.py
│ │ ├── navari_custom_payroll_settings.js
│ │ ├── navari_custom_payroll_settings.py
│ │ ├── test_navari_custom_payroll_settings.py
│ │ └── navari_custom_payroll_settings.json
├── patches.txt
├── hooks.py
└── fixtures
│ └── custom_field.json
├── .gitignore
├── pyproject.toml
├── README.md
└── LICENSE
/nl_attendance_timesheet/config/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/templates/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/templates/pages/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/modules.txt:
--------------------------------------------------------------------------------
1 | Nl Attendance Timesheet
--------------------------------------------------------------------------------
/nl_attendance_timesheet/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.0.1"
2 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/holiday_overtime/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_attendance/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/regular_overtime/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/timesheet_center/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/patches.txt:
--------------------------------------------------------------------------------
1 | [pre_model_sync]
2 |
3 |
4 | [post_model_sync]
5 |
6 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/earned_bonus_vs_attained_score/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_custom_payroll_settings/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 | *.egg-info
4 | *.swp
5 | tags
6 | nl_attendance_timesheet/docs/current
7 | node_modules/
--------------------------------------------------------------------------------
/nl_attendance_timesheet/config/desktop.py:
--------------------------------------------------------------------------------
1 | from frappe import _
2 |
3 | def get_data():
4 | return [
5 | {
6 | "module_name": "Nl Attendance Timesheet",
7 | "type": "module",
8 | "label": _("Nl Attendance Timesheet")
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/calculate_duration.py:
--------------------------------------------------------------------------------
1 | import frappe
2 | from frappe.utils import date_diff
3 |
4 | def calculate_duration(doc, method):
5 | if doc.completion_date and doc.start_date:
6 | doc.duration = date_diff(doc.completion_date, doc.start_date)
7 | else:
8 | doc.duration = 0
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/holiday_overtime/holiday_overtime.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023, Navari Limited and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class HolidayOvertime(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_attendance/navari_attendance.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2024, Navari Ltd and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class NavariAttendance(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/regular_overtime/regular_overtime.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023, Navari Limited and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class RegularOvertime(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_custom_payroll_settings/navari_custom_payroll_settings.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023, Navari Limited and contributors
2 | // For license information, please see license.txt
3 |
4 | frappe.ui.form.on("Navari Custom Payroll Settings", {
5 | // refresh: function(frm) {
6 | // }
7 | });
8 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/earned_bonus_vs_attained_score/earned_bonus_vs_attained_score.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2024, Navari Ltd and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class EarnedBonusvsAttainedScore(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_custom_payroll_settings/navari_custom_payroll_settings.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023, Navari Limited and contributors
2 | # For license information, please see license.txt
3 |
4 | # import frappe
5 | from frappe.model.document import Document
6 |
7 | class NavariCustomPayrollSettings(Document):
8 | pass
9 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_custom_payroll_settings/test_navari_custom_payroll_settings.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023, Navari Limited and Contributors
2 | # See license.txt
3 |
4 | # import frappe
5 | from frappe.tests.utils import FrappeTestCase
6 |
7 |
8 | class TestNavariCustomPayrollSettings(FrappeTestCase):
9 | pass
10 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/config/docs.py:
--------------------------------------------------------------------------------
1 | """
2 | Configuration for docs
3 | """
4 |
5 | # source_link = "https://github.com/[org_name]/nl_attendance_timesheet"
6 | # headline = "App that does everything"
7 | # sub_heading = "Yes, you got that right the first time, everything"
8 |
9 | def get_context(context):
10 | context.brand_html = "Nl Attendance Timesheet"
11 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/timesheet_center/timesheet_center.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2024, Navari Limited and contributors
2 | # For license information, please see license.txt
3 |
4 | import frappe
5 | from frappe.model.document import Document
6 |
7 | class TimesheetCenter(Document):
8 | def validate(self):
9 | if self.start_date > self.end_date:
10 | frappe.throw('Start date cannot be greater than end date')
11 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "nl_attendance_timesheet"
3 | authors = [
4 | { name = "Navari Ltd", email = "cheptoek@navari.co.ke"}
5 | ]
6 | description = "Navari Attendance Timesheet"
7 | requires-python = ">=3.10"
8 | readme = "README.md"
9 | dynamic = ["version"]
10 | dependencies = [
11 | # "frappe~=15.0.0" # Installed and managed by bench.
12 | ]
13 |
14 | [build-system]
15 | requires = ["flit_core >=3.4,<4"]
16 | build-backend = "flit_core.buildapi"
17 |
18 | # These dependencies are only installed when developer mode is enabled
19 | [tool.bench.dev-dependencies]
20 | # package_name = "~=1.1.0"
21 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/timesheet_center/timesheet_center.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2024, Navari Limited and contributors
2 | // For license information, please see license.txt
3 |
4 | frappe.ui.form.on("Timesheet Center", {
5 | refresh: function (frm) {
6 | if (frm.doc.start_date && frm.doc.end_date) {
7 | frm.add_custom_button(__("Generate Timesheets"), function () {
8 | generate_timesheets(frm);
9 | }).addClass("btn-secondary");
10 | }
11 | },
12 | });
13 |
14 | function generate_timesheets(frm) {
15 | frappe.call({
16 | method: "nl_attendance_timesheet.controllers.generate_overtime_timesheets.generate_overtime_timesheets",
17 | args: {
18 | start_date: frm.doc.start_date,
19 | end_date: frm.doc.end_date,
20 | },
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/public/js/payroll_entry.js:
--------------------------------------------------------------------------------
1 | frappe.ui.form.on("Payroll Entry", {
2 | refresh: function (frm) {
3 | if (frm.doc.salary_slips_created && frm.doc.status !== "Queued") {
4 | frm.add_custom_button(
5 | __("Update Timesheet into Salary Slips"),
6 | function () {
7 | fetch_attendance_data(frm);
8 | }
9 | );
10 | }
11 | },
12 | });
13 |
14 | function fetch_attendance_data(frm) {
15 | frappe.call({
16 | method:
17 | "nl_attendance_timesheet.controllers.add_attendance_to_salary_slip.add_attendance_data",
18 | args: {
19 | payroll_entry: frm.doc.name,
20 | },
21 | freeze: true,
22 | freeze_message: __("Updating Timesheet Data..."),
23 | callback: function (r) {
24 | if (r.message) {
25 | frappe.show_alert({
26 | message: r.message,
27 | indicator: "green",
28 | });
29 | frm.reload_doc();
30 | }
31 | },
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/holiday_overtime/holiday_overtime.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2023-10-04 10:11:35.702925",
5 | "doctype": "DocType",
6 | "editable_grid": 1,
7 | "engine": "InnoDB",
8 | "field_order": [
9 | "timesheet",
10 | "hours"
11 | ],
12 | "fields": [
13 | {
14 | "fieldname": "timesheet",
15 | "fieldtype": "Link",
16 | "in_list_view": 1,
17 | "label": "Timesheet",
18 | "options": "Timesheet",
19 | "reqd": 1
20 | },
21 | {
22 | "fieldname": "hours",
23 | "fieldtype": "Float",
24 | "in_list_view": 1,
25 | "label": "Hours"
26 | }
27 | ],
28 | "index_web_pages_for_search": 1,
29 | "istable": 1,
30 | "links": [],
31 | "modified": "2024-05-17 10:01:18.850307",
32 | "modified_by": "Administrator",
33 | "module": "Nl Attendance Timesheet",
34 | "name": "Holiday Overtime",
35 | "owner": "Administrator",
36 | "permissions": [],
37 | "sort_field": "modified",
38 | "sort_order": "DESC",
39 | "states": []
40 | }
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/tests/test_generate_overtime_timesheets.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch
2 |
3 | import frappe
4 | from frappe.tests.utils import FrappeTestCase
5 | from csf_ke.controllers.generate_overtime_timesheets import generate_overtime_timesheets
6 |
7 |
8 | class TestGenerateOvertimeTimesheets(FrappeTestCase):
9 | @classmethod
10 | def setUpClass(cls):
11 | super().setUpClass()
12 | frappe.delete_doc("Timesheet")
13 |
14 | @patch('navari_vf.controllers.generate_overtime_timesheets.frappe.db.get_single_value')
15 | def test_generate_overtime_timesheets_no_activities_set(self, mock_get_single_value):
16 | mock_get_single_value.return_value = None
17 | with self.assertRaises(Exception):
18 | generate_overtime_timesheets()
19 |
20 | def test_generate_overtime_timesheets_with_activities_set(self):
21 | generate_overtime_timesheets()
22 |
23 |
24 | def test_generate_overtime_timesheets_no_attendance_records(self):
25 | generate_overtime_timesheets()
26 |
27 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/regular_overtime/regular_overtime.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2023-10-04 10:04:12.872990",
5 | "default_view": "List",
6 | "doctype": "DocType",
7 | "editable_grid": 1,
8 | "engine": "InnoDB",
9 | "field_order": [
10 | "timesheet",
11 | "hours"
12 | ],
13 | "fields": [
14 | {
15 | "fieldname": "timesheet",
16 | "fieldtype": "Link",
17 | "in_list_view": 1,
18 | "label": "Timesheet",
19 | "options": "Timesheet",
20 | "reqd": 1
21 | },
22 | {
23 | "fieldname": "hours",
24 | "fieldtype": "Float",
25 | "in_list_view": 1,
26 | "label": "Hours"
27 | }
28 | ],
29 | "index_web_pages_for_search": 1,
30 | "istable": 1,
31 | "links": [],
32 | "modified": "2024-05-17 09:59:20.762927",
33 | "modified_by": "Administrator",
34 | "module": "Nl Attendance Timesheet",
35 | "name": "Regular Overtime",
36 | "owner": "Administrator",
37 | "permissions": [],
38 | "sort_field": "modified",
39 | "sort_order": "DESC",
40 | "states": []
41 | }
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/tests/test_add_attendance_to_salary_slip.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import patch
2 |
3 | import frappe
4 | from frappe.tests.utils import FrappeTestCase
5 | from csf_ke.controllers.add_attendance_to_salary_slip import get_holiday_dates
6 |
7 | class TestAddAttendanceToSalarySlip(FrappeTestCase):
8 | @classmethod
9 | def setUpClass(cls):
10 | super().setUpClass()
11 | frappe.delete_doc("Timesheet")
12 |
13 | def test_get_holiday_dates_with_employee_holiday_list(self):
14 | employee = 'EMPLOYEE_ID_WITH_HOLIDAY_LIST'
15 |
16 | expected_dates = ['2024-01-01', '2024-02-14', '2024-05-01']
17 |
18 | with patch('frappe.get_all', return_value=expected_dates):
19 | dates = get_holiday_dates(employee)
20 |
21 | self.assertEqual(dates, expected_dates)
22 |
23 | def test_get_holiday_dates_without_employee_holiday_list(self):
24 | employee = 'EMPLOYEE_ID_WITHOUT_HOLIDAY_LIST'
25 |
26 | with patch('frappe.get_all', return_value=None):
27 | dates = get_holiday_dates(employee)
28 |
29 | self.assertIsNone(dates)
30 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/earned_bonus_vs_attained_score/earned_bonus_vs_attained_score.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2023-10-22 14:23:52.414903",
5 | "doctype": "DocType",
6 | "editable_grid": 1,
7 | "engine": "InnoDB",
8 | "field_order": [
9 | "lower_limit",
10 | "upper_limit",
11 | "attained_score"
12 | ],
13 | "fields": [
14 | {
15 | "fieldname": "lower_limit",
16 | "fieldtype": "Percent",
17 | "in_list_view": 1,
18 | "label": "Lower Limit"
19 | },
20 | {
21 | "fieldname": "upper_limit",
22 | "fieldtype": "Percent",
23 | "in_list_view": 1,
24 | "label": "Upper Limit"
25 | },
26 | {
27 | "fieldname": "attained_score",
28 | "fieldtype": "Percent",
29 | "in_list_view": 1,
30 | "label": "Attained Score"
31 | }
32 | ],
33 | "index_web_pages_for_search": 1,
34 | "istable": 1,
35 | "links": [],
36 | "modified": "2024-07-27 17:02:24.856789",
37 | "modified_by": "Administrator",
38 | "module": "Nl Attendance Timesheet",
39 | "name": "Earned Bonus vs Attained Score",
40 | "owner": "Administrator",
41 | "permissions": [],
42 | "sort_field": "modified",
43 | "sort_order": "DESC",
44 | "states": []
45 | }
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/timesheet_center/timesheet_center.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2024-02-14 21:26:09.004919",
5 | "default_view": "List",
6 | "doctype": "DocType",
7 | "editable_grid": 1,
8 | "engine": "InnoDB",
9 | "field_order": [
10 | "start_date",
11 | "end_date"
12 | ],
13 | "fields": [
14 | {
15 | "fieldname": "start_date",
16 | "fieldtype": "Date",
17 | "in_list_view": 1,
18 | "label": "Start Date",
19 | "reqd": 1
20 | },
21 | {
22 | "fieldname": "end_date",
23 | "fieldtype": "Date",
24 | "in_list_view": 1,
25 | "label": "End Date",
26 | "reqd": 1
27 | }
28 | ],
29 | "index_web_pages_for_search": 1,
30 | "issingle": 1,
31 | "links": [],
32 | "modified": "2024-05-17 09:00:38.846138",
33 | "modified_by": "Administrator",
34 | "module": "Nl Attendance Timesheet",
35 | "name": "Timesheet Center",
36 | "owner": "Administrator",
37 | "permissions": [
38 | {
39 | "create": 1,
40 | "delete": 1,
41 | "email": 1,
42 | "print": 1,
43 | "read": 1,
44 | "role": "System Manager",
45 | "share": 1,
46 | "write": 1
47 | }
48 | ],
49 | "sort_field": "modified",
50 | "sort_order": "DESC",
51 | "states": []
52 | }
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/timesheet_center/test_timesheet_center.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2024, Navari Limited and Contributors
2 | # See license.txt
3 |
4 | import frappe
5 | from frappe.tests.utils import FrappeTestCase
6 | from csf_ke.csf_ke.doctype.timesheet_center.timesheet_center import TimesheetCenter
7 |
8 |
9 | class TestTimesheetCenter(FrappeTestCase):
10 | @classmethod
11 | def setUpClass(cls):
12 | cls.test_doc = TimesheetCenter(
13 | doctype="Timesheet Center",
14 | start_date="2024-01-01",
15 | end_date="2024-01-31"
16 | )
17 |
18 | @classmethod
19 | def tearDownClass(cls):
20 | cls.test_doc.delete()
21 |
22 | def test_validate_start_date_before_end_date(self):
23 | timesheet_center = self.test_doc
24 | timesheet_center.validate()
25 |
26 | self.assertTrue(True)
27 |
28 | def test_validate_start_date_after_end_date(self):
29 | timesheet_center = TimesheetCenter(
30 | doctype="Timesheet Center",
31 | start_date="2024-01-31",
32 | end_date="2024-01-01"
33 | )
34 |
35 | with self.assertRaises(frappe.ValidationError):
36 | timesheet_center.validate()
37 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_attendance/navari_attendance.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2024-12-05 09:42:00.428593",
5 | "default_view": "List",
6 | "doctype": "DocType",
7 | "editable_grid": 1,
8 | "engine": "InnoDB",
9 | "field_order": [
10 | "attendance_date",
11 | "hours_worked",
12 | "billiable_hours",
13 | "include_unpaid_breaks",
14 | "unpaid_breaks_minutes",
15 | "min_hours_to_include_a_break"
16 | ],
17 | "fields": [
18 | {
19 | "fieldname": "attendance_date",
20 | "fieldtype": "Date",
21 | "in_list_view": 1,
22 | "label": "Attendance Date"
23 | },
24 | {
25 | "fieldname": "hours_worked",
26 | "fieldtype": "Float",
27 | "in_list_view": 1,
28 | "label": "Hours Worked"
29 | },
30 | {
31 | "fieldname": "billiable_hours",
32 | "fieldtype": "Float",
33 | "in_list_view": 1,
34 | "label": "Billiable Hours",
35 | "read_only": 1
36 | },
37 | {
38 | "default": "0",
39 | "fieldname": "include_unpaid_breaks",
40 | "fieldtype": "Check",
41 | "label": "Include Unpaid Breaks",
42 | "read_only": 1
43 | },
44 | {
45 | "fieldname": "unpaid_breaks_minutes",
46 | "fieldtype": "Float",
47 | "label": "Unpaid Breaks (Minutes)",
48 | "read_only": 1
49 | },
50 | {
51 | "fieldname": "min_hours_to_include_a_break",
52 | "fieldtype": "Float",
53 | "label": "Min. hours to include a break",
54 | "read_only": 1
55 | }
56 | ],
57 | "index_web_pages_for_search": 1,
58 | "istable": 1,
59 | "links": [],
60 | "modified": "2024-12-05 09:42:00.428593",
61 | "modified_by": "Administrator",
62 | "module": "Nl Attendance Timesheet",
63 | "name": "Navari Attendance",
64 | "owner": "Administrator",
65 | "permissions": [],
66 | "sort_field": "modified",
67 | "sort_order": "DESC",
68 | "states": []
69 | }
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/tests/test_get_employee_attendance.py:
--------------------------------------------------------------------------------
1 | import frappe
2 | from frappe.tests.utils import FrappeTestCase
3 | from frappe.utils import now_datetime
4 | from csf_ke.controllers.get_employee_attendance import get_employee_attendance
5 |
6 |
7 | class TestGetEmployeeAttendance(FrappeTestCase):
8 | @classmethod
9 | def setUpClass(cls):
10 | super().setUpClass()
11 | cls.insert_test_data()
12 |
13 | @classmethod
14 | def insert_test_data(cls):
15 | employee = frappe.get_doc({
16 | "doctype": "Employee",
17 | "employee_name": "Tonny Oluoch Owuor",
18 | "first_name": "Tonny",
19 | "last_name": "Owuor",
20 | "gender": "Male",
21 | "date_of_birth": "1997-10-20",
22 | "date_of_joining": "2021-10-15",
23 | "department": "Hatchery - VFL",
24 | "employment_type": "Contract",
25 | })
26 | employee.insert()
27 |
28 | shift_type = frappe.get_doc({
29 | "doctype": "Shift Type",
30 | "name": "FARM ADMINISTRATIONS SHIFT",
31 | "short_name": "Farm Adm",
32 | })
33 | shift_type.insert()
34 |
35 | # Create test Attendance records
36 | attendance_date = now_datetime().date()
37 | if not frappe.db.exists("Attendance", {"employee": employee.name, "attendance_date": attendance_date}):
38 | attendance = frappe.get_doc({
39 | "doctype": "Attendance",
40 | "employee": employee.name,
41 | "status": "Present",
42 | "attendance_date": attendance_date,
43 | "in_time": now_datetime().replace(hour=7, minute=0, second=0).strftime("%Y-%m-%d %H:%M:%S"),
44 | "working_hours": 9,
45 | "shift": shift_type.name,
46 | })
47 | attendance.insert()
48 |
49 | def test_get_employee_attendance(self):
50 | employee_id = "Test Employee ID"
51 | start_date = now_datetime().replace(day=1).date()
52 | end_date = now_datetime().date()
53 |
54 | attendance_records = get_employee_attendance(employee_id, start_date, end_date)
55 |
56 | self.assertIsNotNone(attendance_records)
57 |
58 | expected_attendance_count = 0
59 | self.assertEqual(len(attendance_records), expected_attendance_count)
60 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/nl_attendance_timesheet/doctype/navari_custom_payroll_settings/navari_custom_payroll_settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": [],
3 | "allow_rename": 1,
4 | "creation": "2023-10-15 13:27:20.418452",
5 | "default_view": "List",
6 | "doctype": "DocType",
7 | "editable_grid": 1,
8 | "engine": "InnoDB",
9 | "field_order": [
10 | "maximum_monthly_hours",
11 | "overtime_15_activity",
12 | "overtime_20_activity",
13 | "column_break_xpqok",
14 | "include_early_entry",
15 | "overtime_threshold",
16 | "earned_bonus_vs_attained_score_matrix_section",
17 | "bonus_vs_score_matrix"
18 | ],
19 | "fields": [
20 | {
21 | "description": "Hours beyond which will be carried over to overtime 1.5",
22 | "fieldname": "maximum_monthly_hours",
23 | "fieldtype": "Float",
24 | "label": "Maximum monthly hours"
25 | },
26 | {
27 | "description": "Activity type used to create overtime 1.5 timesheets",
28 | "fieldname": "overtime_15_activity",
29 | "fieldtype": "Link",
30 | "label": "Overtime 1.5 Activity",
31 | "options": "Activity Type"
32 | },
33 | {
34 | "description": "Activity type used to create overtime 2.0 timesheets",
35 | "fieldname": "overtime_20_activity",
36 | "fieldtype": "Link",
37 | "label": "Overtime 2.0 Activity",
38 | "options": "Activity Type"
39 | },
40 | {
41 | "fieldname": "column_break_xpqok",
42 | "fieldtype": "Column Break"
43 | },
44 | {
45 | "default": "0",
46 | "description": "If checked, hours before shift start time will be considered while making salary slips.",
47 | "fieldname": "include_early_entry",
48 | "fieldtype": "Check",
49 | "label": "Include early entry"
50 | },
51 | {
52 | "fieldname": "earned_bonus_vs_attained_score_matrix_section",
53 | "fieldtype": "Section Break",
54 | "label": "Earned Bonus Vs Attained Score Matrix"
55 | },
56 | {
57 | "fieldname": "bonus_vs_score_matrix",
58 | "fieldtype": "Table",
59 | "label": "Bonus vs Score Matrix",
60 | "options": "Earned Bonus vs Attained Score"
61 | },
62 | {
63 | "default": "30",
64 | "fieldname": "overtime_threshold",
65 | "fieldtype": "Float",
66 | "label": "Overtime Threshold"
67 | }
68 | ],
69 | "grid_page_length": 50,
70 | "index_web_pages_for_search": 1,
71 | "issingle": 1,
72 | "links": [],
73 | "modified": "2025-11-05 12:16:30.955718",
74 | "modified_by": "Administrator",
75 | "module": "Nl Attendance Timesheet",
76 | "name": "Navari Custom Payroll Settings",
77 | "owner": "Administrator",
78 | "permissions": [
79 | {
80 | "create": 1,
81 | "delete": 1,
82 | "email": 1,
83 | "print": 1,
84 | "read": 1,
85 | "role": "System Manager",
86 | "share": 1,
87 | "write": 1
88 | }
89 | ],
90 | "row_format": "Dynamic",
91 | "sort_field": "modified",
92 | "sort_order": "DESC",
93 | "states": []
94 | }
95 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/get_employee_attendance.py:
--------------------------------------------------------------------------------
1 | import frappe
2 | from datetime import datetime, time
3 | from pypika import Criterion, Order
4 |
5 |
6 | def get_employee_attendance(employee_id, start_date, end_date):
7 | SETTINGS_DOCTYPE = "Navari Custom Payroll Settings"
8 | include_early_entry = frappe.db.get_single_value(
9 | SETTINGS_DOCTYPE, "include_early_entry"
10 | )
11 |
12 | attendance = frappe.qb.DocType("Attendance")
13 | employee = frappe.qb.DocType("Employee")
14 | shift_type = frappe.qb.DocType("Shift Type")
15 |
16 | conditions = [
17 | attendance.docstatus == 1,
18 | attendance.employee == employee_id,
19 | attendance.status == "Present",
20 | attendance.attendance_date[start_date:end_date],
21 | ]
22 |
23 | query = (
24 | frappe.qb.from_(attendance)
25 | .inner_join(employee)
26 | .on(employee.name == attendance.employee)
27 | .left_join(shift_type)
28 | .on(attendance.shift == shift_type.name)
29 | .select(
30 | attendance.name.as_("name"),
31 | attendance.shift.as_("shift"),
32 | attendance.attendance_date.as_("attendance_date"),
33 | attendance.in_time.as_("in_time"),
34 | attendance.working_hours.as_("working_hours"),
35 | attendance.payment_hours.as_("payment_hours"),
36 | attendance.overtime.as_("overtime"),
37 | employee.holiday_list.as_("holiday_list"),
38 | shift_type.include_unpaid_breaks.as_("include_unpaid_breaks"),
39 | shift_type.unpaid_breaks_minutes.as_("unpaid_breaks_minutes"),
40 | shift_type.min_hours_to_include_a_break.as_("min_hours_to_include_a_break"),
41 | shift_type.start_time.as_("shift_start_time"),
42 | shift_type.end_time.as_("shift_end_time"),
43 | )
44 | .where(Criterion.all(conditions))
45 | .orderby(attendance.attendance_date, order=Order.asc)
46 | )
47 |
48 | attendance_records = query.run(as_dict=True)
49 |
50 | """TODO: Review section, write a test"""
51 | if not include_early_entry:
52 | for entry in attendance_records:
53 | if entry.in_time:
54 | in_time_str = str(entry.in_time).split(".")[
55 | 0
56 | ] # split because entry.in_time sometimes comes in this format -> 2024-02-15 08:01:48.858030
57 | in_time = datetime.strptime(in_time_str, "%Y-%m-%d %H:%M:%S").time()
58 | shift_start_time = datetime.strptime(
59 | str(entry.shift_start_time), "%H:%M:%S"
60 | ).time()
61 | shift_end_time = datetime.strptime(
62 | str(entry.shift_end_time), "%H:%M:%S"
63 | ).time()
64 |
65 | if in_time < shift_start_time:
66 | extra_hours = (
67 | (shift_start_time.hour - in_time.hour)
68 | + ((shift_start_time.minute - in_time.minute) / 60)
69 | + ((shift_start_time.second - in_time.second) / 3600)
70 | )
71 | entry.working_hours -= extra_hours
72 |
73 | # Deduct unpaid breaks from working hours
74 | entry.working_hours -= entry.unpaid_breaks_minutes / 60
75 |
76 | # Calculate total shift hours including unpaid breaks
77 | total_shift_hours = (
78 | (shift_end_time.hour - shift_start_time.hour)
79 | + ((shift_end_time.minute - shift_start_time.minute) / 60)
80 | + ((shift_end_time.second - shift_start_time.second) / 3600)
81 | - (entry.unpaid_breaks_minutes / 60)
82 | )
83 |
84 | if entry.working_hours > total_shift_hours:
85 | entry.payment_hours = total_shift_hours
86 | else:
87 | entry.payment_hours = entry.working_hours
88 |
89 | return attendance_records
90 |
91 |
92 | def get_employee_overtime_attendance(employee, start_date, end_date):
93 | timesheet = frappe.qb.DocType("Timesheet")
94 | timesheet_detail = frappe.qb.DocType("Timesheet Detail")
95 |
96 | conditions = [
97 | timesheet.docstatus == 1,
98 | timesheet.employee == employee,
99 | timesheet.start_date[start_date:end_date],
100 | ]
101 |
102 | query = (
103 | frappe.qb.from_(timesheet)
104 | .inner_join(timesheet_detail)
105 | .on(timesheet.name == timesheet_detail.parent)
106 | .select(
107 | timesheet_detail.activity_type.as_("activity_type"),
108 | timesheet.total_hours.as_("total_hours"),
109 | timesheet.name.as_("name"),
110 | timesheet.attendance.as_("attendance"),
111 | )
112 | .where(Criterion.all(conditions))
113 | .orderby(timesheet.start_date, order=Order.asc)
114 | )
115 |
116 | overtime = query.run(as_dict=True)
117 |
118 | return overtime
119 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/add_attendance_to_salary_slip.py:
--------------------------------------------------------------------------------
1 | import frappe
2 | from frappe import _
3 | from ..controllers.get_employee_attendance import (
4 | get_employee_attendance,
5 | get_employee_overtime_attendance,
6 | )
7 |
8 | SETTINGS_DOCTYPE = "Navari Custom Payroll Settings"
9 |
10 | maximum_monthly_hours = frappe.db.get_single_value(
11 | SETTINGS_DOCTYPE, "maximum_monthly_hours"
12 | )
13 | overtime_15 = frappe.db.get_single_value(SETTINGS_DOCTYPE, "overtime_15_activity")
14 | overtime_20 = frappe.db.get_single_value(SETTINGS_DOCTYPE, "overtime_20_activity")
15 |
16 |
17 | @frappe.whitelist()
18 | def add_attendance_data(payroll_entry):
19 | salary_slips = frappe.db.get_all(
20 | "Salary Slip", filters={"payroll_entry": payroll_entry, "docstatus": 0}
21 | )
22 |
23 | for entry in salary_slips:
24 | salary_slip = frappe.get_doc("Salary Slip", entry.get("name"))
25 | salary_slip.attendance = []
26 | salary_slip.regular_overtime = []
27 | salary_slip.holiday_overtime = []
28 |
29 | salary_slip.regular_working_hours = 0
30 | salary_slip.overtime_hours = 0
31 | salary_slip.holiday_hours = 0
32 |
33 | attendance = get_employee_attendance(
34 | salary_slip.get("employee"),
35 | salary_slip.get("start_date"),
36 | salary_slip.get("end_date"),
37 | )
38 | overtime_attendance = get_employee_overtime_attendance(
39 | salary_slip.get("employee"),
40 | salary_slip.get("start_date"),
41 | salary_slip.get("end_date"),
42 | )
43 | holiday_dates = get_holiday_dates(salary_slip.get("employee"))
44 |
45 | if attendance:
46 | for attendance_entry in attendance:
47 | if (
48 | attendance_entry.get("attendance_date") not in (holiday_dates or [])
49 | and attendance_entry.get("working_hours") > 0
50 | ):
51 | billiable_hours = 0
52 |
53 | if not attendance_entry.get("include_unpaid_breaks"):
54 | billiable_hours = attendance_entry.get("payment_hours")
55 | else:
56 | if attendance_entry.get("working_hours") > attendance_entry.get(
57 | "min_hours_to_include_a_break"
58 | ):
59 | billiable_hours = attendance_entry.get("working_hours") - (
60 | attendance_entry.get("unpaid_breaks_minutes") / 60
61 | )
62 | else:
63 | billiable_hours = attendance_entry.get("working_hours")
64 |
65 | salary_slip.append(
66 | "attendance",
67 | {
68 | "attendance_date": attendance_entry.get("attendance_date"),
69 | "hours_worked": attendance_entry.get("working_hours"),
70 | "include_unpaid_breaks": attendance_entry.get(
71 | "include_unpaid_breaks"
72 | ),
73 | "unpaid_breaks_minutes": attendance_entry.get(
74 | "unpaid_breaks_minutes"
75 | ),
76 | "min_hours_to_include_a_break": attendance_entry.get(
77 | "min_hours_to_include_a_break"
78 | ),
79 | "billiable_hours": billiable_hours,
80 | },
81 | )
82 |
83 | salary_slip.regular_working_hours += billiable_hours
84 |
85 | if overtime_attendance:
86 | for overtime_attendance_record in overtime_attendance:
87 | if overtime_attendance_record.get("activity_type") == overtime_15:
88 | salary_slip.append(
89 | "regular_overtime",
90 | {
91 | "timesheet": overtime_attendance_record.get("name"),
92 | "hours": overtime_attendance_record.get("total_hours"),
93 | },
94 | )
95 | salary_slip.overtime_hours += overtime_attendance_record.get(
96 | "total_hours"
97 | )
98 |
99 | if overtime_attendance_record.get("activity_type") == overtime_20:
100 | salary_slip.append(
101 | "holiday_overtime",
102 | {
103 | "timesheet": overtime_attendance_record.get("name"),
104 | "hours": overtime_attendance_record.get("total_hours"),
105 | },
106 | )
107 | salary_slip.holiday_hours += overtime_attendance_record.get(
108 | "total_hours"
109 | )
110 |
111 | regular_hours = float(salary_slip.regular_working_hours)
112 | overtime_hours = float(salary_slip.overtime_hours)
113 | max_hours = float(maximum_monthly_hours)
114 |
115 | if regular_hours > max_hours:
116 | excess = regular_hours - max_hours
117 | salary_slip.overtime_hours = overtime_hours + excess
118 | salary_slip.regular_working_hours = max_hours
119 |
120 | elif regular_hours < max_hours:
121 | balance_to_max = max_hours - regular_hours
122 |
123 | if overtime_hours <= balance_to_max:
124 | salary_slip.regular_working_hours = regular_hours + overtime_hours
125 | salary_slip.overtime_hours = 0
126 | else:
127 | salary_slip.regular_working_hours = max_hours
128 | salary_slip.overtime_hours = overtime_hours - balance_to_max
129 |
130 | if (
131 | salary_slip.attendance
132 | or salary_slip.regular_overtime
133 | or salary_slip.holiday_overtime
134 | ):
135 | salary_slip.save(ignore_permissions=True)
136 | frappe.db.commit()
137 |
138 | frappe.response["message"] = _(
139 | "Attendance and Overtime added to Salary Slips successfully."
140 | )
141 |
142 |
143 | def get_holiday_dates(employee):
144 | holiday_list = frappe.db.get_value("Employee", employee, "holiday_list")
145 | if holiday_list:
146 | dates = frappe.db.get_all(
147 | "Holiday", filters={"parent": holiday_list}, pluck="holiday_date"
148 | )
149 | return dates
150 | return None
151 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/hooks.py:
--------------------------------------------------------------------------------
1 | app_name = "nl_attendance_timesheet"
2 | app_title = "Attendance and Timesheet"
3 | app_publisher = "Navari Ltd"
4 | app_description = (
5 | "FrappeHR app to automate creation of employee timesheets from attendance records"
6 | )
7 | app_email = "support@navari.co.ke"
8 | app_license = "GNU Affero General Public License v3.0"
9 | required_apps = ["frappe/erpnext"]
10 |
11 | fixtures = [
12 | {
13 | "doctype": "Custom Field",
14 | "filters": [
15 | ["module", "=", "Nl Attendance Timesheet"],
16 | ],
17 | },
18 | ]
19 |
20 | # Includes in
21 | # ------------------
22 |
23 | # include js, css files in header of desk.html
24 | # app_include_css = "/assets/nl_attendance_timesheet/css/nl_attendance_timesheet.css"
25 | # app_include_js = "/assets/nl_attendance_timesheet/js/nl_attendance_timesheet.js"
26 |
27 | # include js, css files in header of web template
28 | # web_include_css = "/assets/nl_attendance_timesheet/css/nl_attendance_timesheet.css"
29 | # web_include_js = "/assets/nl_attendance_timesheet/js/nl_attendance_timesheet.js"
30 |
31 | # include custom scss in every website theme (without file extension ".scss")
32 | # website_theme_scss = "nl_attendance_timesheet/public/scss/website"
33 |
34 | # include js, css files in header of web form
35 | # webform_include_js = {"doctype": "public/js/doctype.js"}
36 | # webform_include_css = {"doctype": "public/css/doctype.css"}
37 |
38 | # include js in page
39 | # page_js = {"page" : "public/js/file.js"}
40 |
41 | # include js in doctype views
42 | doctype_js = {
43 | "Payroll Entry": "public/js/payroll_entry.js",
44 | }
45 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
46 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
47 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
48 |
49 | # Home Pages
50 | # ----------
51 |
52 | # application home page (will override Website Settings)
53 | # home_page = "login"
54 |
55 | # website user home page (by Role)
56 | # role_home_page = {
57 | # "Role": "home_page"
58 | # }
59 |
60 | # Generators
61 | # ----------
62 |
63 | # automatically create page for each record of this doctype
64 | # website_generators = ["Web Page"]
65 |
66 | # Jinja
67 | # ----------
68 |
69 | # add methods and filters to jinja environment
70 | # jinja = {
71 | # "methods": "nl_attendance_timesheet.utils.jinja_methods",
72 | # "filters": "nl_attendance_timesheet.utils.jinja_filters"
73 | # }
74 |
75 | # Installation
76 | # ------------
77 |
78 | # before_install = "nl_attendance_timesheet.install.before_install"
79 | # after_install = "nl_attendance_timesheet.install.after_install"
80 |
81 | # Uninstallation
82 | # ------------
83 |
84 | # before_uninstall = "nl_attendance_timesheet.uninstall.before_uninstall"
85 | # after_uninstall = "nl_attendance_timesheet.uninstall.after_uninstall"
86 |
87 | # Integration Setup
88 | # ------------------
89 | # To set up dependencies/integrations with other apps
90 | # Name of the app being installed is passed as an argument
91 |
92 | # before_app_install = "nl_attendance_timesheet.utils.before_app_install"
93 | # after_app_install = "nl_attendance_timesheet.utils.after_app_install"
94 |
95 | # Integration Cleanup
96 | # -------------------
97 | # To clean up dependencies/integrations with other apps
98 | # Name of the app being uninstalled is passed as an argument
99 |
100 | # before_app_uninstall = "nl_attendance_timesheet.utils.before_app_uninstall"
101 | # after_app_uninstall = "nl_attendance_timesheet.utils.after_app_uninstall"
102 |
103 | # Desk Notifications
104 | # ------------------
105 | # See frappe.core.notifications.get_notification_config
106 |
107 | # notification_config = "nl_attendance_timesheet.notifications.get_notification_config"
108 |
109 | # Permissions
110 | # -----------
111 | # Permissions evaluated in scripted ways
112 |
113 | # permission_query_conditions = {
114 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
115 | # }
116 | #
117 | # has_permission = {
118 | # "Event": "frappe.desk.doctype.event.event.has_permission",
119 | # }
120 |
121 | # DocType Class
122 | # ---------------
123 | # Override standard doctype classes
124 |
125 | # override_doctype_class = {
126 | # "ToDo": "custom_app.overrides.CustomToDo"
127 | # }
128 |
129 | # Document Events
130 | # ---------------
131 | # Hook on document methods and events
132 |
133 | # doc_events = {
134 | # "Payroll Entry": {
135 | # "on_submit": "nl_attendance_timesheet.controllers.add_attendance_to_salary_slip.add_attendance_data",
136 | # }
137 | # }
138 |
139 | # Scheduled Tasks
140 | # ---------------
141 |
142 | # scheduler_events = {
143 | # "all": [
144 | # "nl_attendance_timesheet.tasks.all"
145 | # ],
146 | # "daily": [
147 | # "nl_attendance_timesheet.tasks.daily"
148 | # ],
149 | # "hourly": [
150 | # "nl_attendance_timesheet.tasks.hourly"
151 | # ],
152 | # "weekly": [
153 | # "nl_attendance_timesheet.tasks.weekly"
154 | # ],
155 | # "monthly": [
156 | # "nl_attendance_timesheet.tasks.monthly"
157 | # ],
158 | # }
159 |
160 | # Testing
161 | # -------
162 |
163 | # before_tests = "nl_attendance_timesheet.install.before_tests"
164 |
165 | # Overriding Methods
166 | # ------------------------------
167 | #
168 | # override_whitelisted_methods = {
169 | # "frappe.desk.doctype.event.event.get_events": "nl_attendance_timesheet.event.get_events"
170 | # }
171 | #
172 | # each overriding function accepts a `data` argument;
173 | # generated from the base implementation of the doctype dashboard,
174 | # along with any modifications made in other Frappe apps
175 | # override_doctype_dashboards = {
176 | # "Task": "nl_attendance_timesheet.task.get_dashboard_data"
177 | # }
178 |
179 | # exempt linked doctypes from being automatically cancelled
180 | #
181 | # auto_cancel_exempted_doctypes = ["Auto Repeat"]
182 |
183 | # Ignore links to specified DocTypes when deleting documents
184 | # -----------------------------------------------------------
185 |
186 | # ignore_links_on_delete = ["Communication", "ToDo"]
187 |
188 | # Request Events
189 | # ----------------
190 | # before_request = ["nl_attendance_timesheet.utils.before_request"]
191 | # after_request = ["nl_attendance_timesheet.utils.after_request"]
192 |
193 | # Job Events
194 | # ----------
195 | # before_job = ["nl_attendance_timesheet.utils.before_job"]
196 | # after_job = ["nl_attendance_timesheet.utils.after_job"]
197 |
198 | # User Data Protection
199 | # --------------------
200 |
201 | # user_data_fields = [
202 | # {
203 | # "doctype": "{doctype_1}",
204 | # "filter_by": "{filter_by}",
205 | # "redact_fields": ["{field_1}", "{field_2}"],
206 | # "partial": 1,
207 | # },
208 | # {
209 | # "doctype": "{doctype_2}",
210 | # "filter_by": "{filter_by}",
211 | # "partial": 1,
212 | # },
213 | # {
214 | # "doctype": "{doctype_3}",
215 | # "strict": False,
216 | # },
217 | # {
218 | # "doctype": "{doctype_4}"
219 | # }
220 | # ]
221 |
222 | # Authentication and authorization
223 | # --------------------------------
224 |
225 | # auth_hooks = [
226 | # "nl_attendance_timesheet.auth.validate"
227 | # ]
228 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/controllers/generate_overtime_timesheets.py:
--------------------------------------------------------------------------------
1 | import frappe
2 | from frappe import _
3 | from frappe.utils.data import get_datetime, nowdate
4 | from datetime import datetime
5 | from pypika import Criterion
6 |
7 | current_date = nowdate()
8 |
9 |
10 | @frappe.whitelist()
11 | def generate_overtime_timesheets(start_date=current_date, end_date=current_date):
12 | SETTINGS_DOCTYPE = "Navari Custom Payroll Settings"
13 | overtime_15 = frappe.db.get_single_value(SETTINGS_DOCTYPE, "overtime_15_activity")
14 | overtime_20 = frappe.db.get_single_value(SETTINGS_DOCTYPE, "overtime_20_activity")
15 |
16 | if not overtime_15 or not overtime_20:
17 | frappe.throw(
18 | "Please set up both Overtime 1.5 and Overtime 2.0 activities in Navari Custom Payroll Settings"
19 | )
20 |
21 | attendance = frappe.qb.DocType("Attendance")
22 | employee = frappe.qb.DocType("Employee")
23 | shift_type = frappe.qb.DocType("Shift Type")
24 |
25 | conditions = [
26 | attendance.docstatus == 1,
27 | attendance.status == "Present",
28 | attendance.attendance_date[start_date:end_date],
29 | ]
30 |
31 | query = (
32 | frappe.qb.from_(attendance)
33 | .inner_join(employee)
34 | .on(employee.name == attendance.employee)
35 | .left_join(shift_type)
36 | .on(attendance.shift == shift_type.name)
37 | .select(
38 | attendance.employee.as_("employee"),
39 | attendance.employee_name.as_("employee_name"),
40 | attendance.name.as_("name"),
41 | attendance.shift.as_("shift"),
42 | attendance.attendance_date.as_("attendance_date"),
43 | attendance.in_time.as_("in_time"),
44 | attendance.out_time.as_("out_time"),
45 | attendance.working_hours.as_("working_hours"),
46 | employee.holiday_list.as_("holiday_list"),
47 | employee.company.as_("company"),
48 | employee.department.as_("department"),
49 | employee.holiday_list.as_("holiday_list"),
50 | shift_type.start_time.as_("shift_start_time"),
51 | shift_type.end_time.as_("shift_end_time"),
52 | shift_type.unpaid_breaks_minutes.as_("unpaid_breaks_minutes"),
53 | )
54 | .where(Criterion.all(conditions))
55 | )
56 |
57 | attendance_records = query.run(as_dict=True)
58 |
59 | for entry in attendance_records:
60 | if entry.get("holiday_list"):
61 | holiday_dates = frappe.db.get_all(
62 | "Holiday", filters={"parent": entry.holiday_list}, pluck="holiday_date"
63 | )
64 | if entry.attendance_date in holiday_dates:
65 | total_work_duration = calculate_holiday_hours(entry)
66 | if total_work_duration:
67 | create_new_timesheet(
68 | entry.employee,
69 | entry.employee_name,
70 | entry.company,
71 | entry.department,
72 | overtime_20,
73 | entry.in_time,
74 | entry.working_hours,
75 | entry.name,
76 | )
77 | else:
78 | from_time, hours = get_from_time_and_hours(entry)
79 | if from_time and hours:
80 | create_new_timesheet(
81 | entry.employee,
82 | entry.employee_name,
83 | entry.company,
84 | entry.department,
85 | overtime_15,
86 | from_time,
87 | hours,
88 | entry.name,
89 | )
90 | else:
91 | from_time, hours = get_from_time_and_hours(entry)
92 | if from_time and hours:
93 | create_new_timesheet(
94 | entry.employee,
95 | entry.employee_name,
96 | entry.company,
97 | entry.department,
98 | overtime_15,
99 | from_time,
100 | hours,
101 | entry.name,
102 | )
103 |
104 |
105 | def calculate_holiday_hours(entry):
106 | if entry.out_time and entry.shift_start_time:
107 | in_time_dt = datetime.strptime(
108 | str(entry.in_time).split(".")[0], "%Y-%m-%d %H:%M:%S"
109 | )
110 | out_time_dt = datetime.strptime(
111 | str(entry.out_time).split(".")[0], "%Y-%m-%d %H:%M:%S"
112 | )
113 | shift_start_time_dt = datetime.combine(
114 | out_time_dt.date(),
115 | datetime.strptime(str(entry.shift_start_time), "%H:%M:%S").time(),
116 | )
117 |
118 | if in_time_dt < shift_start_time_dt:
119 | extra_hours = (
120 | shift_start_time_dt.hour
121 | - in_time_dt.hour
122 | + ((shift_start_time_dt.minute - in_time_dt.minute) / 60)
123 | + ((shift_start_time_dt.second - in_time_dt.second) / 3600)
124 | )
125 | entry.working_hours -= extra_hours
126 |
127 | entry.working_hours -= entry.unpaid_breaks_minutes / 60
128 |
129 | total_work_duration = entry.working_hours
130 | return max(0, total_work_duration)
131 |
132 | return 0
133 |
134 |
135 | def get_from_time_and_hours(entry):
136 | SETTINGS_DOCTYPE = "Navari Custom Payroll Settings"
137 | if entry.out_time and entry.shift_end_time:
138 | check_out_time_str = str(entry.out_time).split(".")[0]
139 | check_out_time = datetime.strptime(
140 | check_out_time_str, "%Y-%m-%d %H:%M:%S"
141 | ).time()
142 | shift_end_time = datetime.strptime(str(entry.shift_end_time), "%H:%M:%S").time()
143 | overtime_threshold = frappe.db.get_single_value(
144 | SETTINGS_DOCTYPE, "overtime_threshold"
145 | )
146 |
147 | if check_out_time > shift_end_time:
148 | overtime_minutes = ((check_out_time.hour - shift_end_time.hour) * 60) + (
149 | check_out_time.minute - shift_end_time.minute
150 | )
151 |
152 | if overtime_minutes > overtime_threshold:
153 |
154 | """convert datetime.timedelta to datetime.time"""
155 | shift_end_total_seconds = entry.shift_end_time.total_seconds()
156 | hours = int(shift_end_total_seconds // 3600)
157 | minutes = int((shift_end_total_seconds % 3600) // 60)
158 | seconds = int(shift_end_total_seconds % 60)
159 | shift_end = get_datetime(f"{hours}:{minutes}:{seconds}").time()
160 |
161 | from_time = datetime.combine(entry.attendance_date, shift_end)
162 |
163 | return from_time, overtime_minutes / 60
164 | else:
165 | """Overtime is less than 30 minutes"""
166 | return None, None
167 | else:
168 | """Check out time is not more than shift end time"""
169 | return None, None
170 | else:
171 | return None, None
172 |
173 |
174 | def create_new_timesheet(
175 | employee,
176 | employee_name,
177 | company,
178 | department,
179 | overtime_type,
180 | from_time,
181 | hours,
182 | attendance,
183 | ):
184 | timesheet = frappe.new_doc("Timesheet")
185 | timesheet.employee = employee
186 | timesheet.company = company
187 | timesheet.department = department
188 | timesheet.attendance = attendance
189 | timesheet.employee_name = employee_name
190 |
191 | timesheet.append(
192 | "time_logs",
193 | {
194 | "activity_type": overtime_type,
195 | "description": overtime_type,
196 | "from_time": from_time,
197 | "hours": hours,
198 | "completed": 1,
199 | },
200 | )
201 |
202 | timesheet.insert(
203 | ignore_permissions=True,
204 | ignore_links=True,
205 | ignore_if_duplicate=True,
206 | ignore_mandatory=True,
207 | )
208 | frappe.db.commit()
209 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Attendance and Timesheet Integration
2 | Frappe App to calculate and create timesheets from attendance records
3 |
4 | ## Introduction
5 |
6 | This app includes functionalities to calculate and create timesheets from attendance records. Timesheets are created based on any hours above normal working hours, recorded in attendance.
7 |
8 | ---
9 | ## Key Features
10 |
11 | ### Doctypes
12 | 1. Employee Checkin
13 | 2. Attendance
14 | 3. Activity Type
15 | 4. Timesheet Center
16 | 5. Timesheet
17 | 6. Navari Custom Payroll Settings
18 | 7. Timesheet Center
19 | - Overtime Generation Process
20 | - Activity Types
21 | 8. Navari Custom Payroll Settings
22 | - [Matching Attendance to Payroll](#matching-attendance-to-payroll)
23 | - [Steps](#steps)
24 | - [Payroll Generation](#payroll-generation)
25 | - [Steps for Payroll Generation](#steps-for-payroll-generation)
26 |
27 | ## Doctypes
28 |
29 | ### Employee Checkin
30 |
31 | Each employee should have an Attendance ID mapped to the biometric/RFID devices' user IDs. Devices send check-in/out logs to the Employee Checkin doctype.
32 |
33 | There's a need for Checkin/Checkout logs, mostly fetched from a biometric system (e.g. https://github.com/navariltd/navari-frappehr-biostar) created under [employee checkin](https://frappehr.com/docs/v14/en/employee_checkin), every day.
34 |
35 |
36 | ### Attendance
37 |
38 | Tracks the attendance status of employees (Present, Absent, On Leave, etc.) based on Employee Checkin records.
39 | Attendance is marked as per the shifts assigned to each employee. This happens automatically for every shift type with *Enable Auto Attendance* checked.
40 | Overtime timesheets are generated, from the attendance data. Happens automatically, one can also choose to generate these manually from the [timesheet center.](#Timesheet_Center)
41 |
42 |
43 | ### Activity Type
44 |
45 | Defines the different types of activities i.e.
46 | - **Overtime 1.5**: Additional time (more than 30 minutes) past an employee's shift end time.
47 | - **Overtime 2.0**: Attendance on a holiday listed for the employee.
48 |
49 | **NB:** *Overtime 1.5 Activity* and *Overtime 2.0 Activity* should be different so as to differentiate between the two types of overtime.
50 |
51 | ### Timesheet Center
52 |
53 | Serves as a hub for managing overtime generation from employee attendance records.
54 | ### Overtime Generation Process
55 |
56 | To generate the overtime timesheets:
57 | 1. Select the start and end dates for which you want to generate timesheets.
58 | 2. Save the document.
59 | 3. Click on the **Generate Timesheets** button, which will then get employees who have worked extra hours. This will be recorded under the Timesheet doctype.
60 |
61 |
62 | ### Timesheet
63 |
64 | Records the details of overtime hours worked by employees.
65 |
66 | ### Navari Custom Payroll Settings
67 | This doctype includes settings related to payroll:
68 | - **Maximum monthly hours**: Hours beyond this are carried over to overtime when creating salary slips.
69 | - **Overtime 1.5 Activity**: Specifies the activity type for regular overtime.
70 | - **Overtime 2.0 Activity**: Specifies the activity type for holiday overtime.
71 | - **Include early entry**: Determines whether hours before shift start time are included in payroll calculations.
72 | - **Overtime Threshold**: The number of minutes/hours after shift end time for overtime to be considered.
73 |
74 | ## Matching Attendance to Payroll
75 |
76 | ### Steps
77 |
78 | 1. **Create an [Employee](https://frappehr.com/docs/v14/en/employee)**: Ensure each employee is properly set up in the system.
79 | 2. **Create [Salary Components](https://frappehr.com/docs/v14/en/salary-component)**: Define the various salary components such as basic salary, allowances, and deductions.
80 | 3. **Create a [Salary Structure](https://frappehr.com/docs/v14/en/salary-structure)**: Develop a salary structure that includes all salary components.
81 | 4. **Create a [Salary Structure Assignment](https://frappehr.com/docs/v14/en/salary-structure-assignment)**: Assign the salary structure to each employee.
82 | 5. **Create a [Payroll Entry](https://frappehr.com/docs/v14/en/payroll-entry)**: Generate payroll entries for each payroll period.
83 | 6. **Generate [Salary Slips](https://frappehr.com/docs/v14/en/salary-slip)**: Create salary slips for each employee, incorporating attendance and overtime data.
84 |
85 | ## Payroll Generation
86 |
87 | ### Steps for Payroll Generation
88 |
89 | 1. **Attendance Data**: Ensure that attendance data is accurately logged and verified in the [Attendance](https://frappehr.com/docs/v14/en/attendance) doctype.
90 | 2. **Generate Overtime Timesheets**: Automatically or manually generate timesheets for overtime using the Timesheet Center for the specified payroll period.
91 | 
92 | 3. **Submit Timesheets**: Submit timesheets to ensure they are included in payroll calculations.
93 | 
94 | 4. **Create Payroll Entry**: Create a payroll entry for the desired payroll period.
95 | 
96 | 5. **Generate Salary Slips**: Generate salary slips, which will incorporate attendance and timesheet records under the attendance tab in the salary slip, ensuring accurate calculation of regular and overtime pay.
97 |
98 | ***Few changes to the payroll process:***
99 | 1. When creating a salary structure for employees who are paid per hour, make sure to check the *Wage based salary (hours)* field, and fill the *Hour Rate* and *Salary Component* fields.
100 | 2. When running payroll on payroll entry, after generating salary slips, attendance data is fetched and added to employees attendance in the salary slips. Attendance data will be picked from [attendance](https://frappehr.com/docs/v14/en/attendance) and timesheet records generated over that payroll period.
101 | 
102 |
103 | 3. Attendance data will be added to the *Attendance Details* tab on a salary slip.
104 | 
105 |
106 | 4. **Regular Working Hours:** Sum of *Billiable Hours* from the *Attendance* table. Hours beyond what is set as *Maximum monthly hours* on *Navari Custom Payroll Settings* are carried over to the *Overtime Hours* field.
107 | 5. **Overtime Hours:** Sum of hours from *Overtime 1.5* table plus what has been carried over from *Regular Working Hours*, incase there is anything to carry over.
108 | 6. **Holiday hours:** Sum of hours from *Overtime 2.0* table.
109 | 7. **Hourly Rate:** Fetched from the salary structure assigned to an employee. Used to calculate basic and overtime pay (Both regular and holiday overtime)
110 | ```Basic salary = (hourly_rate * regular_working_hours)```
111 | ```OT hours = hourly_rate * 1.5 * overtime_hours```
112 | ```Holiday Hours = hourly_rate * 2 * holiday_hours```
113 | *NB: OT here refers to regular overtime*
114 |
115 |
116 | ## Installation
117 | ### Manual Installation
118 | 1. [Install bench](https://github.com/frappe/bench)
119 | 2. [Install ERPNext](https://github.com/frappe/erpnext#installation)
120 | 3. [Install Frappe HR](https://github.com/frappe/hrms)
121 | 4. Once bench, ERPNext and Frappe HR are installed, add nl_attendance_timesheet to your bench by running
122 | ```sh
123 | $ bench get-app https://github.com/navariltd/nl-attendance-timesheet.git
124 | ```
125 | Replace {branch-name} with any of the repository's branches
126 | 5. After that, you can install the nl_attendance_timesheet app on the required site by running
127 | ```sh
128 | $ bench --site {sitename} install-app nl_attendance_timesheet
129 | ```
130 | Replace {sitename} with the name of your site
131 |
132 | ### Frappe Cloud Installation
133 | ### Adding the App to Your Bench
134 |
135 | 1. **Log Into Frappe Cloud Dashboard:**
136 | - Access your account on Frappe Cloud and navigate to the dashboard.
137 |
138 | 2. **Navigate to your Bench Apps Section:**
139 | - In the bench, locate and click on the **Apps** tab. This section allows you to manage all apps available to your bench.
140 |
141 | 3. **Add New App:**
142 | - Click on the **Add App** button. You will be prompted to enter details about the app you wish to add.
143 | - For **App URL**, input `https://github.com/navariltd/nl-attendance-timesheet`. Note: Since this is a private repository, you might be asked to provide credentials to access the repository.
144 |
145 | 4. **Complete the Addition:**
146 | - Follow any additional prompts to complete the app addition process. This might include specifying branch names if you're not using the default branch.
147 |
148 | ### Installing the App on a Specific Site
149 |
150 | After adding the app to your bench, the next step is to install it onto the specific site where you want it to be available.
151 |
152 | 1. **Access Your Sites:**
153 | - From the Frappe Cloud dashboard, navigate to the **Sites** section where you'll see a list of your ERPNext sites.
154 |
155 | 2. **Select Your Site:**
156 | - Click on the site you wish to install the app on.
157 |
158 | 3. **Install the App:**
159 | - Go to the Apps tab of the site and click the **Install App** button. Selecting this option will present you with a list of apps available to install.
160 | - Find `Nl Attendance Timesheet` in the list and proceed with the installation.
161 |
162 | ### Verification and Use
163 |
164 | - **Verify the Installation:**
165 | - Once the installation process completes, verify that the app is correctly installed by accessing your ERPNext site and checking the list of installed apps.
166 |
167 | - **Use the App:**
168 | - Explore the functionalities added by `nl-attendance-timesheet` to ensure everything works as expected.
169 |
--------------------------------------------------------------------------------
/nl_attendance_timesheet/fixtures/custom_field.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "allow_in_quick_entry": 0,
4 | "allow_on_submit": 0,
5 | "bold": 0,
6 | "collapsible": 0,
7 | "collapsible_depends_on": null,
8 | "columns": 0,
9 | "default": null,
10 | "depends_on": null,
11 | "description": null,
12 | "docstatus": 0,
13 | "doctype": "Custom Field",
14 | "dt": "Shift Type",
15 | "fetch_from": null,
16 | "fetch_if_empty": 0,
17 | "fieldname": "include_unpaid_breaks",
18 | "fieldtype": "Check",
19 | "hidden": 0,
20 | "hide_border": 0,
21 | "hide_days": 0,
22 | "hide_seconds": 0,
23 | "ignore_user_permissions": 0,
24 | "ignore_xss_filter": 0,
25 | "in_global_search": 0,
26 | "in_list_view": 0,
27 | "in_preview": 0,
28 | "in_standard_filter": 0,
29 | "insert_after": "end_time",
30 | "is_system_generated": 1,
31 | "is_virtual": 0,
32 | "label": "Include Unpaid Breaks",
33 | "length": 0,
34 | "link_filters": null,
35 | "mandatory_depends_on": null,
36 | "modified": "2025-11-05 11:28:52.789691",
37 | "module": "Nl Attendance Timesheet",
38 | "name": "Shift Type-include_unpaid_breaks",
39 | "no_copy": 0,
40 | "non_negative": 0,
41 | "options": null,
42 | "permlevel": 0,
43 | "placeholder": null,
44 | "precision": "",
45 | "print_hide": 0,
46 | "print_hide_if_no_value": 0,
47 | "print_width": null,
48 | "read_only": 0,
49 | "read_only_depends_on": null,
50 | "report_hide": 0,
51 | "reqd": 0,
52 | "search_index": 0,
53 | "show_dashboard": 0,
54 | "sort_options": 0,
55 | "translatable": 0,
56 | "unique": 0,
57 | "width": null
58 | },
59 | {
60 | "allow_in_quick_entry": 0,
61 | "allow_on_submit": 0,
62 | "bold": 0,
63 | "collapsible": 0,
64 | "collapsible_depends_on": null,
65 | "columns": 0,
66 | "default": null,
67 | "depends_on": null,
68 | "description": null,
69 | "docstatus": 0,
70 | "doctype": "Custom Field",
71 | "dt": "Attendance",
72 | "fetch_from": null,
73 | "fetch_if_empty": 0,
74 | "fieldname": "payment_hours",
75 | "fieldtype": "Float",
76 | "hidden": 0,
77 | "hide_border": 0,
78 | "hide_days": 0,
79 | "hide_seconds": 0,
80 | "ignore_user_permissions": 0,
81 | "ignore_xss_filter": 0,
82 | "in_global_search": 0,
83 | "in_list_view": 0,
84 | "in_preview": 0,
85 | "in_standard_filter": 0,
86 | "insert_after": "working_hours",
87 | "is_system_generated": 1,
88 | "is_virtual": 0,
89 | "label": "Payment Hours",
90 | "length": 0,
91 | "link_filters": null,
92 | "mandatory_depends_on": null,
93 | "modified": "2025-11-05 11:45:05.155866",
94 | "module": "Nl Attendance Timesheet",
95 | "name": "Attendance-payment_hours",
96 | "no_copy": 0,
97 | "non_negative": 0,
98 | "options": null,
99 | "permlevel": 0,
100 | "placeholder": null,
101 | "precision": "",
102 | "print_hide": 0,
103 | "print_hide_if_no_value": 0,
104 | "print_width": null,
105 | "read_only": 1,
106 | "read_only_depends_on": null,
107 | "report_hide": 0,
108 | "reqd": 0,
109 | "search_index": 0,
110 | "show_dashboard": 0,
111 | "sort_options": 0,
112 | "translatable": 0,
113 | "unique": 0,
114 | "width": null
115 | },
116 | {
117 | "allow_in_quick_entry": 0,
118 | "allow_on_submit": 0,
119 | "bold": 0,
120 | "collapsible": 0,
121 | "collapsible_depends_on": null,
122 | "columns": 0,
123 | "default": null,
124 | "depends_on": "eval: doc.include_unpaid_breaks",
125 | "description": null,
126 | "docstatus": 0,
127 | "doctype": "Custom Field",
128 | "dt": "Shift Type",
129 | "fetch_from": null,
130 | "fetch_if_empty": 0,
131 | "fieldname": "unpaid_breaks_minutes",
132 | "fieldtype": "Float",
133 | "hidden": 0,
134 | "hide_border": 0,
135 | "hide_days": 0,
136 | "hide_seconds": 0,
137 | "ignore_user_permissions": 0,
138 | "ignore_xss_filter": 0,
139 | "in_global_search": 0,
140 | "in_list_view": 0,
141 | "in_preview": 0,
142 | "in_standard_filter": 0,
143 | "insert_after": "include_unpaid_breaks",
144 | "is_system_generated": 1,
145 | "is_virtual": 0,
146 | "label": "Unpaid breaks (minutes)",
147 | "length": 0,
148 | "link_filters": null,
149 | "mandatory_depends_on": null,
150 | "modified": "2025-11-05 11:31:18.988788",
151 | "module": "Nl Attendance Timesheet",
152 | "name": "Shift Type-unpaid_breaks_minutes",
153 | "no_copy": 0,
154 | "non_negative": 0,
155 | "options": null,
156 | "permlevel": 0,
157 | "placeholder": null,
158 | "precision": "",
159 | "print_hide": 0,
160 | "print_hide_if_no_value": 0,
161 | "print_width": null,
162 | "read_only": 0,
163 | "read_only_depends_on": null,
164 | "report_hide": 0,
165 | "reqd": 0,
166 | "search_index": 0,
167 | "show_dashboard": 0,
168 | "sort_options": 0,
169 | "translatable": 0,
170 | "unique": 0,
171 | "width": null
172 | },
173 | {
174 | "allow_in_quick_entry": 0,
175 | "allow_on_submit": 0,
176 | "bold": 0,
177 | "collapsible": 0,
178 | "collapsible_depends_on": null,
179 | "columns": 0,
180 | "default": null,
181 | "depends_on": null,
182 | "description": null,
183 | "docstatus": 0,
184 | "doctype": "Custom Field",
185 | "dt": "Attendance",
186 | "fetch_from": null,
187 | "fetch_if_empty": 0,
188 | "fieldname": "overtime",
189 | "fieldtype": "Float",
190 | "hidden": 0,
191 | "hide_border": 0,
192 | "hide_days": 0,
193 | "hide_seconds": 0,
194 | "ignore_user_permissions": 0,
195 | "ignore_xss_filter": 0,
196 | "in_global_search": 0,
197 | "in_list_view": 0,
198 | "in_preview": 0,
199 | "in_standard_filter": 0,
200 | "insert_after": "payment_hours",
201 | "is_system_generated": 1,
202 | "is_virtual": 0,
203 | "label": "Overtime",
204 | "length": 0,
205 | "link_filters": null,
206 | "mandatory_depends_on": null,
207 | "modified": "2025-11-05 11:56:08.512303",
208 | "module": "Nl Attendance Timesheet",
209 | "name": "Attendance-overtime",
210 | "no_copy": 0,
211 | "non_negative": 0,
212 | "options": null,
213 | "permlevel": 0,
214 | "placeholder": null,
215 | "precision": "",
216 | "print_hide": 0,
217 | "print_hide_if_no_value": 0,
218 | "print_width": null,
219 | "read_only": 1,
220 | "read_only_depends_on": null,
221 | "report_hide": 0,
222 | "reqd": 0,
223 | "search_index": 0,
224 | "show_dashboard": 0,
225 | "sort_options": 0,
226 | "translatable": 0,
227 | "unique": 0,
228 | "width": null
229 | },
230 | {
231 | "allow_in_quick_entry": 0,
232 | "allow_on_submit": 0,
233 | "bold": 0,
234 | "collapsible": 0,
235 | "collapsible_depends_on": null,
236 | "columns": 0,
237 | "default": null,
238 | "depends_on": "eval: doc.include_unpaid_breaks",
239 | "description": null,
240 | "docstatus": 0,
241 | "doctype": "Custom Field",
242 | "dt": "Shift Type",
243 | "fetch_from": null,
244 | "fetch_if_empty": 0,
245 | "fieldname": "min_hours_to_include_a_break",
246 | "fieldtype": "Float",
247 | "hidden": 0,
248 | "hide_border": 0,
249 | "hide_days": 0,
250 | "hide_seconds": 0,
251 | "ignore_user_permissions": 0,
252 | "ignore_xss_filter": 0,
253 | "in_global_search": 0,
254 | "in_list_view": 0,
255 | "in_preview": 0,
256 | "in_standard_filter": 0,
257 | "insert_after": "unpaid_breaks_minutes",
258 | "is_system_generated": 1,
259 | "is_virtual": 0,
260 | "label": "Min. hours to include a break",
261 | "length": 0,
262 | "link_filters": null,
263 | "mandatory_depends_on": null,
264 | "modified": "2025-11-05 11:31:30.648984",
265 | "module": "Nl Attendance Timesheet",
266 | "name": "Shift Type-min_hours_to_include_a_break",
267 | "no_copy": 0,
268 | "non_negative": 0,
269 | "options": null,
270 | "permlevel": 0,
271 | "placeholder": null,
272 | "precision": "",
273 | "print_hide": 0,
274 | "print_hide_if_no_value": 0,
275 | "print_width": null,
276 | "read_only": 0,
277 | "read_only_depends_on": null,
278 | "report_hide": 0,
279 | "reqd": 0,
280 | "search_index": 0,
281 | "show_dashboard": 0,
282 | "sort_options": 0,
283 | "translatable": 0,
284 | "unique": 0,
285 | "width": null
286 | },
287 | {
288 | "allow_in_quick_entry": 0,
289 | "allow_on_submit": 0,
290 | "bold": 0,
291 | "collapsible": 0,
292 | "collapsible_depends_on": null,
293 | "columns": 0,
294 | "default": null,
295 | "depends_on": null,
296 | "description": null,
297 | "docstatus": 0,
298 | "doctype": "Custom Field",
299 | "dt": "Salary Structure",
300 | "fetch_from": null,
301 | "fetch_if_empty": 0,
302 | "fieldname": "wage_based_salary_hours",
303 | "fieldtype": "Check",
304 | "hidden": 0,
305 | "hide_border": 0,
306 | "hide_days": 0,
307 | "hide_seconds": 0,
308 | "ignore_user_permissions": 0,
309 | "ignore_xss_filter": 0,
310 | "in_global_search": 0,
311 | "in_list_view": 0,
312 | "in_preview": 0,
313 | "in_standard_filter": 0,
314 | "insert_after": "salary_slip_based_on_timesheet",
315 | "is_system_generated": 1,
316 | "is_virtual": 0,
317 | "label": "Wage based salary (hours)",
318 | "length": 0,
319 | "link_filters": null,
320 | "mandatory_depends_on": null,
321 | "modified": "2025-11-05 11:27:37.447140",
322 | "module": "Nl Attendance Timesheet",
323 | "name": "Salary Structure-wage_based_salary_hours",
324 | "no_copy": 0,
325 | "non_negative": 0,
326 | "options": null,
327 | "permlevel": 0,
328 | "placeholder": null,
329 | "precision": "",
330 | "print_hide": 0,
331 | "print_hide_if_no_value": 0,
332 | "print_width": null,
333 | "read_only": 0,
334 | "read_only_depends_on": null,
335 | "report_hide": 0,
336 | "reqd": 0,
337 | "search_index": 0,
338 | "show_dashboard": 0,
339 | "sort_options": 0,
340 | "translatable": 0,
341 | "unique": 0,
342 | "width": null
343 | },
344 | {
345 | "allow_in_quick_entry": 0,
346 | "allow_on_submit": 0,
347 | "bold": 0,
348 | "collapsible": 0,
349 | "collapsible_depends_on": null,
350 | "columns": 0,
351 | "default": null,
352 | "depends_on": null,
353 | "description": null,
354 | "docstatus": 0,
355 | "doctype": "Custom Field",
356 | "dt": "Salary Slip",
357 | "fetch_from": "salary_structure.wage_based_salary_hours",
358 | "fetch_if_empty": 0,
359 | "fieldname": "wage_based_salary_hours",
360 | "fieldtype": "Check",
361 | "hidden": 0,
362 | "hide_border": 0,
363 | "hide_days": 0,
364 | "hide_seconds": 0,
365 | "ignore_user_permissions": 0,
366 | "ignore_xss_filter": 0,
367 | "in_global_search": 0,
368 | "in_list_view": 0,
369 | "in_preview": 0,
370 | "in_standard_filter": 0,
371 | "insert_after": "salary_slip_based_on_timesheet",
372 | "is_system_generated": 1,
373 | "is_virtual": 0,
374 | "label": "Wage based salary (hours)",
375 | "length": 0,
376 | "link_filters": null,
377 | "mandatory_depends_on": null,
378 | "modified": "2025-11-05 11:27:46.695647",
379 | "module": "Nl Attendance Timesheet",
380 | "name": "Salary Slip-wage_based_salary_hours",
381 | "no_copy": 0,
382 | "non_negative": 0,
383 | "options": null,
384 | "permlevel": 0,
385 | "placeholder": null,
386 | "precision": "",
387 | "print_hide": 0,
388 | "print_hide_if_no_value": 0,
389 | "print_width": null,
390 | "read_only": 1,
391 | "read_only_depends_on": null,
392 | "report_hide": 0,
393 | "reqd": 0,
394 | "search_index": 0,
395 | "show_dashboard": 0,
396 | "sort_options": 0,
397 | "translatable": 0,
398 | "unique": 0,
399 | "width": null
400 | },
401 | {
402 | "allow_in_quick_entry": 0,
403 | "allow_on_submit": 0,
404 | "bold": 0,
405 | "collapsible": 0,
406 | "collapsible_depends_on": null,
407 | "columns": 0,
408 | "default": null,
409 | "depends_on": "eval:doc.wage_based_salary_hours",
410 | "description": null,
411 | "docstatus": 0,
412 | "doctype": "Custom Field",
413 | "dt": "Salary Slip",
414 | "fetch_from": null,
415 | "fetch_if_empty": 0,
416 | "fieldname": "attendance_details_tab_break",
417 | "fieldtype": "Tab Break",
418 | "hidden": 0,
419 | "hide_border": 0,
420 | "hide_days": 0,
421 | "hide_seconds": 0,
422 | "ignore_user_permissions": 0,
423 | "ignore_xss_filter": 0,
424 | "in_global_search": 0,
425 | "in_list_view": 0,
426 | "in_preview": 0,
427 | "in_standard_filter": 0,
428 | "insert_after": "deduct_tax_for_unsubmitted_tax_exemption_proof",
429 | "is_system_generated": 1,
430 | "is_virtual": 0,
431 | "label": "Attendance Details",
432 | "length": 0,
433 | "link_filters": null,
434 | "mandatory_depends_on": null,
435 | "modified": "2025-11-05 11:36:51.318995",
436 | "module": "Nl Attendance Timesheet",
437 | "name": "Salary Slip-attendance_details_tab_break",
438 | "no_copy": 0,
439 | "non_negative": 0,
440 | "options": null,
441 | "permlevel": 0,
442 | "placeholder": null,
443 | "precision": "",
444 | "print_hide": 0,
445 | "print_hide_if_no_value": 0,
446 | "print_width": null,
447 | "read_only": 0,
448 | "read_only_depends_on": null,
449 | "report_hide": 0,
450 | "reqd": 0,
451 | "search_index": 0,
452 | "show_dashboard": 0,
453 | "sort_options": 0,
454 | "translatable": 0,
455 | "unique": 0,
456 | "width": null
457 | },
458 | {
459 | "allow_in_quick_entry": 0,
460 | "allow_on_submit": 0,
461 | "bold": 0,
462 | "collapsible": 1,
463 | "collapsible_depends_on": null,
464 | "columns": 0,
465 | "default": null,
466 | "depends_on": null,
467 | "description": null,
468 | "docstatus": 0,
469 | "doctype": "Custom Field",
470 | "dt": "Salary Slip",
471 | "fetch_from": null,
472 | "fetch_if_empty": 0,
473 | "fieldname": "attendance_section",
474 | "fieldtype": "Section Break",
475 | "hidden": 0,
476 | "hide_border": 0,
477 | "hide_days": 0,
478 | "hide_seconds": 0,
479 | "ignore_user_permissions": 0,
480 | "ignore_xss_filter": 0,
481 | "in_global_search": 0,
482 | "in_list_view": 0,
483 | "in_preview": 0,
484 | "in_standard_filter": 0,
485 | "insert_after": "attendance_details_tab_break",
486 | "is_system_generated": 1,
487 | "is_virtual": 0,
488 | "label": "Attendance",
489 | "length": 0,
490 | "link_filters": null,
491 | "mandatory_depends_on": null,
492 | "modified": "2025-11-05 11:37:02.522399",
493 | "module": "Nl Attendance Timesheet",
494 | "name": "Salary Slip-attendance_section",
495 | "no_copy": 0,
496 | "non_negative": 0,
497 | "options": null,
498 | "permlevel": 0,
499 | "placeholder": null,
500 | "precision": "",
501 | "print_hide": 0,
502 | "print_hide_if_no_value": 0,
503 | "print_width": null,
504 | "read_only": 0,
505 | "read_only_depends_on": null,
506 | "report_hide": 0,
507 | "reqd": 0,
508 | "search_index": 0,
509 | "show_dashboard": 0,
510 | "sort_options": 0,
511 | "translatable": 0,
512 | "unique": 0,
513 | "width": null
514 | },
515 | {
516 | "allow_in_quick_entry": 0,
517 | "allow_on_submit": 0,
518 | "bold": 0,
519 | "collapsible": 0,
520 | "collapsible_depends_on": null,
521 | "columns": 0,
522 | "default": null,
523 | "depends_on": null,
524 | "description": null,
525 | "docstatus": 0,
526 | "doctype": "Custom Field",
527 | "dt": "Salary Slip",
528 | "fetch_from": null,
529 | "fetch_if_empty": 0,
530 | "fieldname": "attendance",
531 | "fieldtype": "Table",
532 | "hidden": 0,
533 | "hide_border": 0,
534 | "hide_days": 0,
535 | "hide_seconds": 0,
536 | "ignore_user_permissions": 0,
537 | "ignore_xss_filter": 0,
538 | "in_global_search": 0,
539 | "in_list_view": 0,
540 | "in_preview": 0,
541 | "in_standard_filter": 0,
542 | "insert_after": "attendance_section",
543 | "is_system_generated": 1,
544 | "is_virtual": 0,
545 | "label": "Attendance",
546 | "length": 0,
547 | "link_filters": null,
548 | "mandatory_depends_on": null,
549 | "modified": "2025-11-05 11:37:42.830357",
550 | "module": "Nl Attendance Timesheet",
551 | "name": "Salary Slip-attendance",
552 | "no_copy": 0,
553 | "non_negative": 0,
554 | "options": "Navari Attendance",
555 | "permlevel": 0,
556 | "placeholder": null,
557 | "precision": "",
558 | "print_hide": 0,
559 | "print_hide_if_no_value": 0,
560 | "print_width": null,
561 | "read_only": 0,
562 | "read_only_depends_on": null,
563 | "report_hide": 0,
564 | "reqd": 0,
565 | "search_index": 0,
566 | "show_dashboard": 0,
567 | "sort_options": 0,
568 | "translatable": 0,
569 | "unique": 0,
570 | "width": null
571 | },
572 | {
573 | "allow_in_quick_entry": 0,
574 | "allow_on_submit": 0,
575 | "bold": 0,
576 | "collapsible": 1,
577 | "collapsible_depends_on": null,
578 | "columns": 0,
579 | "default": null,
580 | "depends_on": null,
581 | "description": null,
582 | "docstatus": 0,
583 | "doctype": "Custom Field",
584 | "dt": "Salary Slip",
585 | "fetch_from": null,
586 | "fetch_if_empty": 0,
587 | "fieldname": "overtime_section",
588 | "fieldtype": "Section Break",
589 | "hidden": 0,
590 | "hide_border": 0,
591 | "hide_days": 0,
592 | "hide_seconds": 0,
593 | "ignore_user_permissions": 0,
594 | "ignore_xss_filter": 0,
595 | "in_global_search": 0,
596 | "in_list_view": 0,
597 | "in_preview": 0,
598 | "in_standard_filter": 0,
599 | "insert_after": "attendance",
600 | "is_system_generated": 1,
601 | "is_virtual": 0,
602 | "label": "Overtime Details",
603 | "length": 0,
604 | "link_filters": null,
605 | "mandatory_depends_on": null,
606 | "modified": "2025-11-05 11:38:01.804221",
607 | "module": "Nl Attendance Timesheet",
608 | "name": "Salary Slip-overtime_section",
609 | "no_copy": 0,
610 | "non_negative": 0,
611 | "options": null,
612 | "permlevel": 0,
613 | "placeholder": null,
614 | "precision": "",
615 | "print_hide": 0,
616 | "print_hide_if_no_value": 0,
617 | "print_width": null,
618 | "read_only": 0,
619 | "read_only_depends_on": null,
620 | "report_hide": 0,
621 | "reqd": 0,
622 | "search_index": 0,
623 | "show_dashboard": 0,
624 | "sort_options": 0,
625 | "translatable": 0,
626 | "unique": 0,
627 | "width": null
628 | },
629 | {
630 | "allow_in_quick_entry": 0,
631 | "allow_on_submit": 0,
632 | "bold": 0,
633 | "collapsible": 0,
634 | "collapsible_depends_on": null,
635 | "columns": 0,
636 | "default": null,
637 | "depends_on": null,
638 | "description": null,
639 | "docstatus": 0,
640 | "doctype": "Custom Field",
641 | "dt": "Salary Slip",
642 | "fetch_from": null,
643 | "fetch_if_empty": 0,
644 | "fieldname": "regular_overtime",
645 | "fieldtype": "Table",
646 | "hidden": 0,
647 | "hide_border": 0,
648 | "hide_days": 0,
649 | "hide_seconds": 0,
650 | "ignore_user_permissions": 0,
651 | "ignore_xss_filter": 0,
652 | "in_global_search": 0,
653 | "in_list_view": 0,
654 | "in_preview": 0,
655 | "in_standard_filter": 0,
656 | "insert_after": "overtime_section",
657 | "is_system_generated": 1,
658 | "is_virtual": 0,
659 | "label": "Overtime 1.5",
660 | "length": 0,
661 | "link_filters": null,
662 | "mandatory_depends_on": null,
663 | "modified": "2025-11-05 11:38:15.582767",
664 | "module": "Nl Attendance Timesheet",
665 | "name": "Salary Slip-regular_overtime",
666 | "no_copy": 0,
667 | "non_negative": 0,
668 | "options": "Regular Overtime",
669 | "permlevel": 0,
670 | "placeholder": null,
671 | "precision": "",
672 | "print_hide": 0,
673 | "print_hide_if_no_value": 0,
674 | "print_width": null,
675 | "read_only": 0,
676 | "read_only_depends_on": null,
677 | "report_hide": 0,
678 | "reqd": 0,
679 | "search_index": 0,
680 | "show_dashboard": 0,
681 | "sort_options": 0,
682 | "translatable": 0,
683 | "unique": 0,
684 | "width": null
685 | },
686 | {
687 | "allow_in_quick_entry": 0,
688 | "allow_on_submit": 0,
689 | "bold": 0,
690 | "collapsible": 1,
691 | "collapsible_depends_on": null,
692 | "columns": 0,
693 | "default": null,
694 | "depends_on": null,
695 | "description": null,
696 | "docstatus": 0,
697 | "doctype": "Custom Field",
698 | "dt": "Salary Slip",
699 | "fetch_from": null,
700 | "fetch_if_empty": 0,
701 | "fieldname": "worked_hours_summary_section",
702 | "fieldtype": "Section Break",
703 | "hidden": 0,
704 | "hide_border": 0,
705 | "hide_days": 0,
706 | "hide_seconds": 0,
707 | "ignore_user_permissions": 0,
708 | "ignore_xss_filter": 0,
709 | "in_global_search": 0,
710 | "in_list_view": 0,
711 | "in_preview": 0,
712 | "in_standard_filter": 0,
713 | "insert_after": "holiday_overtime",
714 | "is_system_generated": 1,
715 | "is_virtual": 0,
716 | "label": "Worked Hours Summary",
717 | "length": 0,
718 | "link_filters": null,
719 | "mandatory_depends_on": null,
720 | "modified": "2025-11-05 11:42:12.367035",
721 | "module": "Nl Attendance Timesheet",
722 | "name": "Salary Slip-worked_hours_summary_section",
723 | "no_copy": 0,
724 | "non_negative": 0,
725 | "options": null,
726 | "permlevel": 0,
727 | "placeholder": null,
728 | "precision": "",
729 | "print_hide": 0,
730 | "print_hide_if_no_value": 0,
731 | "print_width": null,
732 | "read_only": 0,
733 | "read_only_depends_on": null,
734 | "report_hide": 0,
735 | "reqd": 0,
736 | "search_index": 0,
737 | "show_dashboard": 0,
738 | "sort_options": 0,
739 | "translatable": 0,
740 | "unique": 0,
741 | "width": null
742 | },
743 | {
744 | "allow_in_quick_entry": 0,
745 | "allow_on_submit": 0,
746 | "bold": 0,
747 | "collapsible": 0,
748 | "collapsible_depends_on": null,
749 | "columns": 0,
750 | "default": null,
751 | "depends_on": null,
752 | "description": null,
753 | "docstatus": 0,
754 | "doctype": "Custom Field",
755 | "dt": "Salary Slip",
756 | "fetch_from": null,
757 | "fetch_if_empty": 0,
758 | "fieldname": "navari_vf_cb_ss_02",
759 | "fieldtype": "Column Break",
760 | "hidden": 0,
761 | "hide_border": 0,
762 | "hide_days": 0,
763 | "hide_seconds": 0,
764 | "ignore_user_permissions": 0,
765 | "ignore_xss_filter": 0,
766 | "in_global_search": 0,
767 | "in_list_view": 0,
768 | "in_preview": 0,
769 | "in_standard_filter": 0,
770 | "insert_after": "regular_overtime",
771 | "is_system_generated": 1,
772 | "is_virtual": 0,
773 | "label": null,
774 | "length": 0,
775 | "link_filters": null,
776 | "mandatory_depends_on": null,
777 | "modified": "2025-11-05 11:43:28.239553",
778 | "module": "Nl Attendance Timesheet",
779 | "name": "Salary Slip-navari_vf_cb_ss_02",
780 | "no_copy": 0,
781 | "non_negative": 0,
782 | "options": null,
783 | "permlevel": 0,
784 | "placeholder": null,
785 | "precision": "",
786 | "print_hide": 0,
787 | "print_hide_if_no_value": 0,
788 | "print_width": null,
789 | "read_only": 0,
790 | "read_only_depends_on": null,
791 | "report_hide": 0,
792 | "reqd": 0,
793 | "search_index": 0,
794 | "show_dashboard": 0,
795 | "sort_options": 0,
796 | "translatable": 0,
797 | "unique": 0,
798 | "width": null
799 | },
800 | {
801 | "allow_in_quick_entry": 0,
802 | "allow_on_submit": 0,
803 | "bold": 0,
804 | "collapsible": 0,
805 | "collapsible_depends_on": null,
806 | "columns": 0,
807 | "default": null,
808 | "depends_on": null,
809 | "description": null,
810 | "docstatus": 0,
811 | "doctype": "Custom Field",
812 | "dt": "Salary Slip",
813 | "fetch_from": null,
814 | "fetch_if_empty": 0,
815 | "fieldname": "holiday_overtime",
816 | "fieldtype": "Table",
817 | "hidden": 0,
818 | "hide_border": 0,
819 | "hide_days": 0,
820 | "hide_seconds": 0,
821 | "ignore_user_permissions": 0,
822 | "ignore_xss_filter": 0,
823 | "in_global_search": 0,
824 | "in_list_view": 0,
825 | "in_preview": 0,
826 | "in_standard_filter": 0,
827 | "insert_after": "navari_vf_cb_ss_02",
828 | "is_system_generated": 1,
829 | "is_virtual": 0,
830 | "label": "Overtime 2.0",
831 | "length": 0,
832 | "link_filters": null,
833 | "mandatory_depends_on": null,
834 | "modified": "2025-11-05 11:40:10.723375",
835 | "module": "Nl Attendance Timesheet",
836 | "name": "Salary Slip-holiday_overtime",
837 | "no_copy": 0,
838 | "non_negative": 0,
839 | "options": "Holiday Overtime",
840 | "permlevel": 0,
841 | "placeholder": null,
842 | "precision": "",
843 | "print_hide": 0,
844 | "print_hide_if_no_value": 0,
845 | "print_width": null,
846 | "read_only": 0,
847 | "read_only_depends_on": null,
848 | "report_hide": 0,
849 | "reqd": 0,
850 | "search_index": 0,
851 | "show_dashboard": 0,
852 | "sort_options": 0,
853 | "translatable": 0,
854 | "unique": 0,
855 | "width": null
856 | },
857 | {
858 | "allow_in_quick_entry": 0,
859 | "allow_on_submit": 0,
860 | "bold": 0,
861 | "collapsible": 0,
862 | "collapsible_depends_on": null,
863 | "columns": 0,
864 | "default": null,
865 | "depends_on": null,
866 | "description": null,
867 | "docstatus": 0,
868 | "doctype": "Custom Field",
869 | "dt": "Salary Slip",
870 | "fetch_from": null,
871 | "fetch_if_empty": 0,
872 | "fieldname": "overtime_hours",
873 | "fieldtype": "Float",
874 | "hidden": 0,
875 | "hide_border": 0,
876 | "hide_days": 0,
877 | "hide_seconds": 0,
878 | "ignore_user_permissions": 0,
879 | "ignore_xss_filter": 0,
880 | "in_global_search": 0,
881 | "in_list_view": 0,
882 | "in_preview": 0,
883 | "in_standard_filter": 0,
884 | "insert_after": "regular_working_hours",
885 | "is_system_generated": 1,
886 | "is_virtual": 0,
887 | "label": "Overtime Hours",
888 | "length": 0,
889 | "link_filters": null,
890 | "mandatory_depends_on": null,
891 | "modified": "2025-11-05 11:44:28.502600",
892 | "module": "Nl Attendance Timesheet",
893 | "name": "Salary Slip-overtime_hours",
894 | "no_copy": 0,
895 | "non_negative": 0,
896 | "options": null,
897 | "permlevel": 0,
898 | "placeholder": null,
899 | "precision": "",
900 | "print_hide": 0,
901 | "print_hide_if_no_value": 0,
902 | "print_width": null,
903 | "read_only": 1,
904 | "read_only_depends_on": null,
905 | "report_hide": 0,
906 | "reqd": 0,
907 | "search_index": 0,
908 | "show_dashboard": 0,
909 | "sort_options": 0,
910 | "translatable": 0,
911 | "unique": 0,
912 | "width": null
913 | },
914 | {
915 | "allow_in_quick_entry": 0,
916 | "allow_on_submit": 0,
917 | "bold": 0,
918 | "collapsible": 0,
919 | "collapsible_depends_on": null,
920 | "columns": 0,
921 | "default": null,
922 | "depends_on": null,
923 | "description": null,
924 | "docstatus": 0,
925 | "doctype": "Custom Field",
926 | "dt": "Timesheet",
927 | "fetch_from": null,
928 | "fetch_if_empty": 0,
929 | "fieldname": "attendance",
930 | "fieldtype": "Link",
931 | "hidden": 1,
932 | "hide_border": 0,
933 | "hide_days": 0,
934 | "hide_seconds": 0,
935 | "ignore_user_permissions": 0,
936 | "ignore_xss_filter": 0,
937 | "in_global_search": 0,
938 | "in_list_view": 0,
939 | "in_preview": 0,
940 | "in_standard_filter": 0,
941 | "insert_after": "note",
942 | "is_system_generated": 1,
943 | "is_virtual": 0,
944 | "label": "Attendance",
945 | "length": 0,
946 | "link_filters": null,
947 | "mandatory_depends_on": null,
948 | "modified": "2025-11-05 11:26:29.600436",
949 | "module": "Nl Attendance Timesheet",
950 | "name": "Timesheet-attendance",
951 | "no_copy": 0,
952 | "non_negative": 0,
953 | "options": "Attendance",
954 | "permlevel": 0,
955 | "placeholder": null,
956 | "precision": "",
957 | "print_hide": 0,
958 | "print_hide_if_no_value": 0,
959 | "print_width": null,
960 | "read_only": 1,
961 | "read_only_depends_on": null,
962 | "report_hide": 0,
963 | "reqd": 0,
964 | "search_index": 0,
965 | "show_dashboard": 0,
966 | "sort_options": 0,
967 | "translatable": 0,
968 | "unique": 1,
969 | "width": null
970 | },
971 | {
972 | "allow_in_quick_entry": 0,
973 | "allow_on_submit": 0,
974 | "bold": 0,
975 | "collapsible": 0,
976 | "collapsible_depends_on": null,
977 | "columns": 0,
978 | "default": null,
979 | "depends_on": null,
980 | "description": null,
981 | "docstatus": 0,
982 | "doctype": "Custom Field",
983 | "dt": "Salary Slip",
984 | "fetch_from": null,
985 | "fetch_if_empty": 0,
986 | "fieldname": "holiday_hours",
987 | "fieldtype": "Float",
988 | "hidden": 0,
989 | "hide_border": 0,
990 | "hide_days": 0,
991 | "hide_seconds": 0,
992 | "ignore_user_permissions": 0,
993 | "ignore_xss_filter": 0,
994 | "in_global_search": 0,
995 | "in_list_view": 0,
996 | "in_preview": 0,
997 | "in_standard_filter": 0,
998 | "insert_after": "overtime_hours",
999 | "is_system_generated": 1,
1000 | "is_virtual": 0,
1001 | "label": "Holiday Hours",
1002 | "length": 0,
1003 | "link_filters": null,
1004 | "mandatory_depends_on": null,
1005 | "modified": "2025-11-05 11:43:02.813658",
1006 | "module": "Nl Attendance Timesheet",
1007 | "name": "Salary Slip-holiday_hours",
1008 | "no_copy": 0,
1009 | "non_negative": 0,
1010 | "options": null,
1011 | "permlevel": 0,
1012 | "placeholder": null,
1013 | "precision": "",
1014 | "print_hide": 0,
1015 | "print_hide_if_no_value": 0,
1016 | "print_width": null,
1017 | "read_only": 1,
1018 | "read_only_depends_on": null,
1019 | "report_hide": 0,
1020 | "reqd": 0,
1021 | "search_index": 0,
1022 | "show_dashboard": 0,
1023 | "sort_options": 0,
1024 | "translatable": 0,
1025 | "unique": 0,
1026 | "width": null
1027 | },
1028 | {
1029 | "allow_in_quick_entry": 0,
1030 | "allow_on_submit": 0,
1031 | "bold": 0,
1032 | "collapsible": 0,
1033 | "collapsible_depends_on": null,
1034 | "columns": 0,
1035 | "default": null,
1036 | "depends_on": null,
1037 | "description": null,
1038 | "docstatus": 0,
1039 | "doctype": "Custom Field",
1040 | "dt": "Salary Slip",
1041 | "fetch_from": null,
1042 | "fetch_if_empty": 0,
1043 | "fieldname": "navari_vf_cb_ss_01",
1044 | "fieldtype": "Column Break",
1045 | "hidden": 0,
1046 | "hide_border": 0,
1047 | "hide_days": 0,
1048 | "hide_seconds": 0,
1049 | "ignore_user_permissions": 0,
1050 | "ignore_xss_filter": 0,
1051 | "in_global_search": 0,
1052 | "in_list_view": 0,
1053 | "in_preview": 0,
1054 | "in_standard_filter": 0,
1055 | "insert_after": "holiday_hours",
1056 | "is_system_generated": 1,
1057 | "is_virtual": 0,
1058 | "label": null,
1059 | "length": 0,
1060 | "link_filters": null,
1061 | "mandatory_depends_on": null,
1062 | "modified": "2025-11-05 11:39:56.473597",
1063 | "module": "Nl Attendance Timesheet",
1064 | "name": "Salary Slip-navari_vf_cb_ss_01",
1065 | "no_copy": 0,
1066 | "non_negative": 0,
1067 | "options": null,
1068 | "permlevel": 0,
1069 | "placeholder": null,
1070 | "precision": "",
1071 | "print_hide": 0,
1072 | "print_hide_if_no_value": 0,
1073 | "print_width": null,
1074 | "read_only": 0,
1075 | "read_only_depends_on": null,
1076 | "report_hide": 0,
1077 | "reqd": 0,
1078 | "search_index": 0,
1079 | "show_dashboard": 0,
1080 | "sort_options": 0,
1081 | "translatable": 0,
1082 | "unique": 0,
1083 | "width": null
1084 | },
1085 | {
1086 | "allow_in_quick_entry": 0,
1087 | "allow_on_submit": 0,
1088 | "bold": 0,
1089 | "collapsible": 0,
1090 | "collapsible_depends_on": null,
1091 | "columns": 0,
1092 | "default": null,
1093 | "depends_on": null,
1094 | "description": null,
1095 | "docstatus": 0,
1096 | "doctype": "Custom Field",
1097 | "dt": "Salary Slip",
1098 | "fetch_from": "salary_structure.hour_rate",
1099 | "fetch_if_empty": 0,
1100 | "fieldname": "hourly_rate",
1101 | "fieldtype": "Currency",
1102 | "hidden": 0,
1103 | "hide_border": 0,
1104 | "hide_days": 0,
1105 | "hide_seconds": 0,
1106 | "ignore_user_permissions": 0,
1107 | "ignore_xss_filter": 0,
1108 | "in_global_search": 0,
1109 | "in_list_view": 0,
1110 | "in_preview": 0,
1111 | "in_standard_filter": 0,
1112 | "insert_after": "navari_vf_cb_ss_01",
1113 | "is_system_generated": 1,
1114 | "is_virtual": 0,
1115 | "label": "Hourly Rate",
1116 | "length": 0,
1117 | "link_filters": null,
1118 | "mandatory_depends_on": null,
1119 | "modified": "2025-11-05 11:43:44.830349",
1120 | "module": "Nl Attendance Timesheet",
1121 | "name": "Salary Slip-hourly_rate",
1122 | "no_copy": 0,
1123 | "non_negative": 0,
1124 | "options": null,
1125 | "permlevel": 0,
1126 | "placeholder": null,
1127 | "precision": "",
1128 | "print_hide": 0,
1129 | "print_hide_if_no_value": 0,
1130 | "print_width": null,
1131 | "read_only": 0,
1132 | "read_only_depends_on": null,
1133 | "report_hide": 0,
1134 | "reqd": 0,
1135 | "search_index": 0,
1136 | "show_dashboard": 0,
1137 | "sort_options": 0,
1138 | "translatable": 0,
1139 | "unique": 0,
1140 | "width": null
1141 | },
1142 | {
1143 | "allow_in_quick_entry": 0,
1144 | "allow_on_submit": 0,
1145 | "bold": 0,
1146 | "collapsible": 0,
1147 | "collapsible_depends_on": null,
1148 | "columns": 0,
1149 | "default": null,
1150 | "depends_on": null,
1151 | "description": null,
1152 | "docstatus": 0,
1153 | "doctype": "Custom Field",
1154 | "dt": "Salary Slip",
1155 | "fetch_from": null,
1156 | "fetch_if_empty": 0,
1157 | "fieldname": "regular_working_hours",
1158 | "fieldtype": "Float",
1159 | "hidden": 0,
1160 | "hide_border": 0,
1161 | "hide_days": 0,
1162 | "hide_seconds": 0,
1163 | "ignore_user_permissions": 0,
1164 | "ignore_xss_filter": 0,
1165 | "in_global_search": 0,
1166 | "in_list_view": 0,
1167 | "in_preview": 0,
1168 | "in_standard_filter": 0,
1169 | "insert_after": "worked_hours_summary_section",
1170 | "is_system_generated": 1,
1171 | "is_virtual": 0,
1172 | "label": "Regular Working Hours",
1173 | "length": 0,
1174 | "link_filters": null,
1175 | "mandatory_depends_on": null,
1176 | "modified": "2025-11-05 11:42:28.033108",
1177 | "module": "Nl Attendance Timesheet",
1178 | "name": "Salary Slip-regular_working_hours",
1179 | "no_copy": 0,
1180 | "non_negative": 0,
1181 | "options": null,
1182 | "permlevel": 0,
1183 | "placeholder": null,
1184 | "precision": "",
1185 | "print_hide": 0,
1186 | "print_hide_if_no_value": 0,
1187 | "print_width": null,
1188 | "read_only": 1,
1189 | "read_only_depends_on": null,
1190 | "report_hide": 0,
1191 | "reqd": 0,
1192 | "search_index": 0,
1193 | "show_dashboard": 0,
1194 | "sort_options": 0,
1195 | "translatable": 0,
1196 | "unique": 0,
1197 | "width": null
1198 | }
1199 | ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------